* Update gradle version

* Update readme

* Update dependencies

* Update CompanionPane sample

* Update ComposeGallery sample

* Update Diary sample

* Update DragAndDrop sample

* Update DualView sample

* Update DyAdd sample

* Update ExtendedCanvas sample

* Update ListDetail sample

* Update NavRail sample

* Update SourceEditor sample

* Update TwoPage sample

* Update VideoPlusChat sample

* Fix readme table format
This commit is contained in:
Kristen Halper 2022-08-18 10:25:16 -07:00 коммит произвёл GitHub
Родитель a502ff8cc2
Коммит 51fbe4e5b1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
70 изменённых файлов: 472 добавлений и 726 удалений

Просмотреть файл

@ -36,6 +36,7 @@ android {
excludes += ['META-INF/licenses/**', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
}
}
namespace 'com.microsoft.device.display.samples.companionpane'
}
dependencies {
@ -53,6 +54,8 @@ dependencies {
implementation microsoftDependencies.twoPaneLayout
implementation microsoftDependencies.windowState
implementation googleDependencies.material
androidTestImplementation testDependencies.androidxTestCore
androidTestImplementation testDependencies.androidxTestRules
androidTestImplementation testDependencies.androidxTestRunner
@ -62,5 +65,5 @@ dependencies {
androidTestImplementation testDependencies.uiAutomator
androidTestImplementation microsoftDependencies.composeTesting
implementation googleDependencies.material
debugImplementation testDependencies.composeUITestManifest
}

Просмотреть файл

@ -10,49 +10,34 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.microsoft.device.display.samples.companionpane.ui.theme.CompanionPaneAppTheme
import com.microsoft.device.dualscreen.testing.compose.foldableRuleChain
import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.testing.compose.simulateHorizontalFoldingFeature
import com.microsoft.device.dualscreen.testing.compose.simulateVerticalFoldingFeature
import com.microsoft.device.dualscreen.testing.filters.DeviceOrientation
import com.microsoft.device.dualscreen.windowstate.WindowMode
import com.microsoft.device.dualscreen.windowstate.rememberWindowState
import com.microsoft.device.dualscreen.testing.filters.DualScreenTest
import com.microsoft.device.dualscreen.testing.filters.SingleScreenTest
import com.microsoft.device.dualscreen.testing.rules.FoldableTestRule
import com.microsoft.device.dualscreen.testing.runner.FoldableJUnit4ClassRunner
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(FoldableJUnit4ClassRunner::class)
class LayoutTest {
private val composeTestRule = createAndroidComposeRule<MainActivity>()
private val publisherRule = WindowLayoutInfoPublisherRule()
private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private val foldableTestRule = FoldableTestRule()
@get: Rule
val testRule: TestRule
init {
testRule = RuleChain.outerRule(publisherRule).around(composeTestRule)
RuleChain.outerRule(composeTestRule)
}
@get:Rule
val testRule: TestRule = foldableRuleChain(composeTestRule, foldableTestRule)
/**
* Tests that the single portrait layout appears when one pane is shown and the device is in
* the portrait orientation
*/
@Test
@DeviceOrientation(orientation = UiAutomation.ROTATION_FREEZE_0)
@SingleScreenTest(orientation = UiAutomation.ROTATION_FREEZE_0)
fun app_testSinglePortraitLayout() {
composeTestRule.setContent {
CompanionPaneAppTheme {
CompanionPaneAppContent(WindowMode.SINGLE_PORTRAIT)
}
}
// Check that single portrait layout is shown
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.single_port))
composeTestRule.onNodeWithTag(getString(R.string.single_port))
.assertIsDisplayed()
}
@ -61,16 +46,10 @@ class LayoutTest {
* the landscape orientation
*/
@Test
@DeviceOrientation(orientation = UiAutomation.ROTATION_FREEZE_90)
@SingleScreenTest(orientation = UiAutomation.ROTATION_FREEZE_90)
fun app_testSingleLandscapeLayout() {
composeTestRule.setContent {
CompanionPaneAppTheme {
CompanionPaneAppContent(WindowMode.SINGLE_LANDSCAPE)
}
}
// Check that single landscape layout is shown
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.single_land))
composeTestRule.onNodeWithTag(getString(R.string.single_land))
.assertIsDisplayed()
}
@ -79,20 +58,12 @@ class LayoutTest {
*/
@ExperimentalTestApi
@Test
@DualScreenTest(orientation = UiAutomation.ROTATION_FREEZE_0)
fun app_testDualPortraitLayout() {
composeTestRule.setContent {
CompanionPaneAppTheme {
CompanionPaneAppContent(WindowMode.DUAL_PORTRAIT)
}
}
// Simulate vertical foldingFeature
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
// Check that dual portrait panes are shown
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane1))
composeTestRule.onNodeWithTag(getString(R.string.dual_port_pane1))
.assertIsDisplayed()
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane2))
composeTestRule.onNodeWithTag(getString(R.string.dual_port_pane2))
.assertIsDisplayed()
}
@ -101,50 +72,12 @@ class LayoutTest {
*/
@ExperimentalTestApi
@Test
@DualScreenTest(orientation = UiAutomation.ROTATION_FREEZE_90)
fun app_testDualLandscapeLayout() {
composeTestRule.setContent {
CompanionPaneAppTheme {
CompanionPaneAppContent(WindowMode.DUAL_LANDSCAPE)
}
}
// Simulate horizontal foldingFeature
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
// Check that dual landscape panes are shown
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane1))
composeTestRule.onNodeWithTag(getString(R.string.dual_land_pane1))
.assertIsDisplayed()
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane2))
.assertIsDisplayed()
}
/**
* Test that app responds correctly when fold orientation switches from horizontal to vertical
*/
@Test
fun app_testDualLayoutRespondsToFoldOrientationChange() {
composeTestRule.setContent {
CompanionPaneAppTheme {
CompanionPaneApp(composeTestRule.activity.rememberWindowState())
}
}
// Simulate horizontal foldingFeature
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)
// Check that dual landscape panes are shown
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane1))
.assertIsDisplayed()
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_land_pane2))
.assertIsDisplayed()
// Simulate vertical foldingFeature
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
// Check that dual portrait panes are shown
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane1))
.assertIsDisplayed()
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.dual_port_pane2))
composeTestRule.onNodeWithTag(getString(R.string.dual_land_pane2))
.assertIsDisplayed()
}
}

Просмотреть файл

