-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A simple sample for Jetpack Compose in Surface Duo (#31)
* Add Ktlint support * Fix codestyle issue * Use LayoutChangeCallback instead of manual checking * Move to Compose-dev17 * Add ComposeSample to the CI pipeline * Add README * Update .gitignore file * Update ComposeSample dependencies * Update kotlin and code cleanup * Remove test files * Add debug tag * Fix ktlint issues * Update compose and AGP * Remove proguard-rules * Remove buildtypes * urlFragment should be all lowercase * Update image assets * Update layout * Add ComposeSample into new pipeline file * Fix the rebase issue * Delete workspace.xml Co-authored-by: Craig Dunn <[email protected]>
- Loading branch information
1 parent
3880c69
commit 23b1459
Showing
51 changed files
with
1,271 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
### Android template | ||
# Built application files | ||
*.apk | ||
*.ap_ | ||
*.aab | ||
|
||
# Files for the ART/Dalvik VM | ||
*.dex | ||
|
||
# Java class files | ||
*.class | ||
|
||
# Generated files | ||
bin/ | ||
gen/ | ||
out/ | ||
release/ | ||
|
||
# Gradle files | ||
.gradle/ | ||
build/ | ||
|
||
# Local configuration file (sdk path, etc) | ||
local.properties | ||
|
||
# Proguard folder generated by Eclipse | ||
proguard/ | ||
|
||
# Log Files | ||
*.log | ||
|
||
# Android Studio Navigation editor temp files | ||
.navigation/ | ||
|
||
# Android Studio captures folder | ||
captures/ | ||
|
||
# IntelliJ | ||
*.iml | ||
.idea/ | ||
|
||
# Keystore files | ||
# Uncomment the following lines if you do not want to check your keystore files in. | ||
#*.jks | ||
#*.keystore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
page_type: sample | ||
name: "Surface Duo - ComposeSample" | ||
description: "A sample showing how to use Jetpack Compose to build an app on the Surface Duo." | ||
languages: | ||
- kotlin | ||
products: | ||
- surface-duo | ||
urlFragment: compose-sample | ||
--- | ||
|
||
# ComposeSample | ||
|
||
This sample is built with Jetpack Compose, the new UI framework in Android. | ||
|
||
Here are the requirements for the sample. | ||
|
||
- Jetpack Compose version: `0.1.0-dev17` | ||
|
||
- Kotlin version: `1.4.0-rc` | ||
|
||
- Gradle plugin version: `4.2.0-alpha07` | ||
|
||
- Android Studio version: `4.2 Canary 7` | ||
|
||
## Getting Started | ||
|
||
To learn how to load apps on the Surface Duo emulator, see the [documentation](https://docs.microsoft.com/dual-screen/android), and follow [the blog](https://devblogs.microsoft.com/surface-duo). | ||
|
||
|
||
## Features | ||
|
||
The sample uses [List-Detail](https://docs.microsoft.com/dual-screen/introduction#companion-pane) app pattern to show a list of image thumbnails in the single screen. When the app is spanned into two screens, it shows the full image in the other screen. To select the image item from the list will show the full image accordingly. | ||
|
||
![Screenshot](screenshots/Screenshot.png) | ||
|
||
## Contributing | ||
|
||
This project welcomes contributions and suggestions. Most contributions require you to agree to a | ||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us | ||
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. | ||
|
||
When you submit a pull request, a CLA bot will automatically determine whether you need to provide | ||
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions | ||
provided by the bot. You will only need to do this once across all repos using our CLA. | ||
|
||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). | ||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or | ||
contact [[email protected]](mailto:[email protected]) with any additional questions or comments. | ||
|
||
## License | ||
|
||
Copyright (c) Microsoft Corporation. | ||
|
||
MIT License | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* | ||
* * Copyright (c) Microsoft Corporation. All rights reserved. | ||
* * Licensed under the MIT License. | ||
* | ||
*/ | ||
|
||
apply plugin: 'com.android.application' | ||
apply plugin: 'kotlin-android' | ||
apply plugin: 'kotlin-android-extensions' | ||
|
||
android { | ||
compileSdkVersion rootProject.ext.compileSdkVersion | ||
buildToolsVersion rootProject.ext.buildToolsVersion | ||
|
||
defaultConfig { | ||
applicationId "com.microsoft.device.display.samples.composesample" | ||
minSdkVersion 21 | ||
targetSdkVersion 30 | ||
versionCode 1 | ||
versionName "1.0" | ||
|
||
testInstrumentationRunner config.testInstrumentationRunner | ||
} | ||
|
||
compileOptions { | ||
sourceCompatibility JavaVersion.VERSION_1_8 | ||
targetCompatibility JavaVersion.VERSION_1_8 | ||
} | ||
|
||
kotlinOptions { | ||
jvmTarget = '1.8' | ||
} | ||
|
||
buildFeatures { | ||
compose true | ||
} | ||
|
||
composeOptions { | ||
kotlinCompilerVersion "$kotlinVersion" | ||
kotlinCompilerExtensionVersion "$composeVersion" | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation kotlinDependencies.kotlinStdlib | ||
implementation androidxDependencies.ktxCore | ||
implementation androidxDependencies.appCompat | ||
implementation androidxDependencies.window | ||
implementation androidxDependencies.compose | ||
implementation androidxDependencies.composeRuntime | ||
implementation androidxDependencies.composeMaterial | ||
implementation androidxDependencies.composeUITooling | ||
|
||
implementation googleDependencies.material | ||
|
||
testImplementation testDependencies.junit | ||
androidTestImplementation instrumentationTestDependencies.junit | ||
androidTestImplementation instrumentationTestDependencies.espressoCore | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
package="com.microsoft.device.display.samples.composesample"> | ||
|
||
<application | ||
android:allowBackup="true" | ||
android:icon="@mipmap/ic_launcher" | ||
android:label="@string/app_name" | ||
android:roundIcon="@mipmap/ic_launcher_round" | ||
android:supportsRtl="true" | ||
android:theme="@style/Theme.ComposeSample"> | ||
<activity | ||
android:name=".MainActivity" | ||
android:label="@string/app_name" | ||
android:theme="@style/Theme.ComposeSample"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
|
||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
</activity> | ||
</application> | ||
|
||
</manifest> |
129 changes: 129 additions & 0 deletions
129
...seSample/app/src/main/java/com/microsoft/device/display/samples/composesample/HomePage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
* | ||
* * Copyright (c) Microsoft Corporation. All rights reserved. | ||
* * Licensed under the MIT License. | ||
* | ||
*/ | ||
|
||
package com.microsoft.device.display.samples.composesample | ||
|
||
import android.util.Log | ||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.Text | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.fillMaxHeight | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.preferredHeight | ||
import androidx.compose.foundation.layout.preferredWidth | ||
import androidx.compose.foundation.layout.wrapContentSize | ||
import androidx.compose.foundation.lazy.LazyColumnForIndexed | ||
import androidx.compose.foundation.selection.selectable | ||
import androidx.compose.material.Divider | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.livedata.observeAsState | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.res.imageResource | ||
import androidx.compose.ui.text.font.FontWeight | ||
import androidx.compose.ui.unit.dp | ||
import androidx.compose.ui.unit.sp | ||
import androidx.ui.tooling.preview.Preview | ||
import com.microsoft.device.display.samples.composesample.models.DataProvider | ||
import com.microsoft.device.display.samples.composesample.models.ImageModel | ||
import com.microsoft.device.display.samples.composesample.viewModels.AppStateViewModel | ||
|
||
private lateinit var appStateViewModel: AppStateViewModel | ||
private val DEBUG_TAG = "ComposeSample" | ||
|
||
@Preview | ||
@Composable | ||
fun HomePreview() { | ||
val models = DataProvider.imageModels | ||
ShowList(models = models) | ||
} | ||
|
||
@Composable | ||
fun Home(viewModel: AppStateViewModel) { | ||
appStateViewModel = viewModel | ||
SetupUI() | ||
} | ||
|
||
@Composable | ||
fun SetupUI() { | ||
val models = DataProvider.imageModels | ||
val isScreenSpannedLiveData = appStateViewModel.getIsScreenSpannedLiveData() | ||
val isScreenSpanned = isScreenSpannedLiveData.observeAsState(initial = false).value | ||
|
||
Log.i(DEBUG_TAG, "SetupUI isScreenSpanned: $isScreenSpanned") | ||
|
||
if (isScreenSpanned) { | ||
ShowDetailWithList(models) | ||
} else { | ||
ShowList(models) | ||
} | ||
} | ||
|
||
@Composable | ||
private fun ShowList(models: List<ImageModel>) { | ||
ShowListColumn(models, Modifier.fillMaxHeight() then Modifier.fillMaxWidth()) | ||
} | ||
|
||
@Composable | ||
private fun ShowListColumn(models: List<ImageModel>, modifier: Modifier) { | ||
val imageSelectionLiveData = appStateViewModel.getImageSelectionLiveData() | ||
val selectedIndex = imageSelectionLiveData.observeAsState(initial = 0).value | ||
|
||
// ScrollableColumn(modifier) { | ||
// models.forEachIndexed { index, model -> | ||
LazyColumnForIndexed( | ||
items = models, | ||
modifier = modifier | ||
) { index, item -> | ||
Row( | ||
modifier = Modifier.selectable( | ||
selected = (index == selectedIndex), | ||
onClick = { | ||
appStateViewModel.setImageSelectionLiveData(index) | ||
} | ||
) then Modifier.fillMaxWidth(), | ||
verticalGravity = Alignment.CenterVertically | ||
) { | ||
Image(asset = imageResource(item.image), modifier = Modifier.preferredHeight(100.dp).preferredWidth(150.dp)) | ||
Spacer(Modifier.preferredWidth(16.dp)) | ||
Column(modifier = Modifier.fillMaxHeight() then Modifier.padding(16.dp)) { | ||
Text(item.id, modifier = Modifier.fillMaxHeight().wrapContentSize(Alignment.Center), fontSize = 20.sp, fontWeight = FontWeight.Bold) | ||
Text(item.title, modifier = Modifier.fillMaxHeight().wrapContentSize(Alignment.Center)) | ||
} | ||
} | ||
Divider(color = Color.LightGray) | ||
} | ||
} | ||
|
||
@Composable | ||
fun ShowDetailWithList(models: List<ImageModel>) { | ||
val imageSelectionLiveData = appStateViewModel.getImageSelectionLiveData() | ||
val selectedIndex = imageSelectionLiveData.observeAsState(initial = 0).value | ||
val selectedImageModel = models[selectedIndex] | ||
|
||
Row( | ||
modifier = Modifier.fillMaxHeight().wrapContentSize(Alignment.Center) | ||
then Modifier.fillMaxWidth().wrapContentSize(Alignment.Center) | ||
) { | ||
ShowListColumn( | ||
models, Modifier.fillMaxHeight().wrapContentSize(Alignment.Center).weight(1f) | ||
) | ||
Column( | ||
modifier = Modifier.fillMaxHeight().wrapContentSize(Alignment.Center).weight(1f), | ||
horizontalGravity = Alignment.CenterHorizontally, | ||
verticalArrangement = Arrangement.spacedBy(space = 40.dp) | ||
) { | ||
Text(text = selectedImageModel.id, fontSize = 60.sp) | ||
Image(asset = imageResource(selectedImageModel.image)) | ||
} | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
...mple/app/src/main/java/com/microsoft/device/display/samples/composesample/MainActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* | ||
* * Copyright (c) Microsoft Corporation. All rights reserved. | ||
* * Licensed under the MIT License. | ||
* | ||
*/ | ||
|
||
package com.microsoft.device.display.samples.composesample | ||
|
||
import android.os.Bundle | ||
import android.os.Handler | ||
import android.os.Looper | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.compose.ui.platform.setContent | ||
import androidx.core.util.Consumer | ||
import androidx.lifecycle.ViewModelProvider | ||
import androidx.window.WindowLayoutInfo | ||
import androidx.window.WindowManager | ||
import com.microsoft.device.display.samples.composesample.ui.ComposeSampleTheme | ||
import com.microsoft.device.display.samples.composesample.viewModels.AppStateViewModel | ||
import java.util.concurrent.Executor | ||
|
||
class MainActivity : AppCompatActivity() { | ||
private lateinit var windowManager: WindowManager | ||
private lateinit var appStateViewModel: AppStateViewModel | ||
|
||
private val handler = Handler(Looper.getMainLooper()) | ||
private val mainThreadExecutor = Executor { r: Runnable -> handler.post(r) } | ||
private val layoutStateChangeCallback = LayoutStateChangeCallback() | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
windowManager = WindowManager(this, null) | ||
appStateViewModel = ViewModelProvider(this).get(AppStateViewModel::class.java) | ||
|
||
super.onCreate(savedInstanceState) | ||
|
||
setContent { | ||
ComposeSampleTheme { | ||
Home(appStateViewModel) | ||
} | ||
} | ||
} | ||
|
||
override fun onAttachedToWindow() { | ||
super.onAttachedToWindow() | ||
windowManager.registerLayoutChangeCallback(mainThreadExecutor, layoutStateChangeCallback) | ||
} | ||
|
||
override fun onDetachedFromWindow() { | ||
super.onDetachedFromWindow() | ||
windowManager.unregisterLayoutChangeCallback(layoutStateChangeCallback) | ||
} | ||
|
||
inner class LayoutStateChangeCallback : Consumer<WindowLayoutInfo> { | ||
override fun accept(newLayoutInfo: WindowLayoutInfo) { | ||
val isScreenSpanned = newLayoutInfo.displayFeatures.size > 0 | ||
appStateViewModel.setIsScreenSpannedLiveData(isScreenSpanned) | ||
} | ||
} | ||
} |
Oops, something went wrong.