From c7f949c4d41f6a11a277483cebf7ff971825ad50 Mon Sep 17 00:00:00 2001 From: Sorin Albu <74547562+soalb-m@users.noreply.github.com> Date: Wed, 9 Dec 2020 23:50:01 +0200 Subject: [PATCH] Added integration for Photo Editor app with new screen manager SDK (#48) Added integration for Photo Editor app with new screen manager SDK --- PhotoEditor/app/build.gradle | 7 +- .../samples/photoeditor/PhotoEditorUITest.kt | 42 ++- .../utils/ScreenInfoListenerImpl.kt | 55 ++++ PhotoEditor/app/src/main/AndroidManifest.xml | 2 +- .../samples/photoeditor/LiveDataExtensions.kt | 21 ++ .../samples/photoeditor/MainActivity.kt | 247 +++++++++--------- .../photoeditor/PhotoEditorApplication.kt | 16 ++ .../samples/photoeditor/PhotoEditorVM.kt | 17 -- .../photoeditor/PhotoEditorViewModel.kt | 71 +++++ .../src/main/res/drawable/ic_rotate_left.xml | 5 + .../src/main/res/drawable/ic_rotate_right.xml | 5 + .../app/src/main/res/drawable/ic_save.xml | 5 + .../main/res/drawable/seek_bar_ruler_dark.xml | 6 +- .../res/drawable/seek_bar_slider_dark.xml | 6 +- .../res/layout-land/single_screen_layout.xml | 6 +- .../res/layout-land/tools_dual_screen.xml | 6 +- .../res/layout-port/single_screen_layout.xml | 6 +- .../res/layout-port/tools_dual_screen.xml | 6 +- .../app/src/main/res/layout/activity_main.xml | 3 +- .../app/src/main/res/layout/buttons.xml | 5 + .../src/main/res/layout/buttons_vertical.xml | 5 + .../main/res/layout/picture_dual_screen.xml | 6 +- .../main/res/layout/single_screen_sliders.xml | 5 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../app/src/main/res/values/colors.xml | 6 +- .../app/src/main/res/values/dimens.xml | 6 +- .../res/values/ic_launcher_background.xml | 5 + .../app/src/main/res/values/strings.xml | 6 +- .../app/src/main/res/values/styles.xml | 6 +- .../samples/photoeditor/ExampleUnitTest.kt | 5 + PhotoEditor/build.gradle | 5 - PhotoEditor/dependencies.gradle | 22 +- PhotoEditor/gradle.properties | 5 + .../gradle/wrapper/gradle-wrapper.properties | 7 +- PhotoEditor/ktlint.gradle | 1 - PhotoEditor/settings.gradle | 5 + 37 files changed, 436 insertions(+), 206 deletions(-) create mode 100644 PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/utils/ScreenInfoListenerImpl.kt create mode 100644 PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/LiveDataExtensions.kt create mode 100644 PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorApplication.kt delete mode 100644 PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorVM.kt create mode 100644 PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorViewModel.kt diff --git a/PhotoEditor/app/build.gradle b/PhotoEditor/app/build.gradle index c4cff70..bcd50cc 100644 --- a/PhotoEditor/app/build.gradle +++ b/PhotoEditor/app/build.gradle @@ -1,7 +1,6 @@ /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. - * */ apply plugin: 'com.android.application' @@ -33,14 +32,16 @@ android { } dependencies { + implementation microsoftDependencies.screenManager + implementation microsoftDependencies.layouts + implementation microsoftDependencies.fragmentsHandler + implementation kotlinDependencies.kotlinStdlib implementation androidxDependencies.appCompat implementation androidxDependencies.constraintLayout implementation androidxDependencies.ktxCore implementation androidxDependencies.ktxFragment - implementation microsoftDependencies.dualScreenLayout - testImplementation testDependencies.junit androidTestImplementation instrumentationTestDependencies.junit androidTestImplementation instrumentationTestDependencies.espressoCore diff --git a/PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorUITest.kt b/PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorUITest.kt index e1d915d..a9535a2 100644 --- a/PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorUITest.kt +++ b/PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorUITest.kt @@ -1,3 +1,8 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + package com.microsoft.device.display.samples.photoeditor import android.content.Intent @@ -18,8 +23,12 @@ import androidx.test.rule.ActivityTestRule import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until -import com.microsoft.device.dualscreen.layout.ScreenHelper +import com.microsoft.device.display.samples.photoeditor.utils.ScreenInfoListenerImpl +import com.microsoft.device.dualscreen.ScreenManagerProvider import org.hamcrest.CoreMatchers.not +import org.junit.After +import org.junit.Assert +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -34,6 +43,21 @@ import org.hamcrest.CoreMatchers.`is` as iz class PhotoEditorUITest { @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) + private var screenInfoListener = ScreenInfoListenerImpl() + + @Before + fun setup() { + val screenManager = ScreenManagerProvider.getScreenManager() + screenManager.addScreenInfoListener(screenInfoListener) + } + + @After + fun tearDown() { + val screenManager = ScreenManagerProvider.getScreenManager() + screenManager.removeScreenInfoListener(screenInfoListener) + screenInfoListener.resetScreenInfo() + screenInfoListener.resetScreenInfoCounter() + } /** * Tests visibility of controls when app spanned vs. unspanned @@ -53,7 +77,7 @@ class PhotoEditorUITest { onView(withId(R.id.warmth)).check(matches(withEffectiveVisibility(Visibility.INVISIBLE))) spanFromLeft() - assertThat(isSpanned(), iz(true)) + waitForScreenInfoAndAssert { Assert.assertTrue(isSpanned()) } // Switched to dual-screen mode, so dropdown should not exist and all sliders should be visible onView(withId(R.id.controls)).check(doesNotExist()) @@ -100,7 +124,10 @@ class PhotoEditorUITest { device.wait(Until.hasObject(By.pkg(filesPackage).depth(0)), 3000) // timeout at 3 seconds // Before import, drawable is equal to prev - assertThat(prev, iz(activityRule.activity.findViewById(R.id.image).drawable)) + assertThat( + prev, + iz(activityRule.activity.findViewById(R.id.image).drawable) + ) // Hardcoded to select most recently saved file in Files app - must be an image file device.swipe(1550, 1230, 1550, 1230, 100) @@ -207,8 +234,13 @@ class PhotoEditorUITest { device.swipe(rightX, bottomY, rightX, middleY, closeSteps) } + private fun waitForScreenInfoAndAssert(assert: () -> Unit) { + screenInfoListener.waitForScreenInfoChanges() + assert() + screenInfoListener.resetScreenInfo() + } + private fun isSpanned(): Boolean { - onIdle() // wait until layout changes have been fully processed before checking - return ScreenHelper.isDualMode(activityRule.activity) + return screenInfoListener.screenInfo?.isDualMode() == true } } diff --git a/PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/utils/ScreenInfoListenerImpl.kt b/PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/utils/ScreenInfoListenerImpl.kt new file mode 100644 index 0000000..5b4d768 --- /dev/null +++ b/PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/utils/ScreenInfoListenerImpl.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +package com.microsoft.device.display.samples.photoeditor.utils + +import com.microsoft.device.dualscreen.ScreenInfo +import com.microsoft.device.dualscreen.ScreenInfoListener +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * Simple implementation for [ScreenInfoListener] that saves internally the last screen info data. + */ +class ScreenInfoListenerImpl : ScreenInfoListener { + private var _screenInfo: ScreenInfo? = null + val screenInfo: ScreenInfo? + get() = _screenInfo + private var screenInfoLatch: CountDownLatch? = null + + override fun onScreenInfoChanged(screenInfo: ScreenInfo) { + _screenInfo = screenInfo + screenInfoLatch?.countDown() + } + + /** + * Resets the last screen info to [null] + */ + fun resetScreenInfo() { + _screenInfo = null + } + + /** + * Resets screen info counter when waiting for a screen changes to happen before calling + * [waitForScreenInfoChanges]. + */ + fun resetScreenInfoCounter() { + screenInfoLatch = CountDownLatch(1) + } + + /** + * Blocks and waits for the next screen info changes to happen. + * @return {@code true} if the screen info changed before the timeout count reached zero and + * {@code false} if the waiting time elapsed before the changes happened. + */ + fun waitForScreenInfoChanges(): Boolean { + return try { + val result = screenInfoLatch?.await(10, TimeUnit.SECONDS) ?: false + result + } catch (e: InterruptedException) { + false + } + } +} diff --git a/PhotoEditor/app/src/main/AndroidManifest.xml b/PhotoEditor/app/src/main/AndroidManifest.xml index 8f304b7..840f6e3 100644 --- a/PhotoEditor/app/src/main/AndroidManifest.xml +++ b/PhotoEditor/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ LiveData.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer) { + class RemovableObserver : Observer { + override fun onChanged(t: T?) { + observer.onChanged(t) + removeObserver(this) + } + } + + observe(lifecycleOwner, RemovableObserver()) +} diff --git a/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/MainActivity.kt b/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/MainActivity.kt index d9fc25f..b7f11d5 100644 --- a/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/MainActivity.kt +++ b/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/MainActivity.kt @@ -1,7 +1,6 @@ /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. - * */ package com.microsoft.device.display.samples.photoeditor @@ -23,9 +22,7 @@ import android.view.DragEvent import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter -import android.widget.ImageButton import android.widget.SeekBar -import android.widget.Spinner import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -33,38 +30,68 @@ import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.core.app.ActivityCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.view.drawToBitmap -import com.microsoft.device.dualscreen.layout.ScreenHelper +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import com.microsoft.device.dualscreen.ScreenInfo +import com.microsoft.device.dualscreen.ScreenInfoListener +import com.microsoft.device.dualscreen.ScreenManagerProvider +import kotlinx.android.synthetic.main.buttons.* +import kotlinx.android.synthetic.main.picture_dual_screen.* +import kotlinx.android.synthetic.main.single_screen_sliders.* import java.io.IOException import java.time.LocalDateTime -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(), ScreenInfoListener { + companion object { // Request code for image select activity private const val SELECT_IMAGE = 1000 // Default progress value for SeekBar controls private const val DEFAULT_PROGRESS = 50 + } + + private val screenLayout = MutableLiveData() + private val viewModel: PhotoEditorViewModel by viewModels() + + private fun registerRunnable(runnable: Runnable) { + val obs = Observer { + runnable.run() + } + screenLayout.observeOnce(this, obs) + } - // Default property value for ImageFilterView attributes (state of original image) - private const val ORIGINAL_STATE = 1f + override fun onScreenInfoChanged(screenInfo: ScreenInfo) { + screenLayout.value = screenInfo + } + + override fun onResume() { + super.onResume() + ScreenManagerProvider.getScreenManager().addScreenInfoListener(this) + } + + override fun onPause() { + super.onPause() + ScreenManagerProvider.getScreenManager().removeScreenInfoListener(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - setupLayout(savedInstanceState) - savedInstanceState?.let { - val image = findViewById(R.id.image) + val setupLayoutRunnable = Runnable { + setupLayout() // Restore image - val vm by viewModels() - image.setImageDrawable(vm.getImage().value) + viewModel.getImage().value?.let { + image.setImageDrawable(viewModel.getImage().value) + } - image.brightness = it.getFloat("brightness") - image.saturation = it.getFloat("saturation") - image.warmth = it.getFloat("warmth") + image.brightness = viewModel.brightness + image.saturation = viewModel.saturation + image.warmth = viewModel.warmth } + registerRunnable(setupLayoutRunnable) } /** @@ -72,23 +99,27 @@ class MainActivity : AppCompatActivity() { * @param outState: Bundle that contains the information to pass between states */ override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - val image = findViewById(R.id.image) - // SeekBar progress data - outState.putFloat("saturation", image.saturation) - outState.putFloat("brightness", image.brightness) - outState.putFloat("warmth", image.warmth) + outState.putFloat("saturation", viewModel.saturation) + outState.putFloat("brightness", viewModel.brightness) + outState.putFloat("warmth", viewModel.warmth) // Selected control in dropdown (only present in single-screen views) - val controls = findViewById(R.id.controls) - controls?.let { - outState.putInt("selectedControl", it.selectedItemPosition) - } + outState.putInt("selectedControl", viewModel.selectedControl) // Actual edited image - saved in ViewModel - val vm by viewModels() - vm.updateImage(findViewById(R.id.image).drawable) + viewModel.updateImage(image.drawable) + + super.onSaveInstanceState(outState) + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + super.onRestoreInstanceState(savedInstanceState) + + viewModel.saturation = savedInstanceState.getFloat("saturation") + viewModel.brightness = savedInstanceState.getFloat("brightness") + viewModel.warmth = savedInstanceState.getFloat("warmth") + viewModel.selectedControl = savedInstanceState.getInt("selectedControl") } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -97,10 +128,10 @@ class MainActivity : AppCompatActivity() { // Select image to edit from photo gallery if (requestCode == SELECT_IMAGE && resultCode == Activity.RESULT_OK && data?.data != null) { val uri: Uri = data.data!! - val image = findViewById(R.id.image) image.setImageBitmap(BitmapFactory.decodeStream(contentResolver.openInputStream(uri))) - resetControls(image) + val resetControlsRunnable = Runnable { resetControls(image) } + registerRunnable(resetControlsRunnable) } } @@ -109,32 +140,33 @@ class MainActivity : AppCompatActivity() { * @param image: ImageFilterView object that contains current photo */ private fun resetControls(image: ImageFilterView) { - val sat = findViewById(R.id.saturation) - val bright = findViewById(R.id.brightness) - val warmth = findViewById(R.id.warmth) - // Reset SeekBar values - sat.progress = DEFAULT_PROGRESS - bright.progress = DEFAULT_PROGRESS + saturation.progress = DEFAULT_PROGRESS + brightness.progress = DEFAULT_PROGRESS warmth.progress = DEFAULT_PROGRESS - // Reset dropdown and SeekBar visibility if single-screen view - if (!ScreenHelper.isDualMode(this)) { - sat.visibility = View.VISIBLE - bright.visibility = View.INVISIBLE - warmth.visibility = View.INVISIBLE - findViewById(R.id.controls).setSelection(0) - } + resetSeekBarVisibility() // Reset image filters - image.saturation = ORIGINAL_STATE - image.brightness = ORIGINAL_STATE - image.warmth = ORIGINAL_STATE + viewModel.resetValues() + image.saturation = viewModel.saturation + image.brightness = viewModel.brightness + image.warmth = viewModel.warmth } - private fun setupLayout(savedInstanceState: Bundle?) { - val image = findViewById(R.id.image) + private fun resetSeekBarVisibility() { + // Reset dropdown and SeekBar visibility if single-screen view + screenLayout.value?.let { screenInfo -> + if (!screenInfo.isDualMode()) { + saturation.visibility = View.VISIBLE + brightness.visibility = View.INVISIBLE + warmth.visibility = View.INVISIBLE + controls.setSelection(0) + } + } + } + private fun setupLayout() { // Set up click handling for importing images from photo gallery image.setOnClickListener { val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) @@ -164,15 +196,21 @@ class MainActivity : AppCompatActivity() { } // Set up all controls - setUpSaturation(image, savedInstanceState?.getFloat("saturation")) - setUpBrightness(image, savedInstanceState?.getFloat("brightness")) - setUpWarmth(image, savedInstanceState?.getFloat("warmth")) + setUpSaturation(image, viewModel.saturation) + setUpBrightness(image, viewModel.brightness) + setUpWarmth(image, viewModel.warmth) setUpRotate(image) setUpSave(image) - // Set up single screen control dropdown - if (!ScreenHelper.isDualMode(this)) { - setUpToggle(savedInstanceState?.getInt("selectedControl")) + prepareToggle() + } + + private fun prepareToggle() { + screenLayout.value?.let { screenInfo -> + // Set up single screen control dropdown + if (!screenInfo.isDualMode()) { + setUpToggle(viewModel.selectedControl) + } } } @@ -181,7 +219,7 @@ class MainActivity : AppCompatActivity() { * @param image: ImageFilterView object that needs to be reset */ private fun hideDropTarget(image: ImageFilterView) { - image.alpha = ORIGINAL_STATE + image.alpha = 1f image.setPadding(0, 0, 0, 0) image.cropToPadding = false image.setBackgroundColor(Color.TRANSPARENT) @@ -220,8 +258,6 @@ class MainActivity : AppCompatActivity() { private fun setUpToggle(selectedControl: Int?) { // Set up contents of controls dropdown - val controls = findViewById(R.id.controls) - ArrayAdapter.createFromResource( this, R.array.controls_array, @@ -235,26 +271,22 @@ class MainActivity : AppCompatActivity() { controls.setSelection(selectedControl ?: 0) // Set up response to changing the selected control - val sat = findViewById(R.id.saturation) - val bright = findViewById(R.id.brightness) - val warmth = findViewById(R.id.warmth) - controls.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) { when (parent.getItemAtPosition(pos)) { getString(R.string.saturation) -> { - sat.visibility = View.VISIBLE - bright.visibility = View.INVISIBLE + saturation.visibility = View.VISIBLE + brightness.visibility = View.INVISIBLE warmth.visibility = View.INVISIBLE } getString(R.string.brightness) -> { - sat.visibility = View.INVISIBLE - bright.visibility = View.VISIBLE + saturation.visibility = View.INVISIBLE + brightness.visibility = View.VISIBLE warmth.visibility = View.INVISIBLE } getString(R.string.warmth) -> { - sat.visibility = View.INVISIBLE - bright.visibility = View.INVISIBLE + saturation.visibility = View.INVISIBLE + brightness.visibility = View.INVISIBLE warmth.visibility = View.VISIBLE } } @@ -265,92 +297,74 @@ class MainActivity : AppCompatActivity() { } private fun setUpWarmth(image: ImageFilterView, progress: Float?) { - val warmth = findViewById(R.id.warmth) - // Restore value from previous state if available progress?.let { image.warmth = it warmth.progress = (it * DEFAULT_PROGRESS).toInt() + viewModel.warmth = image.warmth } - warmth.setOnSeekBarChangeListener(object : - SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged( - seek: SeekBar, - progress: Int, - fromUser: Boolean - ) { - // warmth from 0.5 (cold) to 1 (original) to 2 (warm), progress from 0 to 100 - image.warmth = progress.toFloat() / DEFAULT_PROGRESS - } + warmth.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seek: SeekBar, progress: Int, fromUser: Boolean) { + // warmth from 0.5 (cold) to 1 (original) to 2 (warm), progress from 0 to 100 + image.warmth = progress.toFloat() / DEFAULT_PROGRESS + viewModel.warmth = image.warmth + } - override fun onStartTrackingTouch(seek: SeekBar) {} + override fun onStartTrackingTouch(seek: SeekBar) {} - override fun onStopTrackingTouch(seek: SeekBar) {} - }) + override fun onStopTrackingTouch(seek: SeekBar) {} + }) } private fun setUpBrightness(image: ImageFilterView, progress: Float?) { - val brightness = findViewById(R.id.brightness) - // Restore value from previous state if available progress?.let { image.brightness = it brightness.progress = (it * DEFAULT_PROGRESS).toInt() + viewModel.brightness = image.brightness } - brightness.setOnSeekBarChangeListener(object : - SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged( - seek: SeekBar, - progress: Int, - fromUser: Boolean - ) { - // brightness from 0 (black) to 1 (original) to 2 (twice as bright), progress from 0 to 100 - image.brightness = progress.toFloat() / DEFAULT_PROGRESS - } + brightness.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seek: SeekBar, progress: Int, fromUser: Boolean) { + // brightness from 0 (black) to 1 (original) to 2 (twice as bright), progress from 0 to 100 + image.brightness = progress.toFloat() / DEFAULT_PROGRESS + viewModel.brightness = image.brightness + } - override fun onStartTrackingTouch(seek: SeekBar) {} + override fun onStartTrackingTouch(seek: SeekBar) {} - override fun onStopTrackingTouch(seek: SeekBar) {} - }) + override fun onStopTrackingTouch(seek: SeekBar) {} + }) } private fun setUpSaturation(image: ImageFilterView, progress: Float?) { - val saturation = findViewById(R.id.saturation) - // Restore value from previous state if available progress?.let { image.saturation = it saturation.progress = (it * DEFAULT_PROGRESS).toInt() + viewModel.saturation = image.saturation } - saturation.setOnSeekBarChangeListener(object : - SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged( - seek: SeekBar, - progress: Int, - fromUser: Boolean - ) { - // saturation from 0 (grayscale) to 1 (original) to 2 (hyper-saturated), progress from 0 to 100 - image.saturation = progress.toFloat() / DEFAULT_PROGRESS - } + saturation.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seek: SeekBar, progress: Int, fromUser: Boolean) { + // saturation from 0 (grayscale) to 1 (original) to 2 (hyper-saturated), progress from 0 to 100 + image.saturation = progress.toFloat() / DEFAULT_PROGRESS + viewModel.saturation = image.saturation + } - override fun onStartTrackingTouch(seek: SeekBar) {} + override fun onStartTrackingTouch(seek: SeekBar) {} - override fun onStopTrackingTouch(seek: SeekBar) {} - }) + override fun onStopTrackingTouch(seek: SeekBar) {} + }) } private fun setUpRotate(image: ImageFilterView) { - val left = findViewById(R.id.rotate_left) - - left.setOnClickListener { + rotate_left.setOnClickListener { applyRotationMatrix(270f, image) } - val right = findViewById(R.id.rotate_right) - right.setOnClickListener { + rotate_right.setOnClickListener { applyRotationMatrix(90f, image) } } @@ -374,7 +388,6 @@ class MainActivity : AppCompatActivity() { } private fun setUpSave(image: ImageFilterView) { - val save = findViewById(R.id.save) save.setOnClickListener { // Get current size of drawable so entire ImageView is not drawn to bitmap val rect = RectF() diff --git a/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorApplication.kt b/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorApplication.kt new file mode 100644 index 0000000..22f0b6d --- /dev/null +++ b/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorApplication.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +package com.microsoft.device.display.samples.photoeditor + +import android.app.Application +import com.microsoft.device.dualscreen.ScreenManagerProvider + +class PhotoEditorApplication : Application() { + override fun onCreate() { + super.onCreate() + ScreenManagerProvider.init(this) + } +} diff --git a/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorVM.kt b/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorVM.kt deleted file mode 100644 index 9f99819..0000000 --- a/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorVM.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.microsoft.device.display.samples.photoeditor - -import android.graphics.drawable.Drawable -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel - -class PhotoEditorVM : ViewModel() { - private var image = MutableLiveData() - - fun updateImage(newImage: Drawable) { - image.value = newImage - } - - fun getImage(): MutableLiveData { - return image - } -} diff --git a/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorViewModel.kt b/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorViewModel.kt new file mode 100644 index 0000000..2c0d967 --- /dev/null +++ b/PhotoEditor/app/src/main/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorViewModel.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +package com.microsoft.device.display.samples.photoeditor + +import android.graphics.drawable.Drawable +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class PhotoEditorViewModel : ViewModel() { + companion object { + // Default property value for ImageFilterView attributes (state of original image) + const val DEFAULT_ATTRIBUTE_VALUE = 1f + + // Default property value for ImageFilterView attributes (state of original image) + private const val DEFAULT_SELECTED_CONTROL = 0 + } + + private var image = MutableLiveData() + private var _saturation = MutableLiveData() + private var _brightness = MutableLiveData() + private var _warmth = MutableLiveData() + private var _selectedControl = MutableLiveData() + + fun updateImage(newImage: Drawable) { + image.value = newImage + } + + fun getImage(): MutableLiveData { + return image + } + + var saturation: Float + set(value) { + _saturation.value = value + } + get() { + return _saturation.value ?: DEFAULT_ATTRIBUTE_VALUE + } + + var brightness: Float + set(value) { + _brightness.value = value + } + get() { + return _brightness.value ?: DEFAULT_ATTRIBUTE_VALUE + } + + var warmth: Float + set(value) { + _warmth.value = value + } + get() { + return _warmth.value ?: DEFAULT_ATTRIBUTE_VALUE + } + var selectedControl: Int + set(value) { + _selectedControl.value = value + } + get() { + return _selectedControl.value ?: DEFAULT_SELECTED_CONTROL + } + + fun resetValues() { + _brightness.value = DEFAULT_ATTRIBUTE_VALUE + _saturation.value = DEFAULT_ATTRIBUTE_VALUE + _warmth.value = DEFAULT_ATTRIBUTE_VALUE + } +} diff --git a/PhotoEditor/app/src/main/res/drawable/ic_rotate_left.xml b/PhotoEditor/app/src/main/res/drawable/ic_rotate_left.xml index be955f1..95044f6 100644 --- a/PhotoEditor/app/src/main/res/drawable/ic_rotate_left.xml +++ b/PhotoEditor/app/src/main/res/drawable/ic_rotate_left.xml @@ -1,3 +1,8 @@ + + + + diff --git a/PhotoEditor/app/src/main/res/drawable/seek_bar_slider_dark.xml b/PhotoEditor/app/src/main/res/drawable/seek_bar_slider_dark.xml index 8095f88..b870e30 100644 --- a/PhotoEditor/app/src/main/res/drawable/seek_bar_slider_dark.xml +++ b/PhotoEditor/app/src/main/res/drawable/seek_bar_slider_dark.xml @@ -1,9 +1,7 @@ - + + + + + + + + diff --git a/PhotoEditor/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/PhotoEditor/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 036d09b..b9e0fb0 100644 --- a/PhotoEditor/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/PhotoEditor/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,4 +1,9 @@ + + diff --git a/PhotoEditor/app/src/main/res/values/colors.xml b/PhotoEditor/app/src/main/res/values/colors.xml index 24a1add..a9df384 100644 --- a/PhotoEditor/app/src/main/res/values/colors.xml +++ b/PhotoEditor/app/src/main/res/values/colors.xml @@ -1,8 +1,6 @@ diff --git a/PhotoEditor/app/src/main/res/values/dimens.xml b/PhotoEditor/app/src/main/res/values/dimens.xml index c7a6802..b8c5036 100644 --- a/PhotoEditor/app/src/main/res/values/dimens.xml +++ b/PhotoEditor/app/src/main/res/values/dimens.xml @@ -1,8 +1,6 @@ diff --git a/PhotoEditor/app/src/main/res/values/ic_launcher_background.xml b/PhotoEditor/app/src/main/res/values/ic_launcher_background.xml index c5d5899..1a2a856 100644 --- a/PhotoEditor/app/src/main/res/values/ic_launcher_background.xml +++ b/PhotoEditor/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,9 @@ + + #FFFFFF \ No newline at end of file diff --git a/PhotoEditor/app/src/main/res/values/strings.xml b/PhotoEditor/app/src/main/res/values/strings.xml index 246ede8..1a23837 100644 --- a/PhotoEditor/app/src/main/res/values/strings.xml +++ b/PhotoEditor/app/src/main/res/values/strings.xml @@ -1,8 +1,6 @@ diff --git a/PhotoEditor/app/src/main/res/values/styles.xml b/PhotoEditor/app/src/main/res/values/styles.xml index 182d93a..98b9ab6 100644 --- a/PhotoEditor/app/src/main/res/values/styles.xml +++ b/PhotoEditor/app/src/main/res/values/styles.xml @@ -1,8 +1,6 @@ diff --git a/PhotoEditor/app/src/test/java/com/microsoft/device/display/samples/photoeditor/ExampleUnitTest.kt b/PhotoEditor/app/src/test/java/com/microsoft/device/display/samples/photoeditor/ExampleUnitTest.kt index b399579..aa9812a 100644 --- a/PhotoEditor/app/src/test/java/com/microsoft/device/display/samples/photoeditor/ExampleUnitTest.kt +++ b/PhotoEditor/app/src/test/java/com/microsoft/device/display/samples/photoeditor/ExampleUnitTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + package com.microsoft.device.display.samples.photoeditor import org.junit.Assert.assertEquals diff --git a/PhotoEditor/build.gradle b/PhotoEditor/build.gradle index fc54a3d..6ed8e4b 100644 --- a/PhotoEditor/build.gradle +++ b/PhotoEditor/build.gradle @@ -1,7 +1,6 @@ /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. - * */ // Top-level build file where you can add configuration options common to all sub-projects/modules. @@ -25,10 +24,6 @@ allprojects { repositories { google() jcenter() - - maven { - url config.duoSdkUrl - } } apply from: "$rootDir/ktlint.gradle" } diff --git a/PhotoEditor/dependencies.gradle b/PhotoEditor/dependencies.gradle index 67911a0..31610da 100644 --- a/PhotoEditor/dependencies.gradle +++ b/PhotoEditor/dependencies.gradle @@ -1,25 +1,20 @@ /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. - * */ ext { - gradlePluginVersion = "3.6.3" - kotlinVersion = "1.3.72" + gradlePluginVersion = "4.1.1" + kotlinVersion = "1.4.21" compileSdkVersion = 29 buildToolsVersion = '29.0.2' targetSdkVersion = compileSdkVersion minSdkVersion = compileSdkVersion - //Surface Duo - duo_url = "https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1" - config = [ + duoSdkVersion : "1.0.0-beta1", gradlePlugin : "com.android.tools.build:gradle:$gradlePluginVersion", kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion", - duoSdkUrl : - "https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1", testInstrumentationRunner: "androidx.test.runner.AndroidJUnitRunner" ] @@ -54,11 +49,18 @@ ext { ] //Microsoft dependencies version - dualScreenLayoutVersion = "1.0.0-alpha01" + screenManagerWindowManagerVersion = config.duoSdkVersion + screenManagerDisplayMaskVersion = config.duoSdkVersion + fragmentsHandlerVersion = config.duoSdkVersion + layoutsVersion = config.duoSdkVersion microsoftDependencies = [ - dualScreenLayout: "com.microsoft.device:dualscreen-layout:$dualScreenLayoutVersion" + screenManagerWindowManager: "com.microsoft.device.dualscreen:screenmanager-windowmanager:$screenManagerWindowManagerVersion", + screenManagerDisplayMask : "com.microsoft.device.dualscreen:screenmanager-displaymask:$screenManagerDisplayMaskVersion", + fragmentsHandler : "com.microsoft.device.dualscreen:fragmentshandler:$fragmentsHandlerVersion", + layouts : "com.microsoft.device.dualscreen:layouts:$layoutsVersion", ] + microsoftDependencies["screenManager"] = microsoftDependencies.screenManagerWindowManager //Test dependencies version junitVersion = "4.13" diff --git a/PhotoEditor/gradle.properties b/PhotoEditor/gradle.properties index 4d15d01..a592b98 100644 --- a/PhotoEditor/gradle.properties +++ b/PhotoEditor/gradle.properties @@ -1,3 +1,8 @@ +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# + # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* diff --git a/PhotoEditor/gradle/wrapper/gradle-wrapper.properties b/PhotoEditor/gradle/wrapper/gradle-wrapper.properties index 7f823a1..70eeb9a 100644 --- a/PhotoEditor/gradle/wrapper/gradle-wrapper.properties +++ b/PhotoEditor/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,11 @@ +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# + #Mon Jun 15 14:27:57 EDT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/PhotoEditor/ktlint.gradle b/PhotoEditor/ktlint.gradle index 296aa17..edf036e 100644 --- a/PhotoEditor/ktlint.gradle +++ b/PhotoEditor/ktlint.gradle @@ -1,7 +1,6 @@ /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. - * */ repositories { diff --git a/PhotoEditor/settings.gradle b/PhotoEditor/settings.gradle index 72ef60b..c2a49c0 100644 --- a/PhotoEditor/settings.gradle +++ b/PhotoEditor/settings.gradle @@ -1,2 +1,7 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + include ':app' rootProject.name = "Photo Editor" \ No newline at end of file