@ -8,28 +8,17 @@ package com.microsoft.device.display.samples.companionpane
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.hasParent
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.microsoft.device.display.samples.companionpane.ui.theme.CompanionPaneAppTheme
import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.windowstate.WindowMode
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
class TopAppBarTest {
private val composeTestRule = createAndroidComposeRule<MainActivity>()
private val publisherRule = WindowLayoutInfoPublisherRule()
@get: Rule
val testRule: TestRule
init {
testRule = RuleChain.outerRule(publisherRule).around(composeTestRule)
RuleChain.outerRule(composeTestRule)
}
val composeTestRule = createComposeRule()
/**
* Tests that the app title shows in the pane 1 top bar when in single portrait mode
@ -45,9 +34,9 @@ class TopAppBarTest {
// Check that app title appears in top bar
composeTestRule.onNode(
hasParent(
hasTestTag(composeTestRule.getString(R.string.top_bar))
hasTestTag(getString(R.string.top_bar))
)
).assertTextEquals(composeTestRule.getString(R.string.app_name))
).assertTextEquals(getString(R.string.app_name))
}
/**
@ -64,9 +53,9 @@ class TopAppBarTest {
// Check that app title appears in top bar
composeTestRule.onNode(
hasParent(
hasTestTag(composeTestRule.getString(R.string.top_bar))
hasTestTag(getString(R.string.top_bar))
)
).assertTextEquals(composeTestRule.getString(R.string.app_name))
).assertTextEquals(getString(R.string.app_name))
}
/**
@ -83,9 +72,9 @@ class TopAppBarTest {
// Check that app title appears in top bar
composeTestRule.onNode(
hasParent(
hasTestTag(composeTestRule.getString(R.string.top_bar))
hasTestTag(getString(R.string.top_bar))
)
).assertTextEquals(composeTestRule.getString(R.string.app_name))
).assertTextEquals(getString(R.string.app_name))
}
/**
@ -102,9 +91,9 @@ class TopAppBarTest {
// Check that app title appears in top bar
composeTestRule.onNode(
hasParent(
hasTestTag(composeTestRule.getString(R.string.top_bar))
hasTestTag(getString(R.string.top_bar))
)
).assertTextEquals(composeTestRule.getString(R.string.app_name))
).assertTextEquals(getString(R.string.app_name))
}
/**
@ -119,7 +108,7 @@ class TopAppBarTest {
}
// Check that top bar doesn't exist
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.top_bar))
composeTestRule.onNodeWithTag(getString(R.string.top_bar))
.assertDoesNotExist()
}
@ -135,7 +124,7 @@ class TopAppBarTest {
}
// Check that top bar doesn't exist
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.top_bar))
composeTestRule.onNodeWithTag(getString(R.string.top_bar))
.assertDoesNotExist()
}
@ -153,7 +142,7 @@ class TopAppBarTest {
// Check that app title appears blank in top bar
composeTestRule.onNode(
hasParent(
hasTestTag(composeTestRule.getString(R.string.top_bar))
hasTestTag(getString(R.string.top_bar))
)
).assertTextEquals("")
}
@ -170,7 +159,7 @@ class TopAppBarTest {
}
// Check that top bar doesn't exist
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.top_bar))
composeTestRule.onNodeWithTag(getString(R.string.top_bar))
.assertDoesNotExist()
}
}

Просмотреть файл

@ -4,8 +4,7 @@
~ Licensed under the MIT License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.device.display.samples.companionpane">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

Просмотреть файл

@ -63,10 +63,10 @@ fun Pane1(windowMode: WindowMode, sliderState: SliderState = rememberSliderState
topBar = { CompanionPaneTopBar(stringResource(R.string.app_name)) }
) {
when (windowMode) {
WindowMode.SINGLE_PORTRAIT -> PortraitLayout()
WindowMode.SINGLE_LANDSCAPE -> LandscapeLayout(sliderState)
WindowMode.DUAL_PORTRAIT -> DualPortraitPane1()
WindowMode.DUAL_LANDSCAPE -> DualLandscapePane1()
WindowMode.SINGLE_PORTRAIT -> PortraitLayout(it)
WindowMode.SINGLE_LANDSCAPE -> LandscapeLayout(sliderState, it)
WindowMode.DUAL_PORTRAIT -> DualPortraitPane1(it)
WindowMode.DUAL_LANDSCAPE -> DualLandscapePane1(it)
}
}
}
@ -78,7 +78,7 @@ fun Pane2(windowMode: WindowMode, sliderState: SliderState = rememberSliderState
Scaffold(
topBar = { CompanionPaneTopBar() }
) {
DualPortraitPane2(sliderState)
DualPortraitPane2(sliderState, it)
}
}
WindowMode.DUAL_LANDSCAPE -> DualLandscapePane2(sliderState)

Просмотреть файл

@ -9,6 +9,7 @@ import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -39,10 +40,11 @@ import com.microsoft.device.display.samples.companionpane.ui.components.Vignette
private val shortSlideWidth = 200.dp
@Composable
fun DualLandscapePane1() {
fun DualLandscapePane1(paddingValues: PaddingValues) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(bottom = 20.dp)
.clipToBounds()
.testTag(stringResource(R.string.dual_land_pane1)),
@ -53,11 +55,12 @@ fun DualLandscapePane1() {
}
@Composable
fun DualLandscapePane2(sliderState: SliderState) {
fun DualLandscapePane2(sliderState: SliderState, paddingValues: PaddingValues = PaddingValues()) {
Surface {
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(start = 5.dp, end = 5.dp, bottom = 5.dp)
.testTag(stringResource(R.string.dual_land_pane2)),
verticalArrangement = Arrangement.SpaceEvenly,
@ -67,6 +70,7 @@ fun DualLandscapePane2(sliderState: SliderState) {
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier
.fillMaxWidth()
.padding(paddingValues)
.horizontalScroll(rememberScrollState())
) {
Column(verticalArrangement = Arrangement.SpaceEvenly) {
@ -106,10 +110,11 @@ fun DualLandscapePane2(sliderState: SliderState) {
}
@Composable
fun LandscapeLayout(sliderState: SliderState) {
fun LandscapeLayout(sliderState: SliderState, paddingValues: PaddingValues) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(top = 20.dp, start = 20.dp, end = 10.dp, bottom = 10.dp)
.verticalScroll(rememberScrollState())
.testTag(stringResource(R.string.single_land)),

Просмотреть файл

@ -7,6 +7,7 @@ package com.microsoft.device.display.samples.companionpane.ui.view
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
@ -33,10 +34,11 @@ import com.microsoft.device.display.samples.companionpane.ui.components.Vignette
private val longSliderWidth = 350.dp
@Composable
fun DualPortraitPane1() {
fun DualPortraitPane1(paddingValues: PaddingValues) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.testTag(stringResource(R.string.dual_port_pane1)),
verticalArrangement = Arrangement.SpaceEvenly
) {
@ -46,10 +48,11 @@ fun DualPortraitPane1() {
}
@Composable
fun DualPortraitPane2(sliderState: SliderState) {
fun DualPortraitPane2(sliderState: SliderState, paddingValues: PaddingValues) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.testTag(stringResource(R.string.dual_port_pane2)),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(80.dp, Alignment.CenterVertically)
@ -73,10 +76,11 @@ fun DualPortraitPane2(sliderState: SliderState) {
}
@Composable
fun PortraitLayout() {
fun PortraitLayout(paddingValues: PaddingValues) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.testTag(stringResource(R.string.single_port)),
verticalArrangement = Arrangement.spacedBy(15.dp),
horizontalAlignment = Alignment.CenterHorizontally

Просмотреть файл

@ -36,6 +36,7 @@ android {
excludes += ['META-INF/licenses/**', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
}
}
namespace 'com.microsoft.device.display.samples.composegallery'
}
dependencies {

Просмотреть файл

@ -16,32 +16,26 @@ import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToIndex
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.microsoft.device.display.samples.composegallery.models.DataProvider
import com.microsoft.device.display.samples.composegallery.ui.ComposeGalleryTheme
import com.microsoft.device.display.samples.composegallery.ui.view.ComposeGalleryApp
import com.microsoft.device.dualscreen.testing.compose.foldableRuleChain
import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.testing.compose.simulateVerticalFoldingFeature
import com.microsoft.device.dualscreen.testing.filters.MockFoldingFeature
import com.microsoft.device.dualscreen.testing.rules.FoldableTestRule
import com.microsoft.device.dualscreen.testing.runner.FoldableJUnit4ClassRunner
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(FoldableJUnit4ClassRunner::class)
class PaneSynchronizationTest {
private val composeTestRule = createAndroidComposeRule<MainActivity>()
private val publisherRule = WindowLayoutInfoPublisherRule()
private val foldableTestRule = FoldableTestRule()
@get: Rule
val testRule: TestRule
init {
testRule = RuleChain.outerRule(publisherRule).around(composeTestRule)
RuleChain.outerRule(composeTestRule)
}
@get:Rule
val testRule: TestRule = foldableRuleChain(composeTestRule, foldableTestRule)
/**
* Test that clicking an item in the list pane updates the image shown in the detail pane
@ -50,12 +44,6 @@ class PaneSynchronizationTest {
@Test
@MockFoldingFeature(orientation = MockFoldingFeature.FoldingFeatureOrientation.VERTICAL)
fun app_verticalFold_testListItemClickUpdatesDetailPane() {
composeTestRule.setContent {
ComposeGalleryTheme {
ComposeGalleryApp()
}
}
// Scroll to end of list
val index = 7
composeTestRule.onNode(
@ -80,40 +68,6 @@ class PaneSynchronizationTest {
).assertIsDisplayed()
}
/**
* Test that a selection made when no fold is present (not a large screen device) is remembered when a vertical
* fold is introduced
*/
@Test
fun app_testSelectionPersistenceAfterVerticalFold() {
composeTestRule.setContent {
ComposeGalleryTheme {
ComposeGalleryApp()
}
}
// Click on third surface duo entry
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.duo3_content_des))
.performClick()
// Check that detail view of third surface duo is displayed
composeTestRule.onNodeWithText(composeTestRule.getString(R.string.duo3_id))
.assertIsDisplayed()
// REVISIT: return to list view so state isn't saved for other tests
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_list))
.performClick()
// Simulate a vertical foldFeature so two panes are visible
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
// Check that third surface duo image is still displayed
composeTestRule.onNode(
hasContentDescription(composeTestRule.getString(R.string.duo3_content_des))
and hasAnySibling(hasText(composeTestRule.getString(R.string.duo3_id)))
).assertIsDisplayed()
}
/**
* Test that one pane is continuously shown on a non-large screen device, even when a horizontal fold is
* introduced
@ -121,12 +75,6 @@ class PaneSynchronizationTest {
@Test
@MockFoldingFeature(orientation = MockFoldingFeature.FoldingFeatureOrientation.HORIZONTAL)
fun app_testOnePaneShowsWithHorizontalFold() {
composeTestRule.setContent {
ComposeGalleryTheme {
ComposeGalleryApp()
}
}
// Check that the list view is displayed
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.gallery_list))
.assertIsDisplayed()

Просмотреть файл

@ -9,7 +9,7 @@ import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasParent
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
@ -29,7 +29,7 @@ import org.junit.Test
*/
class TopAppBarTest {
@get: Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
val composeTestRule = createComposeRule()
private val models = DataProvider.imageModels
private val selectedImageIndex = 0
@ -48,7 +48,7 @@ class TopAppBarTest {
}
// isDualMode is true, icon should be hidden
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_detail))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_detail))
.assertDoesNotExist()
}
@ -64,7 +64,7 @@ class TopAppBarTest {
}
// isDualMode is false, icon should show
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_detail))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_detail))
.assertIsDisplayed()
}
@ -80,7 +80,7 @@ class TopAppBarTest {
}
// isDualMode is true, icon should be hidden
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_list))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_list))
.assertDoesNotExist()
}
@ -96,7 +96,7 @@ class TopAppBarTest {
}
// isDualMode is false, icon should show
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_list))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_list))
.assertIsDisplayed()
}
@ -112,8 +112,8 @@ class TopAppBarTest {
}
composeTestRule.onNode(
hasParent(hasTestTag(composeTestRule.getString(R.string.top_app_bar)))
and hasText(composeTestRule.getString(R.string.app_name))
hasParent(hasTestTag(getString(R.string.top_app_bar)))
and hasText(getString(R.string.app_name))
).assertIsDisplayed()
}
@ -129,8 +129,8 @@ class TopAppBarTest {
}
composeTestRule.onNode(
hasParent(hasTestTag(composeTestRule.getString(R.string.top_app_bar)))
and hasText(composeTestRule.getString(R.string.app_name))
hasParent(hasTestTag(getString(R.string.top_app_bar)))
and hasText(getString(R.string.app_name))
).assertIsDisplayed()
}
@ -146,8 +146,8 @@ class TopAppBarTest {
}
composeTestRule.onNode(
hasParent(hasTestTag(composeTestRule.getString(R.string.top_app_bar)))
and hasText(composeTestRule.getString(R.string.app_name))
hasParent(hasTestTag(getString(R.string.top_app_bar)))
and hasText(getString(R.string.app_name))
).assertIsDisplayed()
}
@ -163,7 +163,7 @@ class TopAppBarTest {
}
composeTestRule.onNode(
hasParent(hasTestTag(composeTestRule.getString(R.string.top_app_bar)))
hasParent(hasTestTag(getString(R.string.top_app_bar)))
and hasText("")
).assertExists()
}
@ -180,23 +180,23 @@ class TopAppBarTest {
}
// Check that list pane is currently displayed
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.gallery_list))
composeTestRule.onNodeWithTag(getString(R.string.gallery_list))
.assertIsDisplayed()
// Click on picture/detail icon
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_detail))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_detail))
.performClick()
// Check that list pane is no longer displayed
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.gallery_list))
composeTestRule.onNodeWithTag(getString(R.string.gallery_list))
.assertDoesNotExist()
// Click on list icon
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_list))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_list))
.performClick()
// Check that list pane is displayed again
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.gallery_list))
composeTestRule.onNodeWithTag(getString(R.string.gallery_list))
.assertIsDisplayed()
}
}

Просмотреть файл

@ -4,8 +4,7 @@
~ Licensed under the MIT License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.device.display.samples.composegallery">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

Просмотреть файл

@ -10,7 +10,9 @@ import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
@ -38,7 +40,7 @@ fun TwoPaneScope.DetailPane(models: List<ImageModel>, selectedIndex: Int) {
)
}
) {
GalleryItemDetail(models = models, selectedIndex = selectedIndex)
GalleryItemDetail(models = models, selectedIndex = selectedIndex, paddingValues = it)
}
}
@ -54,7 +56,7 @@ private fun TwoPaneScope.DetailActions() {
}
@Composable
private fun GalleryItemDetail(models: List<ImageModel>, selectedIndex: Int) {
private fun GalleryItemDetail(models: List<ImageModel>, selectedIndex: Int, paddingValues: PaddingValues) {
val selectedImageModel = models[selectedIndex]
Crossfade(
@ -62,7 +64,7 @@ private fun GalleryItemDetail(models: List<ImageModel>, selectedIndex: Int) {
animationSpec = tween(600)
) {
Column(
modifier = Modifier.fillMaxSize(),
modifier = Modifier.fillMaxSize().padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp, Alignment.CenterVertically)
) {

Просмотреть файл

@ -7,6 +7,7 @@ package com.microsoft.device.display.samples.composegallery.ui.view
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
@ -56,6 +57,7 @@ fun TwoPaneScope.ListPane(
models = models,
selectedImageIndex = selectedImageIndex,
updateImageIndex = updateImageIndex,
paddingValues = it
)
}
}
@ -76,10 +78,12 @@ private fun TwoPaneScope.GalleryList(
models: List<ImageModel>,
selectedImageIndex: Int,
updateImageIndex: (Int) -> Unit,
paddingValues: PaddingValues
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.testTag(stringResource(R.string.gallery_list)),
) {
itemsIndexed(models) { index, item ->

Просмотреть файл

@ -43,6 +43,7 @@ android {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
namespace 'com.microsoft.device.display.samples.diary'
}
dependencies {

Просмотреть файл

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.microsoft.device.display.samples.diary">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Просмотреть файл

@ -42,31 +42,10 @@ fun TwoPaneScope.CalendarPage(
updateDate: (LocalDate) -> Unit,
updateContent: () -> Unit
) {
val twoPaneScope = this
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = stringResource(id = R.string.app_name))
},
backgroundColor = MaterialTheme.colors.primaryVariant,
actions = {
if (twoPaneScope.isSinglePane) {
IconButton(
onClick = { twoPaneScope.navigateToPane2() }
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = stringResource(R.string.edit_diary)
)
}
}
}
)
}
) {
Column {
topBar = { CalendarTopBar() }
) { paddingValues ->
Column(modifier = Modifier.padding(paddingValues)) {
AndroidView(
{
CalendarView(it)
@ -87,14 +66,12 @@ fun TwoPaneScope.CalendarPage(
}
)
Spacer(
modifier = Modifier
.background(color = Color.Gray)
.height(1.dp)
.fillMaxWidth()
)
Text(
modifier = Modifier
.fillMaxWidth()
@ -110,10 +87,9 @@ fun TwoPaneScope.CalendarPage(
)
)
)
Text(
modifier = Modifier.padding(10.dp),
text = content,
Modifier.padding(10.dp),
style = TextStyle(
fontSize = 18.sp,
textAlign = TextAlign.Left,
@ -122,3 +98,21 @@ fun TwoPaneScope.CalendarPage(
}
}
}
@Composable
fun TwoPaneScope.CalendarTopBar() {
TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
backgroundColor = MaterialTheme.colors.primaryVariant,
actions = {
if (this@CalendarTopBar.isSinglePane) {
IconButton(onClick = { this@CalendarTopBar.navigateToPane2() }) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = stringResource(R.string.edit_diary)
)
}
}
}
)
}

Просмотреть файл

@ -35,36 +35,13 @@ fun TwoPaneScope.DiaryPage(
selectedDate: LocalDate,
updateContent: () -> Unit
) {
val twoPaneScope = this
val context = LocalContext.current
val rootDataDir: File = context.applicationContext.dataDir
Scaffold(
topBar = {
TopAppBar(
title = {
if (isSinglePane) {
Text(text = stringResource(R.string.app_name))
}
},
backgroundColor = MaterialTheme.colors.primaryVariant,
actions = {
if (twoPaneScope.isSinglePane) {
IconButton(
onClick = { twoPaneScope.navigateToPane1() }
) {
Icon(
imageVector = Icons.Default.DateRange,
contentDescription = stringResource(R.string.date_picker)
)
}
}
}
)
}
topBar = { DiaryTopBar() }
) {
Column {
Column(modifier = Modifier.padding(it)) {
TextField(
value = text,
placeholder = { Text(stringResource(R.string.diary_placeholder)) },
@ -74,7 +51,6 @@ fun TwoPaneScope.DiaryPage(
.fillMaxSize()
.weight(0.9f)
)
Button(
modifier = Modifier
.width(150.dp)
@ -90,3 +66,24 @@ fun TwoPaneScope.DiaryPage(
}
}
}
@Composable
fun TwoPaneScope.DiaryTopBar() {
TopAppBar(
title = {
if (isSinglePane)
Text(text = stringResource(R.string.app_name))
},
backgroundColor = MaterialTheme.colors.primaryVariant,
actions = {
if (this@DiaryTopBar.isSinglePane) {
IconButton(onClick = { this@DiaryTopBar.navigateToPane1() }) {
Icon(
imageVector = Icons.Default.DateRange,
contentDescription = stringResource(R.string.date_picker)
)
}
}
}
)
}

Просмотреть файл

@ -31,6 +31,7 @@ android {
excludes += ['META-INF/licenses/**', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
}
}
namespace 'com.microsoft.device.display.samples.draganddrop'
}
dependencies {

Просмотреть файл

@ -11,8 +11,6 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.microsoft.device.display.samples.draganddrop.ui.theme.DragAndDropSamplesTheme
import com.microsoft.device.display.samples.draganddrop.ui.view.DragAndDropApp
import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.testing.compose.simulateHorizontalFoldingFeature
import org.junit.Rule
@ -38,12 +36,6 @@ class DragAndDropTest {
*/
@Test
fun app_showsBothPanes_before_and_after_horizontalFold() {
composeTestRule.setContent {
DragAndDropSamplesTheme {
DragAndDropApp()
}
}
// Assert the drag pane is now shown
composeTestRule.onNodeWithTag(
composeTestRule.getString(R.string.drag_pane)

Просмотреть файл

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.device.display.samples.draganddrop">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

Просмотреть файл

@ -10,6 +10,7 @@ package com.microsoft.device.display.samples.draganddrop.ui.view
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
@ -37,19 +38,18 @@ import com.microsoft.device.dualscreen.draganddrop.MimeType
@Composable
fun DragPaneWithTopBar() {
Scaffold(
topBar = {
TopBarWithTitle()
}
topBar = { TopBarWithTitle() }
) {
DragPane()
DragPane(paddingValues = it)
}
}
@Composable
fun DragPane(modifier: Modifier = Modifier) {
fun DragPane(modifier: Modifier = Modifier, paddingValues: PaddingValues = PaddingValues()) {
Row(
modifier = modifier
.fillMaxSize()
.padding(paddingValues)
.background(lightGray)
.testTag(stringResource(R.string.drag_pane))
) {

Просмотреть файл

@ -11,6 +11,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
@ -48,14 +49,10 @@ fun DropPaneWithTopBar(
updateDragImage: (Painter?) -> Unit
) {
Scaffold(
topBar = {
TopAppBar(backgroundColor = colors.primary) {}
},
floatingActionButton = {
ResetFloatingActionButton(updateDragText, updateDragImage)
}
topBar = { TopAppBar(backgroundColor = colors.primary) {} },
floatingActionButton = { ResetFloatingActionButton(updateDragText, updateDragImage) }
) {
DropPane(dragText, updateDragText, dragImage, updateDragImage)
DropPane(dragText, updateDragText, dragImage, updateDragImage, paddingValues = it)
}
}
@ -65,7 +62,8 @@ fun DropPane(
updateDragText: (String?) -> Unit,
dragImage: Painter?,
updateDragImage: (Painter?) -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues()
) {
var isDroppingText by remember { mutableStateOf(false) }
var isDroppingImage by remember { mutableStateOf(false) }
@ -73,6 +71,7 @@ fun DropPane(
DropContainer(
modifier = modifier
.padding(paddingValues)
.testTag(stringResource(R.string.drop_pane)),
onDrag = { inBounds, isDragging ->
if (!inBounds || !isDragging) {
@ -112,6 +111,7 @@ fun DropPaneContent(dragText: String?, isDroppingText: Boolean, dragImage: Paint
@Composable
fun RowScope.DropImageBox(dragImage: Painter?, isDroppingImage: Boolean) {
val boxColor = if (isDroppingImage) mediumGray else lightGray
Box(
modifier = Modifier
.weight(1f)
@ -148,6 +148,7 @@ fun DropImagePlaceholder() {
@Composable
fun RowScope.DropTextBox(text: String?, isDroppingText: Boolean) {
val boxColor = if (isDroppingText) mediumGray else lightGray
Box(
modifier = Modifier
.weight(1f)

Просмотреть файл

@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
@ -91,7 +92,7 @@ fun DragAndDropSinglePane(
ResetFloatingActionButton(updateDragText, updateDragImage)
}
) {
Column {
Column(modifier = Modifier.padding(it)) {
DragPane(modifier = Modifier.weight(1f))
Spacer(modifier = Modifier.height(10.dp))
DropPane(dragText, updateDragText, dragImage, updateDragImage, Modifier.weight(1f))

Просмотреть файл

@ -36,6 +36,7 @@ android {
excludes += ['META-INF/licenses/**', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
}
}
namespace 'com.microsoft.device.display.samples.dualview'
}
dependencies {
@ -63,4 +64,6 @@ dependencies {
androidTestImplementation testDependencies.composeJunit
androidTestImplementation testDependencies.uiAutomator
androidTestImplementation microsoftDependencies.composeTesting
debugImplementation testDependencies.composeUITestManifest
}

Просмотреть файл

@ -17,11 +17,8 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToIndex
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.microsoft.device.display.samples.dualview.models.restaurants
import com.microsoft.device.display.samples.dualview.ui.theme.DualViewAppTheme
import com.microsoft.device.display.samples.dualview.ui.view.DualViewApp
import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.testing.compose.simulateHorizontalFoldingFeature
import com.microsoft.device.dualscreen.windowstate.WindowState
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
@ -47,12 +44,6 @@ class MapImageTest {
@ExperimentalTestApi
@Test
fun app_horizontalFold_mapUpdatesAfterRestaurantClick() {
composeTestRule.setContent {
DualViewAppTheme {
DualViewApp(WindowState(hasFold = true, foldIsHorizontal = true, foldIsSeparating = true))
}
}
// Simulate horizontal foldFeature
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)

Просмотреть файл

@ -18,12 +18,11 @@ import androidx.compose.ui.test.assert
import androidx.compose.ui.test.hasAnyChild
import androidx.compose.ui.test.hasScrollToIndexAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToIndex
import androidx.compose.ui.text.TextStyle
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.microsoft.device.display.samples.dualview.models.restaurants
import com.microsoft.device.display.samples.dualview.ui.theme.DualViewAppTheme
import com.microsoft.device.display.samples.dualview.ui.theme.selectedBody1
@ -35,20 +34,10 @@ import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.twopanelayout.twopanelayout.TwoPaneScopeTest
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
class RestaurantListTest {
private val composeTestRule = createAndroidComposeRule<MainActivity>()
private val publisherRule = WindowLayoutInfoPublisherRule()
@get: Rule
val testRule: TestRule
init {
testRule = RuleChain.outerRule(publisherRule).around(composeTestRule)
RuleChain.outerRule(composeTestRule)
}
val composeTestRule = createComposeRule()
/**
* Tests that clicking on each restaurant list item updates the item's text style
@ -91,7 +80,7 @@ class RestaurantListTest {
// Assert that the details for the first restaurant are horizontally scrollable
composeTestRule.onNode(
matcher = hasAnyChild(hasText(composeTestRule.getString(R.string.pestle_rock)))
matcher = hasAnyChild(hasText(getString(R.string.pestle_rock)))
and SemanticsMatcher.keyIsDefined(SemanticsProperties.HorizontalScrollAxisRange),
useUnmergedTree = true
).assertExists()
@ -108,7 +97,7 @@ class RestaurantListTest {
// Assert that the details for the first restaurant are not horizontally scrollable
composeTestRule.onNode(
matcher = hasAnyChild(hasText(composeTestRule.getString(R.string.pestle_rock)))
matcher = hasAnyChild(hasText(getString(R.string.pestle_rock)))
and SemanticsMatcher.keyIsDefined(SemanticsProperties.HorizontalScrollAxisRange),
useUnmergedTree = true
).assertDoesNotExist()
@ -127,7 +116,7 @@ class RestaurantListTest {
composeTestRule.onNode(hasScrollToIndexAction()).performScrollToIndex(index)
// Get semantics node for current restaurant
val currentRestaurantTitle = composeTestRule.getString(restaurants[index].title)
val currentRestaurantTitle = getString(restaurants[index].title)
val currentRestaurant = composeTestRule.onNodeWithContentDescription(currentRestaurantTitle)
// Check that the unselected text style is being used

Просмотреть файл

@ -9,7 +9,7 @@ import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasParent
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
@ -25,7 +25,7 @@ import org.junit.Test
class TopAppBarTest {
@get: Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
val composeTestRule = createComposeRule()
/**
* Tests that the map icon shows in the restaurant top bar when single screen
@ -38,7 +38,7 @@ class TopAppBarTest {
}
}
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_map))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_map))
.assertIsDisplayed()
}
@ -53,7 +53,7 @@ class TopAppBarTest {
}
}
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_map))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_map))
.assertDoesNotExist()
}
@ -68,7 +68,7 @@ class TopAppBarTest {
}
}
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_rest))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_rest))
.assertIsDisplayed()
}
@ -83,7 +83,7 @@ class TopAppBarTest {
}
}
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_rest))
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_rest))
.assertDoesNotExist()
}
@ -99,8 +99,8 @@ class TopAppBarTest {
}
composeTestRule.onNode(
hasParent(hasTestTag(composeTestRule.getString(R.string.restaurant_top_bar)))
and hasText(composeTestRule.getString(R.string.app_name))
hasParent(hasTestTag(getString(R.string.restaurant_top_bar)))
and hasText(getString(R.string.app_name))
).assertExists()
}
@ -116,8 +116,8 @@ class TopAppBarTest {
}
composeTestRule.onNode(
hasParent(hasTestTag(composeTestRule.getString(R.string.restaurant_top_bar)))
and hasText(composeTestRule.getString(R.string.app_name))
hasParent(hasTestTag(getString(R.string.restaurant_top_bar)))
and hasText(getString(R.string.app_name))
).assertExists()
}
@ -133,8 +133,8 @@ class TopAppBarTest {
}
composeTestRule.onNode(
hasParent(hasTestTag(composeTestRule.getString(R.string.map_top_bar)))
and hasText(composeTestRule.getString(R.string.app_name))
hasParent(hasTestTag(getString(R.string.map_top_bar)))
and hasText(getString(R.string.app_name))
).assertExists()
}
@ -150,7 +150,7 @@ class TopAppBarTest {
}
composeTestRule.onNode(
hasParent(hasTestTag(composeTestRule.getString(R.string.map_top_bar)))
hasParent(hasTestTag(getString(R.string.map_top_bar)))
and hasText("")
).assertExists()
}
@ -167,18 +167,18 @@ class TopAppBarTest {
}
// Assert restaurant view is shown first
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.restaurant_top_bar)).assertExists()
composeTestRule.onNodeWithTag(getString(R.string.restaurant_top_bar)).assertExists()
// Click map icon to switch views
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_map)).performClick()
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_map)).performClick()
// Assert map view is now shown
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.map_top_bar)).assertExists()
composeTestRule.onNodeWithTag(getString(R.string.map_top_bar)).assertExists()
// Click restaurant icon to switch views
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.switch_to_rest)).performClick()
composeTestRule.onNodeWithContentDescription(getString(R.string.switch_to_rest)).performClick()
// Assert restaurant view is shown again
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.restaurant_top_bar)).assertExists()
composeTestRule.onNodeWithTag(getString(R.string.restaurant_top_bar)).assertExists()
}
}

Просмотреть файл

@ -5,8 +5,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.microsoft.device.display.samples.dualview">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"

Просмотреть файл

@ -12,8 +12,10 @@ import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
@ -51,14 +53,12 @@ fun TwoPaneScope.MapViewWithTopBar(isDualLandscape: Boolean, selectedIndex: Int)
Scaffold(
topBar = { if (!isDualLandscape) MapTopBar() }
) {
MapView(selectedIndex)
MapView(selectedIndex, it)
}
}
@Composable
fun TwoPaneScope.MapTopBar() {
val twoPaneScope = this
TopAppBar(
modifier = Modifier.testTag(stringResource(R.string.map_top_bar)),
title = {
@ -72,8 +72,8 @@ fun TwoPaneScope.MapTopBar() {
)
},
actions = {
if (twoPaneScope.isSinglePane) {
IconButton(onClick = { twoPaneScope.navigateToPane1() }) {
if (this@MapTopBar.isSinglePane) {
IconButton(onClick = { this@MapTopBar.navigateToPane1() }) {
Icon(
painter = painterResource(R.drawable.ic_list),
contentDescription = stringResource(R.string.switch_to_rest),
@ -87,15 +87,18 @@ fun TwoPaneScope.MapTopBar() {
}
@Composable
fun MapView(selectedIndex: Int) {
fun MapView(selectedIndex: Int, paddingValues: PaddingValues) {
var selectedMapId = R.drawable.unselected_map
var selectedTitleId = R.string.map_description
if (selectedIndex > nonSelection) {
selectedMapId = restaurants[selectedIndex].mapImageResourceId
selectedTitleId = restaurants[selectedIndex].description
}
Box(
modifier = Modifier
.padding(paddingValues)
.clipToBounds()
.testTag(
stringResource(R.string.map_image)

Просмотреть файл

@ -69,17 +69,15 @@ fun TwoPaneScope.RestaurantViewWithTopBar(
Scaffold(
topBar = { RestaurantTopBar() }
) {
RestaurantView(viewWidth, selectedIndex, updateSelectedIndex)
RestaurantView(viewWidth, selectedIndex, updateSelectedIndex, it)
}
}
@Composable
fun TwoPaneScope.RestaurantTopBar() {
val twoPaneScope = this
TopAppBar(
modifier = Modifier.testTag(stringResource(R.string.restaurant_top_bar)),
actions = { if (twoPaneScope.isSinglePane) twoPaneScope.RestaurantActionButton() },
actions = { if (this@RestaurantTopBar.isSinglePane) this@RestaurantTopBar.RestaurantActionButton() },
title = {
Text(
text = stringResource(R.string.app_name),
@ -106,18 +104,23 @@ private fun TwoPaneScope.RestaurantActionButton() {
}
@Composable
fun TwoPaneScope.RestaurantView(viewWidth: Int, selectedIndex: Int, updateSelectedIndex: (Int) -> Unit) {
val twoPaneScope = this
fun TwoPaneScope.RestaurantView(
viewWidth: Int,
selectedIndex: Int,
updateSelectedIndex: (Int) -> Unit,
paddingValues: PaddingValues
) {
Column(
modifier = Modifier.padding(top = outlinePadding.dp, start = outlinePadding.dp, end = outlinePadding.dp),
modifier = Modifier
.padding(paddingValues)
.padding(top = outlinePadding.dp, start = outlinePadding.dp, end = outlinePadding.dp),
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
Text(
text = stringResource(R.string.list_title),
style = typography.subtitle1
)
twoPaneScope.RestaurantListView(viewWidth, selectedIndex, updateSelectedIndex)
this@RestaurantView.RestaurantListView(viewWidth, selectedIndex, updateSelectedIndex)
}
}

Просмотреть файл

@ -46,6 +46,7 @@ android {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
namespace 'com.microsoft.device.display.samples.dyadd'
}
dependencies {

Просмотреть файл

@ -5,8 +5,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.microsoft.device.display.samples.dyadd">
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"

Просмотреть файл

@ -19,14 +19,12 @@ import com.microsoft.device.display.samples.dyadd.ui.pages.calculatorModel
@Composable
fun Answer(modifier: Modifier = Modifier) {
val eq: String = if (!calculatorModel.isOnX) " " + equationStringEquivalent(calculatorModel.currentEquation) + " " else ""
val eq: String =
if (!calculatorModel.isOnX) " " + equationStringEquivalent(calculatorModel.currentEquation) + " " else ""
val y: String = if (!calculatorModel.isOnX) calculatorModel.y else ""
LazyColumn(
modifier = modifier
.padding(
start = 20.dp,
end = 20.dp,
)
modifier = modifier.padding(start = 20.dp, end = 20.dp)
) {
item {
Text(

Просмотреть файл

@ -5,10 +5,9 @@
package com.microsoft.device.display.samples.dyadd.ui.pages.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -16,19 +15,12 @@ import androidx.compose.ui.unit.dp
import com.microsoft.device.display.samples.dyadd.models.Action
import com.microsoft.device.display.samples.dyadd.models.Equation
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun AdvancedEquationGrid(columnCount: Int, modifier: Modifier = Modifier) {
LazyVerticalGrid(
cells = GridCells.Fixed(columnCount),
// content padding
modifier = modifier,
contentPadding = PaddingValues(
start = 12.dp,
top = 16.dp,
end = 12.dp,
bottom = 16.dp
),
columns = GridCells.Fixed(columnCount),
contentPadding = PaddingValues(start = 12.dp, top = 16.dp, end = 12.dp, bottom = 16.dp),
content = {
item { ActionButton(ac = Action.DEG, color = MaterialTheme.colors.primaryVariant) }
item { EquationButton(eq = Equation.POW, color = MaterialTheme.colors.primary) }
@ -49,20 +41,14 @@ fun AdvancedEquationGrid(columnCount: Int, modifier: Modifier = Modifier) {
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun NumericGrid(modifier: Modifier = Modifier) {
val list = (0..10).map { it.toString() }
LazyVerticalGrid(
cells = GridCells.Fixed(3),
columns = GridCells.Fixed(3),
modifier = modifier,
// content padding
contentPadding = PaddingValues(
start = 12.dp,
top = 16.dp,
end = 6.dp,
bottom = 16.dp
),
contentPadding = PaddingValues(start = 12.dp, top = 16.dp, end = 6.dp, bottom = 16.dp),
content = {
items(list.size) { index ->
if (index == list.size - 1) {
@ -75,18 +61,11 @@ fun NumericGrid(modifier: Modifier = Modifier) {
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BasicCalculationGrid() {
LazyVerticalGrid(
cells = GridCells.Fixed(2),
// content padding
contentPadding = PaddingValues(
start = 6.dp,
top = 16.dp,
end = 12.dp,
bottom = 16.dp
),
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(start = 6.dp, top = 16.dp, end = 12.dp, bottom = 16.dp),
content = {
item { EquationButton(eq = Equation.DIV, color = MaterialTheme.colors.secondary) }
item { ActionButton(ac = Action.CLR, color = MaterialTheme.colors.secondaryVariant) }

Просмотреть файл

@ -68,10 +68,10 @@ fun ButtonLayout(
onClick: () -> Unit
) {
Button(
onClick = { onClick() },
modifier = Modifier
.padding(5.dp)
.size(90.dp),
onClick = { onClick() },
colors = ButtonDefaults.buttonColors(backgroundColor = color),
shape = MaterialTheme.shapes.large
) {
@ -88,6 +88,7 @@ fun ButtonText(content: String) {
val button = MaterialTheme.typography.button
var textStyle by remember { mutableStateOf(button) }
var readyToDraw by remember { mutableStateOf(false) }
Text(
text = content,
style = textStyle,

Просмотреть файл

@ -29,14 +29,9 @@ import kotlinx.coroutines.launch
fun History(addRecordToTop: Boolean, modifier: Modifier = Modifier) {
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
LazyColumn(
modifier = modifier
.padding(
start = 20.dp,
end = 20.dp,
bottom = 5.dp,
top = 15.dp
),
modifier = modifier.padding(start = 20.dp, end = 20.dp, bottom = 5.dp, top = 15.dp),
state = listState,
) {
items(historyModel.records.size) { index ->

Просмотреть файл

@ -29,10 +29,14 @@ android {
kotlinCompilerExtensionVersion composeVersion
}
packagingOptions {
exclude "META-INF/licenses/**"
exclude "META-INF/AL2.0"
exclude "META-INF/LGPL2.1"
jniLibs {
excludes += ['META-INF/licenses/**']
}
resources {
excludes += ['META-INF/licenses/**', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
}
}
namespace 'com.microsoft.device.display.samples.extendedcanvas'
}
dependencies {

Просмотреть файл

@ -15,7 +15,6 @@ import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import com.microsoft.device.display.samples.extendedcanvas.ui.ExtendedCanvasAppsTheme
import com.microsoft.device.dualscreen.testing.compose.getString
import org.junit.Rule
import org.junit.Test
@ -29,12 +28,6 @@ class ExtendedCanvasTest {
*/
@Test
fun topBar_shows() {
composeTestRule.setContent {
ExtendedCanvasAppsTheme {
ExtendedCanvasApp()
}
}
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.top_bar))
.assertIsDisplayed()
}
@ -44,12 +37,6 @@ class ExtendedCanvasTest {
*/
@Test
fun mapView_testImageDrags() {
composeTestRule.setContent {
ExtendedCanvasAppsTheme {
ExtendedCanvasApp()
}
}
// Get node for the map image
val mapImageNode =
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.map_image))

Просмотреть файл

@ -4,8 +4,7 @@
~ Licensed under the MIT License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.device.display.samples.extendedcanvas">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

Просмотреть файл

@ -9,8 +9,10 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
@ -43,9 +45,10 @@ var SemanticsPropertyReceiver.imageOffset by ImageOffsetKey
@Composable
fun ExtendedCanvasApp() {
Scaffold(
topBar = { TitleTopBar() },
content = { ScaleImage() }
)
topBar = { TitleTopBar() }
) {
ScaleImage(it)
}
}
@Composable
@ -66,7 +69,7 @@ fun TitleTopBar() {
}
@Composable
fun ScaleImage() {
fun ScaleImage(paddingValues: PaddingValues) {
val minScale = 0.8f
val maxScale = 6f
var scale by remember { mutableStateOf(1.5f) }
@ -75,10 +78,9 @@ fun ScaleImage() {
scale *= zoomChange
offset += offsetChange
}
Image(
painter = painterResource(id = R.drawable.mock_map),
contentDescription = stringResource(R.string.map_image),
contentScale = ContentScale.Crop,
modifier = Modifier
.semantics {
imageOffset = offset
@ -96,6 +98,10 @@ fun ScaleImage() {
}
)
}
.fillMaxSize(),
.fillMaxSize()
.padding(paddingValues),
painter = painterResource(id = R.drawable.mock_map),
contentDescription = stringResource(R.string.map_image),
contentScale = ContentScale.Crop,
)
}

Просмотреть файл

@ -36,6 +36,7 @@ android {
excludes += ['META-INF/licenses/**', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
}
}
namespace 'com.microsoft.device.display.samples.listdetail'
}
dependencies {
@ -63,4 +64,6 @@ dependencies {
androidTestImplementation testDependencies.composeJunit
androidTestImplementation testDependencies.uiAutomator
androidTestImplementation microsoftDependencies.composeTesting
debugImplementation testDependencies.composeUITestManifest
}

Просмотреть файл

@ -6,8 +6,6 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.microsoft.device.display.samples.listdetail.models.images
import com.microsoft.device.display.samples.listdetail.ui.theme.ListDetailComposeSampleTheme
import com.microsoft.device.display.samples.listdetail.ui.view.ListDetailApp
import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.testing.compose.simulateHorizontalFoldingFeature
import com.microsoft.device.dualscreen.testing.compose.simulateVerticalFoldingFeature
@ -34,12 +32,6 @@ class ListDetailTest {
*/
@Test
fun app_verticalFold_showDetailAfterListClicks() {
composeTestRule.setContent {
ListDetailComposeSampleTheme {
ListDetailApp()
}
}
// Simulate vertical foldFeature
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
@ -62,12 +54,6 @@ class ListDetailTest {
*/
@Test
fun app_horizontalFold_showsList() {
composeTestRule.setContent {
ListDetailComposeSampleTheme {
ListDetailApp()
}
}
// Simulate horizontal foldFeature
publisherRule.simulateHorizontalFoldingFeature(composeTestRule)

Просмотреть файл

@ -6,7 +6,7 @@
package com.microsoft.device.display.samples.listdetail
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
@ -21,7 +21,7 @@ import org.junit.Test
class TopAppBarTest {
@get: Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
val composeTestRule = createComposeRule()
/**
* Tests that app title shows in the top bar of the list view
@ -34,9 +34,7 @@ class TopAppBarTest {
}
}
composeTestRule.onNode(
hasText(composeTestRule.getString(R.string.app_name))
).assertExists()
composeTestRule.onNode(hasText(getString(R.string.app_name))).assertExists()
}
/**
@ -51,9 +49,7 @@ class TopAppBarTest {
}
// Assert the back button is not shown
composeTestRule.onNodeWithContentDescription(
composeTestRule.getString(R.string.back_to_list)
).assertDoesNotExist()
composeTestRule.onNodeWithContentDescription(getString(R.string.back_to_list)).assertDoesNotExist()
}
/**
@ -68,9 +64,7 @@ class TopAppBarTest {
}
// Assert the back button is shown
composeTestRule.onNodeWithContentDescription(
composeTestRule.getString(R.string.back_to_list)
).assertExists()
composeTestRule.onNodeWithContentDescription(getString(R.string.back_to_list)).assertExists()
}
/**
@ -86,44 +80,29 @@ class TopAppBarTest {
}
// Assert the list view is shown first
composeTestRule.onNodeWithTag(
composeTestRule.getString(R.string.list_view)
).assertExists()
composeTestRule.onNodeWithTag(getString(R.string.list_view)).assertExists()
// Assert the back button is not shown yet
composeTestRule.onNodeWithContentDescription(
composeTestRule.getString(R.string.back_to_list)
).assertDoesNotExist()
composeTestRule.onNodeWithContentDescription(getString(R.string.back_to_list)).assertDoesNotExist()
// Click the first image from the list
val index = 0
composeTestRule.onNodeWithContentDescription(
index.toString()
).performClick()
composeTestRule.onNodeWithContentDescription(index.toString()).performClick()
// Assert the correct detail image is shown
composeTestRule.onNodeWithContentDescription(
composeTestRule.getString(R.string.image_tag) + index.toString()
).assertExists()
composeTestRule.onNodeWithContentDescription(getString(R.string.image_tag) + index.toString())
.assertExists()
// Assert the back button is now shown
composeTestRule.onNodeWithContentDescription(
composeTestRule.getString(R.string.back_to_list)
).assertExists()
composeTestRule.onNodeWithContentDescription(getString(R.string.back_to_list)).assertExists()
// Click the back button to go back to the list view
composeTestRule.onNodeWithContentDescription(
composeTestRule.getString(R.string.back_to_list)
).performClick()
composeTestRule.onNodeWithContentDescription(getString(R.string.back_to_list)).performClick()
// Assert the detail view is not shown
composeTestRule.onNodeWithTag(
composeTestRule.getString(R.string.detail_view)
).assertDoesNotExist()
composeTestRule.onNodeWithTag(getString(R.string.detail_view)).assertDoesNotExist()
// Assert the list view is now shown
composeTestRule.onNodeWithTag(
composeTestRule.getString(R.string.list_view)
).assertExists()
composeTestRule.onNodeWithTag(getString(R.string.list_view)).assertExists()
}
}

Просмотреть файл

@ -4,8 +4,7 @@
~ Licensed under the MIT License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.device.display.samples.listdetail">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

Просмотреть файл

@ -8,6 +8,7 @@ package com.microsoft.device.display.samples.listdetail.ui.view
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -48,10 +49,9 @@ fun TwoPaneScope.DetailViewWithTopBar(selectedIndex: Int) {
topBar = {
DetailViewTopBar()
},
content = {
DetailView(selectedIndex)
}
)
) {
DetailView(selectedIndex, it)
}
}
@Composable
@ -82,10 +82,10 @@ fun TwoPaneScope.DetailViewTopBarButton() {
}
@Composable
fun DetailView(selectedIndex: Int) {
fun DetailView(selectedIndex: Int, paddingValues: PaddingValues) {
val selectedImageId = images[selectedIndex]
Column {
Column(modifier = Modifier.padding(paddingValues)) {
Box(
modifier = Modifier
.testTag(stringResource(R.string.detail_view))
@ -116,9 +116,7 @@ fun DetailView(selectedIndex: Int) {
@Composable
fun ImageInfoTile(modifier: Modifier) {
Row(
modifier = modifier.horizontalScroll(
rememberScrollState()
),
modifier = modifier.horizontalScroll(rememberScrollState()),
verticalAlignment = Alignment.CenterVertically
) {
Box(

Просмотреть файл

@ -8,6 +8,7 @@ package com.microsoft.device.display.samples.listdetail.ui.view
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@ -43,8 +44,9 @@ private val horizontalPadding = 15.dp
fun TwoPaneScope.ListViewWithTopBar(selectedIndex: Int, updateSelectedIndex: (Int) -> Unit) {
Scaffold(
topBar = { ListViewTopBar() },
content = { ListView(selectedIndex, updateSelectedIndex) }
)
) {
ListView(selectedIndex, updateSelectedIndex, it)
}
}
@Composable
@ -64,19 +66,14 @@ fun ListViewTopBar() {
}
@Composable
fun TwoPaneScope.ListView(selectedIndex: Int, updateSelectedIndex: (Int) -> Unit) {
fun TwoPaneScope.ListView(selectedIndex: Int, updateSelectedIndex: (Int) -> Unit, paddingValues: PaddingValues) {
val subImageList = images.chunked(3)
val twoPaneScope = this
Box(
modifier = Modifier
.fillMaxSize()
.padding(
top = verticalPadding,
bottom = verticalPadding,
start = horizontalPadding,
end = horizontalPadding
)
.padding(paddingValues)
.padding(horizontal = horizontalPadding, vertical = verticalPadding)
.testTag(stringResource(R.string.list_view))
) {
LazyColumn(
@ -103,7 +100,7 @@ fun TwoPaneScope.ListView(selectedIndex: Int, updateSelectedIndex: (Int) -> Unit
selected = (listIndex == selectedIndex),
onClick = {
updateSelectedIndex(listIndex)
twoPaneScope.navigateToPane2()
this@ListView.navigateToPane2()
}
)
)

Просмотреть файл

@ -33,6 +33,7 @@ android {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
namespace 'com.microsoft.device.display.samples.navigationrail'
}
dependencies {
@ -59,4 +60,6 @@ dependencies {
androidTestImplementation testDependencies.composeJunit
androidTestImplementation testDependencies.uiAutomator
androidTestImplementation microsoftDependencies.composeTesting
debugImplementation testDependencies.composeUITestManifest
}

Просмотреть файл

@ -19,7 +19,7 @@ import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasAnyChild
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
@ -51,7 +51,7 @@ import org.junit.Test
@ExperimentalAnimationApi
class DetailTest {
@get: Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
val composeTestRule = createComposeRule()
/**
* Tests that the back button appears in pane 2 in single screen mode
@ -63,7 +63,7 @@ class DetailTest {
}
// Assert that back button is visible
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.back)).assertIsDisplayed()
composeTestRule.onNodeWithContentDescription(getString(R.string.back)).assertIsDisplayed()
}
/**
@ -76,7 +76,7 @@ class DetailTest {
}
// Assert that back button is visible
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.back)).assertIsDisplayed()
composeTestRule.onNodeWithContentDescription(getString(R.string.back)).assertIsDisplayed()
}
/**
@ -89,7 +89,7 @@ class DetailTest {
}
// Assert that back button does not exist
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.back)).assertDoesNotExist()
composeTestRule.onNodeWithContentDescription(getString(R.string.back)).assertDoesNotExist()
}
/**
@ -105,24 +105,24 @@ class DetailTest {
}
// Assert "plants" title is visible
composeTestRule.onNodeWithText("plants").assertIsDisplayed()
composeTestRule.onNodeWithText(getString(R.string.plants).lowercase()).assertIsDisplayed()
// Click on first plant item
val firstEntryDescription =
composeTestRule.getString(R.string.image_description, plantList[0].name, plantList[0].id)
getString(R.string.image_description, plantList[0].name, plantList[0].id)
composeTestRule.onNodeWithContentDescription(firstEntryDescription).performClick()
// Assert that back button is visible
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.back)).assertIsDisplayed()
composeTestRule.onNodeWithContentDescription(getString(R.string.back)).assertIsDisplayed()
// Assert that "plants" title is no longer visible
composeTestRule.onNodeWithText("plants").assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.plants).lowercase()).assertDoesNotExist()
// Click back button
composeTestRule.onNodeWithContentDescription(composeTestRule.getString(R.string.back)).performClick()
composeTestRule.onNodeWithContentDescription(getString(R.string.back)).performClick()
// Assert that "plants" title is visible again
composeTestRule.onNodeWithText("plants").assertIsDisplayed()
composeTestRule.onNodeWithText(getString(R.string.plants).lowercase()).assertIsDisplayed()
}
/**
@ -135,26 +135,26 @@ class DetailTest {
}
// Assert drawer is collapsed on start
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.content_drawer))
composeTestRule.onNodeWithTag(getString(R.string.content_drawer))
.assertDrawerStateEquals(DrawerState.Collapsed)
// Swipe content drawer up
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.content_drawer)).performTouchInput {
composeTestRule.onNodeWithTag(getString(R.string.content_drawer)).performTouchInput {
val start = this.topCenter
val end = Offset(start.x, start.y - 200)
swipe(start, end, 200)
}
// Assert drawer is now expanded
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.content_drawer))
composeTestRule.onNodeWithTag(getString(R.string.content_drawer))
.assertDrawerStateEquals(DrawerState.Expanded)
// Swipe content drawer back down
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.content_drawer))
composeTestRule.onNodeWithTag(getString(R.string.content_drawer))
.performTouchInput { swipeDown() }
// Assert drawer is collapsed again
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.content_drawer))
composeTestRule.onNodeWithTag(getString(R.string.content_drawer))
.assertDrawerStateEquals(DrawerState.Collapsed)
}

Просмотреть файл

@ -7,6 +7,7 @@ package com.microsoft.device.display.samples.navigationrail
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -15,7 +16,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.semantics.SemanticsProperties.VerticalScrollAxisRange
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performScrollTo
@ -38,7 +39,7 @@ import org.junit.Test
@ExperimentalAnimationApi
class GalleryTest {
@get: Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
val composeTestRule = createComposeRule()
/**
* Tests that the content of the gallery view is vertically scrollable
@ -52,15 +53,15 @@ class GalleryTest {
galleryList = DataProvider.plantList,
currentImageId = id,
onImageSelected = { newId -> id = newId },
horizontalPadding = 10.dp
horizontalPadding = 10.dp,
paddingValues = PaddingValues()
)
}
}
// Assert that last plant is not visible at the start
val lastEntry = DataProvider.plantList.last()
val lastEntryDescription =
composeTestRule.getString(R.string.image_description, lastEntry.name, lastEntry.id)
val lastEntryDescription = getString(R.string.image_description, lastEntry.name, lastEntry.id)
composeTestRule.onNodeWithContentDescription(lastEntryDescription).assertDoesNotExist()
// Assert that gallery has vertical scroll action, then scroll to the end of the gallery
@ -83,10 +84,10 @@ class GalleryTest {
}
// Assert "plants" title is visible in gallery view
composeTestRule.onNodeWithText("plants").assertIsDisplayed()
composeTestRule.onNodeWithText(getString(R.string.plants).lowercase()).assertIsDisplayed()
// Assert that placeholder view isn't shown on start up (only one pane)
val placeholderText = composeTestRule.activity.getString(R.string.placeholder_msg, "plants")
val placeholderText = getString(R.string.placeholder_msg, getString(R.string.plants).lowercase())
composeTestRule.onNodeWithText(placeholderText).assertDoesNotExist()
}
}

Просмотреть файл

@ -11,7 +11,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.ExperimentalUnitApi
@ -29,9 +29,8 @@ import org.junit.Test
@ExperimentalMaterialApi
@ExperimentalAnimationApi
class NavComponentTest {
@get: Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
val composeTestRule = createComposeRule()
/**
* Tests that clicking each icon in the bottom navigation bar switches the gallery destination
@ -73,8 +72,8 @@ class NavComponentTest {
}
// Assert that nav rail, not bottom nav, is displayed
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.nav_rail)).assertIsDisplayed()
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.bottom_nav)).assertDoesNotExist()
composeTestRule.onNodeWithTag(getString(R.string.nav_rail)).assertIsDisplayed()
composeTestRule.onNodeWithTag(getString(R.string.bottom_nav)).assertDoesNotExist()
}
/**
@ -89,8 +88,8 @@ class NavComponentTest {
}
// Assert that nav rail, not bottom nav, is displayed
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.nav_rail)).assertIsDisplayed()
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.bottom_nav)).assertDoesNotExist()
composeTestRule.onNodeWithTag(getString(R.string.nav_rail)).assertIsDisplayed()
composeTestRule.onNodeWithTag(getString(R.string.bottom_nav)).assertDoesNotExist()
}
/**
@ -105,8 +104,8 @@ class NavComponentTest {
}
// Assert that nav rail, not bottom nav, is displayed
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.nav_rail)).assertIsDisplayed()
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.bottom_nav)).assertDoesNotExist()
composeTestRule.onNodeWithTag(getString(R.string.nav_rail)).assertIsDisplayed()
composeTestRule.onNodeWithTag(getString(R.string.bottom_nav)).assertDoesNotExist()
}
/**
@ -121,8 +120,8 @@ class NavComponentTest {
}
// Assert that bottom nav, not nav rail, is displayed
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.bottom_nav)).assertIsDisplayed()
composeTestRule.onNodeWithTag(composeTestRule.getString(R.string.nav_rail)).assertDoesNotExist()
composeTestRule.onNodeWithTag(getString(R.string.bottom_nav)).assertIsDisplayed()
composeTestRule.onNodeWithTag(getString(R.string.nav_rail)).assertDoesNotExist()
}
/**
@ -132,7 +131,7 @@ class NavComponentTest {
private fun clickEachNavIcon() {
for (destination in GallerySections.values()) {
// Click on nav icon
composeTestRule.onNode(hasText(composeTestRule.getString(destination.title)) and hasClickAction())
composeTestRule.onNode(hasText(getString(destination.title)) and hasClickAction())
.performClick()
// Assert that new destination route is shown in the top bar

Просмотреть файл

@ -24,49 +24,36 @@ import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeUp
import androidx.compose.ui.unit.ExperimentalUnitApi
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
import com.microsoft.device.display.samples.navigationrail.models.DataProvider
import com.microsoft.device.display.samples.navigationrail.ui.theme.NavigationRailAppTheme
import com.microsoft.device.display.samples.navigationrail.ui.view.GallerySections
import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
import com.microsoft.device.dualscreen.testing.compose.foldableRuleChain
import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.testing.compose.simulateVerticalFoldingFeature
import com.microsoft.device.dualscreen.windowstate.WindowState
import com.microsoft.device.dualscreen.testing.filters.MockFoldingFeature
import com.microsoft.device.dualscreen.testing.rules.FoldableTestRule
import com.microsoft.device.dualscreen.testing.runner.FoldableJUnit4ClassRunner
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@ExperimentalFoundationApi
@ExperimentalUnitApi
@ExperimentalMaterialApi
@ExperimentalAnimationApi
@RunWith(FoldableJUnit4ClassRunner::class)
class PaneSynchronizationTest {
private val composeTestRule = createAndroidComposeRule<MainActivity>()
private val publisherRule = WindowLayoutInfoPublisherRule()
private val foldableTestRule = FoldableTestRule()
@get: Rule
val testRule: TestRule
init {
testRule = RuleChain.outerRule(publisherRule).around(composeTestRule)
RuleChain.outerRule(composeTestRule)
}
@get:Rule
val testRule: TestRule = foldableRuleChain(composeTestRule, foldableTestRule)
/**
* Tests that the correct placeholder views appear when a gallery is first opened and no items are selected
*/
@Test
@MockFoldingFeature(orientation = MockFoldingFeature.FoldingFeatureOrientation.VERTICAL)
fun app_verticalFold_testPlaceholderViewAppearsOnStart() {
composeTestRule.setContent {
NavigationRailAppTheme {
NavigationRailApp(WindowState(hasFold = true, foldIsSeparating = true))
}
}
// Simulate a vertical foldFeature
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
for (gallery in GallerySections.values()) {
// Click on gallery
composeTestRule.onNode(hasText(gallery.route, ignoreCase = true) and hasClickAction()).performClick()
@ -82,16 +69,8 @@ class PaneSynchronizationTest {
* for the clicked item when a vertical fold is present
*/
@Test
@MockFoldingFeature(orientation = MockFoldingFeature.FoldingFeatureOrientation.VERTICAL)
fun app_verticalFold_galleryClickUpdatesSelection() {
composeTestRule.setContent {
NavigationRailAppTheme {
NavigationRailApp(WindowState(hasFold = true, foldIsSeparating = true))
}
}
// Simulate a vertical foldFeature
publisherRule.simulateVerticalFoldingFeature(composeTestRule)
GallerySections.values().forEachIndexed { i, gallery ->
// Click on gallery
composeTestRule.onNode(hasText(gallery.route, ignoreCase = true) and hasClickAction())

Просмотреть файл

@ -4,8 +4,7 @@
~ Licensed under the MIT License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.device.display.samples.navigationrail">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

Просмотреть файл

@ -37,18 +37,16 @@ fun TwoPaneNavScope.GalleryNavRail(
updateImageId: (Int?) -> Unit,
updateRoute: (String) -> Unit,
) {
val twoPaneNavScope = this
NavigationRail(
modifier = Modifier.testTag(stringResource(id = R.string.nav_rail)),
backgroundColor = MaterialTheme.colors.primary,
) {
Spacer(Modifier.height(NAV_RAIL_TOP_SPACING))
val currentDestination =
if (twoPaneNavScope.isSinglePane)
if (this@GalleryNavRail.isSinglePane)
navController.currentBackStackEntryAsState().value?.destination?.route
else
twoPaneNavScope.currentPane1Destination
this@GalleryNavRail.currentPane1Destination
galleries.forEach { gallery ->
NavRailItemWithSelector(
icon = {
@ -57,7 +55,7 @@ fun TwoPaneNavScope.GalleryNavRail(
label = { NavItemLabel(stringResource(gallery.title)) },
selected = isNavItemSelected(currentDestination, gallery.route),
onClick = {
twoPaneNavScope.navItemOnClick(navController, gallery.route, updateImageId, updateRoute)
this@GalleryNavRail.navItemOnClick(navController, gallery.route, updateImageId, updateRoute)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.onPrimary
@ -74,17 +72,15 @@ fun TwoPaneNavScope.GalleryBottomNav(
updateImageId: (Int?) -> Unit,
updateRoute: (String) -> Unit,
) {
val twoPaneNavScope = this
BottomNavigation(
modifier = Modifier.testTag(stringResource(id = R.string.bottom_nav)),
backgroundColor = MaterialTheme.colors.primary,
) {
val currentDestination =
if (twoPaneNavScope.isSinglePane)
if (this@GalleryBottomNav.isSinglePane)
navController.currentBackStackEntryAsState().value?.destination?.route
else
twoPaneNavScope.currentPane1Destination
this@GalleryBottomNav.currentPane1Destination
galleries.forEach { gallery ->
BottomNavItemWithSelector(
icon = {
@ -93,7 +89,7 @@ fun TwoPaneNavScope.GalleryBottomNav(
label = { NavItemLabel(stringResource(gallery.title)) },
selected = isNavItemSelected(currentDestination, gallery.route),
onClick = {
twoPaneNavScope.navItemOnClick(navController, gallery.route, updateImageId, updateRoute)
this@GalleryBottomNav.navItemOnClick(navController, gallery.route, updateImageId, updateRoute)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.onPrimary

Просмотреть файл

@ -15,9 +15,8 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
@ -80,8 +79,6 @@ fun TwoPaneNavScope.GalleryViewWithTopBar(
activity?.finish()
}
val twoPaneNavScope = this
// Use navigation rail when dual screen (more space), otherwise use bottom navigation
Scaffold(
bottomBar = {
@ -91,15 +88,16 @@ fun TwoPaneNavScope.GalleryViewWithTopBar(
) { paddingValues ->
Row(Modifier.padding(paddingValues)) {
if (isDualScreen)
twoPaneNavScope.GalleryNavRail(navController, navDestinations, updateImageId, updateRoute)
this@GalleryViewWithTopBar.GalleryNavRail(navController, navDestinations, updateImageId, updateRoute)
Scaffold(
topBar = { GalleryTopBar(section.route, horizontalPadding) }
) {
) { paddingValues ->
GalleryView(
galleryList = section.list,
currentImageId = imageId,
onImageSelected = { id -> twoPaneNavScope.onImageSelected(id, updateImageId, navController) },
onImageSelected = { id -> this@GalleryViewWithTopBar.onImageSelected(id, updateImageId, navController) },
horizontalPadding = horizontalPadding,
paddingValues = paddingValues
)
}
}
@ -137,10 +135,12 @@ fun GalleryView(
galleryList: List<Image>,
currentImageId: Int?,
onImageSelected: (Int) -> Unit,
horizontalPadding: Dp
horizontalPadding: Dp,
paddingValues: PaddingValues
) {
LazyVerticalGrid(
cells = GridCells.Fixed(count = NUM_COLUMNS),
modifier = Modifier.padding(paddingValues),
columns = GridCells.Fixed(count = NUM_COLUMNS),
verticalArrangement = Arrangement.spacedBy(GALLERY_SPACING, Alignment.Top),
horizontalArrangement = Arrangement.spacedBy(GALLERY_SPACING, Alignment.CenterHorizontally),
contentPadding = PaddingValues(
@ -149,8 +149,10 @@ fun GalleryView(
bottom = GALLERY_SPACING
)
) {
items(galleryList) { item ->
GalleryItem(item, currentImageId, onImageSelected)
galleryList.forEach { item ->
item {
GalleryItem(item, currentImageId, onImageSelected)
}
}
}
}

Просмотреть файл

@ -8,7 +8,7 @@ products:
description: "Samples showing how to use Jetpack Compose to achieve dual-screen user interface patterns."
urlFragment: all
---
![build-test-check](https://github.com/microsoft/surface-duo-compose-samples/actions/workflows/build_test_check.yml/badge.svg) ![Compose Version](https://img.shields.io/badge/Jetpack%20Compose-1.1.1-brightgreen)
![build-test-check](https://github.com/microsoft/surface-duo-compose-samples/actions/workflows/build_test_check.yml/badge.svg) ![Compose Version](https://img.shields.io/badge/Jetpack%20Compose-1.2.0-brightgreen)
# Surface Duo Jetpack Compose Samples
@ -26,7 +26,7 @@ Please check out our page on [Jetpack Compose for Microsoft Surface Duo](https:/
## Prerequisites
- Jetpack Compose version: `1.1.1`
- Jetpack Compose version: `1.2.0`
- Jetpack WindowManager version: `1.0.0`
@ -70,17 +70,17 @@ The samples are built with Microsoft Compose libraries, [TwoPaneLayout](https://
| Blog post | Video |
|---|---|
| [Source Editor & Diary samples for Jetpack Compose](https://devblogs.microsoft.com/surface-duo/jetpack-compose-source-editor-diary-samples/) | [Twitch #79: More new Jetpack Compose samples] (https://www.youtube.com/watch?v=eO0D3MHvKLA)
| [Video+Chat and Calculator samples for Jetpack Compose](https://devblogs.microsoft.com/surface-duo/jetpack-compose-video-calculator-samples/) | [Twitch #77: New Jetpack Compose samples](https://www.twitch.tv/videos/1519558235) |
| [Jetpack Compose TwoPaneLayout update](https://devblogs.microsoft.com/surface-duo/jetpack-compose-foldable-twopanelayout/) | [Twitch #76: Jetpack Compose TwoPaneLayout update](https://www.youtube.com/watch?v=cI73qh_mTOo)|
| [Source Editor & Diary samples for Jetpack Compose](https://devblogs.microsoft.com/surface-duo/jetpack-compose-source-editor-diary-samples/) | [Twitch #79: More new Jetpack Compose samples](https://www.youtube.com/watch?v=eO0D3MHvKLA) |
| [Video+Chat and Calculator samples for Jetpack Compose](https://devblogs.microsoft.com/surface-duo/jetpack-compose-video-calculator-samples/) | [Twitch #77: New Jetpack Compose samples](https://www.youtube.com/watch?v=b4WG3moVriA) |
| [Jetpack Compose TwoPaneLayout update](https://devblogs.microsoft.com/surface-duo/jetpack-compose-foldable-twopanelayout/) | [Twitch #76: Jetpack Compose TwoPaneLayout update](https://www.youtube.com/watch?v=cI73qh_mTOo) |
| [Write foldable tests quickly with Test Kit](https://devblogs.microsoft.com/surface-duo/foldable-ui-test-kit/) | [Twitch #63: Test Kit for foldable apps](https://www.youtube.com/watch?v=3I0qU5SeUBM) |
| [Jetpack Compose UI testing](https://devblogs.microsoft.com/surface-duo/jetpack-compose-ui-test/) | [Twitch #59: Jetpack Compose testing](https://www.youtube.com/watch?v=Q3lDz7PjO7U) |
| [Jetpack Compose WindowState preview](https://devblogs.microsoft.com/surface-duo/jetpack-compose-windowstate-preview/) | [Twitch #53: Jetpack Compose WindowState for foldable devices](https://www.youtube.com/watch?v=qOIliow-uS4) |
| [Get started with Jetpack Compose](https://devblogs.microsoft.com/surface-duo/get-started-with-jetpack-compose/) | [Twitch #44: Get started with Jetpack Compose](https://www.youtube.com/watch?v=ijXDWDtdiIE) |
| [Jetpack Compose Navigation Rail](https://devblogs.microsoft.com/surface-duo/jetpack-compose-navigation-rail/) | [Twitch #42: Jetpack Compose Navigation Rail](https://www.youtube.com/watch?v=pdoIyOU7Suk)
| [Jetpack Compose Navigation Rail](https://devblogs.microsoft.com/surface-duo/jetpack-compose-navigation-rail/) | [Twitch #42: Jetpack Compose Navigation Rail](https://www.youtube.com/watch?v=pdoIyOU7Suk) |
| [New TwoPaneLayout Compose library preview](https://devblogs.microsoft.com/surface-duo/jetpack-compose-twopanelayout-preview/) | [Twitch #25: TwoPaneLayout preview for Jetpack Compose](https://www.youtube.com/watch?v=Q66bR2jKdrg) |
| [Jetpack Compose foldable and dual-screen development](https://devblogs.microsoft.com/surface-duo/jetpack-compose-foldable-samples) | [Twitch #9: Jetpack Compose samples](https://www.youtube.com/watch?v=m8bMjFhBbN8) |
| [Jetpack Compose on Microsoft Surface Duo](https://devblogs.microsoft.com/surface-duo/jetpack-compose-dual-screen-sample/) | N/A|
| [Jetpack Compose on Microsoft Surface Duo](https://devblogs.microsoft.com/surface-duo/jetpack-compose-dual-screen-sample/) | N/A |
## Related links

Просмотреть файл

@ -42,6 +42,7 @@ android {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
namespace 'com.microsoft.device.display.samples.sourceeditorcompose'
}
dependencies {

Просмотреть файл

@ -7,8 +7,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.microsoft.device.display.samples.sourceeditorcompose">
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"

Просмотреть файл

@ -8,6 +8,7 @@
package com.microsoft.device.display.samples.sourceeditorcompose.ui.pages
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
@ -29,32 +30,34 @@ import com.microsoft.device.dualscreen.twopanelayout.TwoPaneScope
*/
@Composable
fun TwoPaneScope.EditorPage(text: String, updateText: (String) -> Unit) {
val twoPaneScope = this
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = stringResource(R.string.app_name))
},
backgroundColor = MaterialTheme.colors.primaryVariant,
contentColor = Color.White,
actions = {
if (twoPaneScope.isSinglePane) {
IconButton(onClick = { twoPaneScope.navigateToPane2() }) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = stringResource(R.string.show_html)
)
}
}
}
)
}
topBar = { EditorTopBar() }
) {
TextField(
value = text,
onValueChange = { newText -> updateText(newText) },
modifier = Modifier.fillMaxSize()
modifier = Modifier
.fillMaxSize()
.padding(it)
)
}
}
@Composable
fun TwoPaneScope.EditorTopBar() {
TopAppBar(
title = { Text(text = stringResource(R.string.app_name)) },
backgroundColor = MaterialTheme.colors.primaryVariant,
contentColor = Color.White,
actions = {
if (this@EditorTopBar.isSinglePane) {
IconButton(onClick = { this@EditorTopBar.navigateToPane2() }) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = stringResource(R.string.show_html)
)
}
}
}
)
}

Просмотреть файл

@ -10,6 +10,7 @@ package com.microsoft.device.display.samples.sourceeditorcompose.ui.pages
import android.view.ViewGroup
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
@ -19,6 +20,7 @@ import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
@ -30,31 +32,11 @@ import com.microsoft.device.dualscreen.twopanelayout.TwoPaneScope
*/
@Composable
fun TwoPaneScope.PreviewPage(text: String) {
val twoPaneScope = this
Scaffold(
topBar = {
TopAppBar(
title = {
if (isSinglePane) {
Text(text = stringResource(id = R.string.app_name))
}
},
contentColor = Color.White,
backgroundColor = MaterialTheme.colors.primaryVariant,
actions = {
if (twoPaneScope.isSinglePane) {
IconButton(onClick = { twoPaneScope.navigateToPane1() }) {
Icon(
imageVector = Icons.Filled.Edit,
contentDescription = stringResource(R.string.show_source)
)
}
}
}
)
}
) {
topBar = { PreviewTopBar() }
) { paddingValues ->
AndroidView(
modifier = Modifier.padding(paddingValues),
factory = {
WebView(it).apply {
layoutParams = ViewGroup.LayoutParams(
@ -71,3 +53,25 @@ fun TwoPaneScope.PreviewPage(text: String) {
)
}
}
@Composable
fun TwoPaneScope.PreviewTopBar() {
TopAppBar(
title = {
if (isSinglePane)
Text(text = stringResource(id = R.string.app_name))
},
contentColor = Color.White,
backgroundColor = MaterialTheme.colors.primaryVariant,
actions = {
if (this@PreviewTopBar.isSinglePane) {
IconButton(onClick = { this@PreviewTopBar.navigateToPane1() }) {
Icon(
imageVector = Icons.Filled.Edit,
contentDescription = stringResource(R.string.show_source)
)
}
}
}
)
}

Просмотреть файл

@ -38,6 +38,7 @@ android {
excludes += ['META-INF/licenses/**', 'META-INF/AL2.0', 'META-INF/LGPL2.1']
}
}
namespace 'com.microsoft.device.display.samples.twopage'
}
dependencies {
@ -63,4 +64,6 @@ dependencies {
androidTestImplementation testDependencies.composeJunit
androidTestImplementation testDependencies.uiAutomator
androidTestImplementation microsoftDependencies.composeTesting
debugImplementation testDependencies.composeUITestManifest
}

Просмотреть файл

@ -15,7 +15,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.hasScrollAction
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.text.font.FontWeight
@ -28,7 +28,7 @@ import org.junit.Test
class PageContentTest {
@get: Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
val composeTestRule = createComposeRule()
/**
* Tests that page content is scrollable
@ -42,14 +42,14 @@ class PageContentTest {
}
// Assert last text element in not visible
composeTestRule.onNodeWithText(composeTestRule.getString(R.string.article_title)).assertIsNotDisplayed()
composeTestRule.onNodeWithText(getString(R.string.article_title)).assertIsNotDisplayed()
// Assert page has scroll action
composeTestRule.onNode(hasScrollAction()).assertExists()
// Scroll to end of page content and assert last element is now visible
composeTestRule.onNodeWithText(composeTestRule.getString(R.string.article_title)).performScrollTo()
composeTestRule.onNodeWithText(composeTestRule.getString(R.string.article_title)).assertIsDisplayed()
composeTestRule.onNodeWithText(getString(R.string.article_title)).performScrollTo()
composeTestRule.onNodeWithText(getString(R.string.article_title)).assertIsDisplayed()
}
/**
@ -69,7 +69,7 @@ class PageContentTest {
composeTestRule.onNodeWithText(pageNumberText).assertIsDisplayed()
// Scroll to end of page content and assert that page number is still visible
composeTestRule.onNodeWithText(composeTestRule.getString(R.string.article_title)).performScrollTo()
composeTestRule.onNodeWithText(getString(R.string.article_title)).performScrollTo()
composeTestRule.onNodeWithText(pageNumberText).assertIsDisplayed()
}

Просмотреть файл

@ -5,7 +5,6 @@
package com.microsoft.device.display.samples.twopage
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
@ -14,31 +13,24 @@ import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
import androidx.compose.ui.unit.dp
import com.microsoft.device.display.samples.twopage.ui.theme.TwoPageAppTheme
import com.microsoft.device.display.samples.twopage.ui.view.TwoPageApp
import com.microsoft.device.display.samples.twopage.ui.view.TwoPageAppContent
import com.microsoft.device.dualscreen.testing.compose.foldableRuleChain
import com.microsoft.device.dualscreen.testing.compose.getString
import com.microsoft.device.dualscreen.testing.createWindowLayoutInfoPublisherRule
import com.microsoft.device.dualscreen.testing.filters.MockFoldingFeature
import com.microsoft.device.dualscreen.testing.filters.SingleScreenTest
import com.microsoft.device.dualscreen.windowstate.WindowState
import com.microsoft.device.dualscreen.testing.rules.FoldableTestRule
import com.microsoft.device.dualscreen.testing.runner.FoldableJUnit4ClassRunner
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(FoldableJUnit4ClassRunner::class)
class PageSwipeTest {
private val composeTestRule = createAndroidComposeRule<MainActivity>()
private val publisherRule = createWindowLayoutInfoPublisherRule()
private val foldableTestRule = FoldableTestRule()
@get: Rule
val testRule: TestRule
init {
testRule = RuleChain.outerRule(publisherRule).around(composeTestRule)
RuleChain.outerRule(composeTestRule)
}
@get:Rule
val testRule: TestRule = foldableRuleChain(composeTestRule, foldableTestRule)
/**
* Tests that the pages swipe only between 1 and 4 in single screen mode
@ -46,12 +38,6 @@ class PageSwipeTest {
@Test
@SingleScreenTest
fun app_singlescreen_pagesSwipeWithinLimits() {
composeTestRule.setContent {
TwoPageAppTheme {
TwoPageAppContent(pane1WidthDp = 0.dp, pane2WidthDp = 0.dp, isDualScreen = false, foldSizeDp = 0.dp)
}
}
swipeOnePageAtATime()
}
@ -61,12 +47,6 @@ class PageSwipeTest {
@Test
@MockFoldingFeature(orientation = MockFoldingFeature.FoldingFeatureOrientation.HORIZONTAL)
fun app_horizontalFold_pagesSwipeWithinLimits() {
composeTestRule.setContent {
TwoPageAppTheme {
TwoPageApp(WindowState(hasFold = true, foldIsHorizontal = true, foldIsSeparating = true))
}
}
swipeOnePageAtATime()
}
@ -119,19 +99,6 @@ class PageSwipeTest {
@Test
@MockFoldingFeature(orientation = MockFoldingFeature.FoldingFeatureOrientation.VERTICAL)
fun app_verticalFold_pagesSwipeWithinLimits() {
composeTestRule.setContent {
val pageWidth = LocalConfiguration.current.screenWidthDp / 2
TwoPageAppTheme {
TwoPageAppContent(
pane1WidthDp = pageWidth.dp,
pane2WidthDp = pageWidth.dp,
isDualScreen = true,
foldSizeDp = 0.dp
)
}
}
val pageTags = listOf(R.string.page1_tag, R.string.page2_tag, R.string.page3_tag, R.string.page4_tag)
// Swipe forwards

Просмотреть файл

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.device.display.samples.twopage">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

Просмотреть файл

@ -5,6 +5,7 @@
package com.microsoft.device.display.samples.twopage.ui.view
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@ -39,7 +40,8 @@ fun TwoPageApp(windowState: WindowState) {
pane1WidthDp = windowState.pane1SizeDp.width,
pane2WidthDp = windowState.pane2SizeDp.width,
isDualScreen = windowState.isDualPortrait(),
foldSizeDp = windowState.foldSizeDp
foldSizeDp = windowState.foldSizeDp,
paddingValues = it
)
}
)
@ -62,7 +64,13 @@ fun TwoPageTopBar() {
}
@Composable
fun TwoPageAppContent(pane1WidthDp: Dp, pane2WidthDp: Dp, isDualScreen: Boolean, foldSizeDp: Dp) {
fun TwoPageAppContent(
pane1WidthDp: Dp,
pane2WidthDp: Dp,
isDualScreen: Boolean,
foldSizeDp: Dp,
paddingValues: PaddingValues
) {
// Calculate page text width based on the smallest pane width
val pageTextWidth = min(pane1WidthDp, pane2WidthDp)
@ -70,18 +78,20 @@ fun TwoPageAppContent(pane1WidthDp: Dp, pane2WidthDp: Dp, isDualScreen: Boolean,
val pagePadding = abs(pane1WidthDp.value - pane2WidthDp.value).dp + foldSizeDp
val pages = setupPages(pageTextWidth, pagePadding)
PageViews(pages, isDualScreen)
PageViews(pages, isDualScreen, paddingValues)
}
@Composable
fun PageViews(pages: List<@Composable () -> Unit>, isDualScreen: Boolean) {
fun PageViews(pages: List<@Composable () -> Unit>, isDualScreen: Boolean, paddingValues: PaddingValues) {
val maxPage = (pages.size - 1).coerceAtLeast(0)
val pagerState: PagerState =
remember { PagerState(currentPage = 0, minPage = 0, maxPage = maxPage) }
pagerState.isDualMode = isDualScreen
ViewPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
pages[page]()
}

Просмотреть файл

@ -42,6 +42,7 @@ android {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
namespace 'com.microsoft.device.display.samples.videochatcomposesample'
}
dependencies {

Просмотреть файл

@ -5,8 +5,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.microsoft.device.display.samples.videochatcomposesample">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>

Просмотреть файл

@ -62,6 +62,7 @@ fun ChatPage(focusManager: FocusManager) {
modifier = Modifier
.fillMaxHeight(0.85f)
.fillMaxWidth()
.padding(it)
.background(MaterialTheme.colors.secondary)
)
}
@ -149,6 +150,7 @@ fun ChatInputBar(focusManager: FocusManager) {
@Composable
fun ChatList(modifier: Modifier = Modifier) {
val chatModel = ChatModel()
LazyColumn(
modifier = modifier.padding(all = 10.dp)
) {

Просмотреть файл

@ -51,26 +51,14 @@ fun VideoPage(
@Composable
fun FullscreenButton(modifier: Modifier, isFullScreen: Boolean, updateFullScreen: (Boolean) -> Unit) {
fun onClick() = updateFullScreen(!isFullScreen)
val drawableResId = if (isFullScreen) R.drawable.exitfullscreen else R.drawable.fullscreen
val stringResId = if (isFullScreen) R.string.contentFull else R.string.contentMin
if (isFullScreen) Icon(
Icon(
tint = MaterialTheme.colors.onBackground,
painter = painterResource(id = R.drawable.exitfullscreen),
contentDescription = stringResource(id = R.string.contentFull),
modifier = modifier.clickable(
onClick = {
onClick()
}
)
) else Icon(
tint = MaterialTheme.colors.onBackground,
painter = painterResource(id = R.drawable.fullscreen),
contentDescription = stringResource(id = R.string.contentMin),
modifier = modifier.clickable(
onClick = {
onClick()
}
)
painter = painterResource(id = drawableResId),
contentDescription = stringResource(id = stringResId),
modifier = modifier.clickable(onClick = { onClick() })
)
}

Просмотреть файл

@ -4,9 +4,9 @@
*/
ext {
gradlePluginVersion = '7.1.1'
kotlinVersion = "1.6.10"
compileSdkVersion = 31
gradlePluginVersion = '7.2.0'
kotlinVersion = "1.7.0"
compileSdkVersion = 32
targetSdkVersion = compileSdkVersion
minSdkVersion = 23
@ -21,9 +21,9 @@ ext {
]
// AndroidX versions
appCompatVersion = '1.4.1'
ktxCoreVersion = '1.7.0'
lcRunVersion = '2.4.1'
appCompatVersion = '1.4.2'
ktxCoreVersion = '1.8.0'
lcRunVersion = '2.5.1'
androidxDependencies = [
appCompat : "androidx.appcompat:appcompat:$appCompatVersion",
ktxCore : "androidx.core:core-ktx:$ktxCoreVersion",
@ -31,9 +31,9 @@ ext {
]
// Compose dependencies
composeVersion = "1.1.1"
activityComposeVersion = "1.4.0"
navigationComposeVersion = '2.4.2'
composeVersion = '1.2.0'
activityComposeVersion = '1.5.1'
navigationComposeVersion = '2.5.1'
composeDependencies = [
composeAnimation : "androidx.compose.animation:animation:$composeVersion",
composeRuntime : "androidx.compose.runtime:runtime-livedata:$composeVersion",
@ -64,8 +64,8 @@ ext {
]
// Google dependencies
materialVersion = '1.5.0'
exoPlayerVersion = '2.17.1'
materialVersion = '1.6.1'
exoPlayerVersion = '2.18.1'
systemUiControllerVersion = '0.17.0'
googleDependencies = [
material: "com.google.android.material:material:$materialVersion",
@ -74,10 +74,10 @@ ext {
]
// Microsoft dependencies
twoPaneLayoutVersion = "1.0.1-alpha02"
windowStateVersion = "1.0.0-alpha04"
composeTestingVersion = "1.0.0-alpha04"
dragAndDropVersion = "1.0.0-alpha01"
twoPaneLayoutVersion = "1.0.1-alpha03"
windowStateVersion = "1.0.0-alpha05"
composeTestingVersion = "1.0.0-alpha06"
dragAndDropVersion = "1.0.0-alpha02"
microsoftDependencies = [
twoPaneLayout : "com.microsoft.device.dualscreen:twopanelayout:$twoPaneLayoutVersion",
windowState : "com.microsoft.device.dualscreen:windowstate:$windowStateVersion",

2
gradle/wrapper/gradle-wrapper.properties поставляемый
Просмотреть файл

@ -5,7 +5,7 @@
#Thu Feb 25 11:36:45 PST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME