diff --git a/CompanionPane/build.gradle b/CompanionPane/build.gradle
index c3221a9..84b561d 100644
--- a/CompanionPane/build.gradle
+++ b/CompanionPane/build.gradle
@@ -35,7 +35,6 @@ dependencies {
implementation androidxDependencies.ktxCore
implementation androidxDependencies.appCompat
- implementation androidxDependencies.window
implementation composeDependencies.composeUI
implementation composeDependencies.composeRuntime
@@ -44,6 +43,7 @@ dependencies {
implementation composeDependencies.activityCompose
implementation microsoftDependencies.twoPaneLayout
+ implementation project(':WindowState')
implementation googleDependencies.material
}
\ No newline at end of file
diff --git a/CompanionPane/src/main/java/com/microsoft/device/display/samples/companionpane/HomePage.kt b/CompanionPane/src/main/java/com/microsoft/device/display/samples/companionpane/HomePage.kt
index e40c0d3..139cdf9 100644
--- a/CompanionPane/src/main/java/com/microsoft/device/display/samples/companionpane/HomePage.kt
+++ b/CompanionPane/src/main/java/com/microsoft/device/display/samples/companionpane/HomePage.kt
@@ -5,7 +5,6 @@
package com.microsoft.device.display.samples.companionpane
-import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
@@ -26,7 +25,6 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -34,14 +32,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowInfoRepository
import com.microsoft.device.display.samples.companionpane.uicomponent.BrightnessPanel
import com.microsoft.device.display.samples.companionpane.uicomponent.DefinitionPanel
import com.microsoft.device.display.samples.companionpane.uicomponent.EffectPanel
@@ -51,56 +46,20 @@ import com.microsoft.device.display.samples.companionpane.uicomponent.MagicWandP
import com.microsoft.device.display.samples.companionpane.uicomponent.ShortFilterControl
import com.microsoft.device.display.samples.companionpane.uicomponent.VignettePanel
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneLayout
-import kotlinx.coroutines.flow.collect
+import com.microsoft.device.dualscreen.windowstate.WindowMode
+import com.microsoft.device.dualscreen.windowstate.WindowState
private val shortSlideWidth = 200.dp
private val longSlideWidth = 350.dp
-const val SMALLEST_TABLET_SCREEN_WIDTH_DP = 585
-
-enum class ScreenState {
- SinglePortrait,
- SingleLandscape,
- DualPortrait,
- DualLandscape
-}
@Composable
-fun SetupUI(windowInfoRep: WindowInfoRepository) {
- var screenState by remember { mutableStateOf(ScreenState.SinglePortrait) }
- var isAppSpanned by remember { mutableStateOf(false) }
- var isHingeHorizontal by remember { mutableStateOf(false) }
-
- LaunchedEffect(windowInfoRep) {
- windowInfoRep.windowLayoutInfo
- .collect { newLayoutInfo ->
- val displayFeatures = newLayoutInfo.displayFeatures
- isAppSpanned = displayFeatures.isNotEmpty()
- if (isAppSpanned) {
- val foldingFeature = displayFeatures.first() as? FoldingFeature
- foldingFeature?.let {
- isHingeHorizontal = it.orientation == FoldingFeature.Orientation.HORIZONTAL
- }
- }
- }
- }
-
- val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
- val smallestScreenWidthDp = LocalConfiguration.current.smallestScreenWidthDp
- val isTablet = smallestScreenWidthDp > SMALLEST_TABLET_SCREEN_WIDTH_DP
- val isDualScreen = (isAppSpanned || isTablet)
-
- screenState =
- if (isDualScreen) {
- val showDualLandscape = if (isAppSpanned) isHingeHorizontal else isPortrait
- if (showDualLandscape) ScreenState.DualLandscape else ScreenState.DualPortrait
- } else {
- // NOTE: the LocalConfiguration orientation info should only be used in single screen mode
- if (isPortrait) ScreenState.SinglePortrait else ScreenState.SingleLandscape
- }
+fun CompanionPaneApp(windowState: WindowState) {
+ var windowMode by remember { mutableStateOf(WindowMode.SINGLE_PORTRAIT) }
+ windowMode = windowState.windowMode
TwoPaneLayout(
- pane1 = { Pane1(screenState) },
- pane2 = { Pane2(screenState) },
+ pane1 = { Pane1(windowMode) },
+ pane2 = { Pane2(windowMode) },
)
}
@@ -127,14 +86,14 @@ fun ShowWithTopBar(content: @Composable () -> Unit, title: String? = null) {
}
@Composable
-fun Pane1(screenState: ScreenState) {
+fun Pane1(windowMode: WindowMode) {
ShowWithTopBar(
content = {
- when (screenState) {
- ScreenState.SinglePortrait -> PortraitLayout()
- ScreenState.SingleLandscape -> LandscapeLayout()
- ScreenState.DualPortrait -> DualPortraitPane1()
- ScreenState.DualLandscape -> DualLandscapePane1()
+ when (windowMode) {
+ WindowMode.SINGLE_PORTRAIT -> PortraitLayout()
+ WindowMode.SINGLE_LANDSCAPE -> LandscapeLayout()
+ WindowMode.DUAL_PORTRAIT -> DualPortraitPane1()
+ WindowMode.DUAL_LANDSCAPE -> DualLandscapePane1()
}
},
title = stringResource(R.string.app_name)
@@ -142,14 +101,14 @@ fun Pane1(screenState: ScreenState) {
}
@Composable
-fun Pane2(screenState: ScreenState) {
- when (screenState) {
- ScreenState.DualPortrait -> {
+fun Pane2(windowMode: WindowMode) {
+ when (windowMode) {
+ WindowMode.DUAL_PORTRAIT -> {
ShowWithTopBar(
content = { DualPortraitPane2() }
)
}
- ScreenState.DualLandscape -> DualLandscapePane2()
+ WindowMode.DUAL_LANDSCAPE -> DualLandscapePane2()
else -> {}
}
}
diff --git a/CompanionPane/src/main/java/com/microsoft/device/display/samples/companionpane/MainActivity.kt b/CompanionPane/src/main/java/com/microsoft/device/display/samples/companionpane/MainActivity.kt
index 9634f7f..c21c28c 100644
--- a/CompanionPane/src/main/java/com/microsoft/device/display/samples/companionpane/MainActivity.kt
+++ b/CompanionPane/src/main/java/com/microsoft/device/display/samples/companionpane/MainActivity.kt
@@ -8,21 +8,21 @@ package com.microsoft.device.display.samples.companionpane
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
-import androidx.window.layout.WindowInfoRepository
-import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository
import com.microsoft.device.display.samples.companionpane.ui.CompanionPaneAppsTheme
+import com.microsoft.device.dualscreen.windowstate.WindowState
+import com.microsoft.device.dualscreen.windowstate.rememberWindowState
class MainActivity : AppCompatActivity() {
- private lateinit var windowInfoRep: WindowInfoRepository
+ private lateinit var windowState: WindowState
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- windowInfoRep = windowInfoRepository()
-
setContent {
+ windowState = rememberWindowState()
+
CompanionPaneAppsTheme {
- SetupUI(windowInfoRep)
+ CompanionPaneApp(windowState)
}
}
}
diff --git a/ComposeGallery/build.gradle b/ComposeGallery/build.gradle
index 56544ec..6d6eb42 100644
--- a/ComposeGallery/build.gradle
+++ b/ComposeGallery/build.gradle
@@ -40,7 +40,6 @@ dependencies {
implementation androidxDependencies.ktxCore
implementation androidxDependencies.appCompat
- implementation androidxDependencies.window
implementation composeDependencies.composeUI
implementation composeDependencies.composeRuntime
@@ -49,6 +48,7 @@ dependencies {
implementation composeDependencies.activityCompose
implementation microsoftDependencies.twoPaneLayout
+ implementation project(':WindowState')
implementation googleDependencies.material
diff --git a/ComposeGallery/src/androidTest/java/com/microsoft/device/display/samples/composegallery/PaneSynchronizationTest.kt b/ComposeGallery/src/androidTest/java/com/microsoft/device/display/samples/composegallery/PaneSynchronizationTest.kt
index 55a4a27..fd0670b 100644
--- a/ComposeGallery/src/androidTest/java/com/microsoft/device/display/samples/composegallery/PaneSynchronizationTest.kt
+++ b/ComposeGallery/src/androidTest/java/com/microsoft/device/display/samples/composegallery/PaneSynchronizationTest.kt
@@ -5,6 +5,7 @@
package com.microsoft.device.display.samples.composegallery
+import android.graphics.Rect
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasAnyAncestor
@@ -26,6 +27,9 @@ import com.microsoft.device.display.samples.composegallery.ui.view.ComposeGaller
import com.microsoft.device.dualscreen.testutils.getString
import com.microsoft.device.dualscreen.testutils.simulateHorizontalFold
import com.microsoft.device.dualscreen.testutils.simulateVerticalFold
+import com.microsoft.device.dualscreen.windowstate.FoldState
+import com.microsoft.device.dualscreen.windowstate.WindowSizeClass
+import com.microsoft.device.dualscreen.windowstate.WindowState
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
@@ -52,8 +56,16 @@ class PaneSynchronizationTest {
composeTestRule.setContent {
ComposeGalleryTheme {
ComposeGalleryApp(
- foldableState = FoldableState(hasFold = true, isFoldHorizontal = false),
- widthSizeClass = WindowSizeClass.Medium
+ WindowState(
+ hasFold = true,
+ isFoldHorizontal = false,
+ foldBounds = Rect(),
+ foldState = FoldState.HALF_OPENED,
+ foldSeparates = true,
+ foldOccludes = true,
+ widthSizeClass = WindowSizeClass.MEDIUM,
+ heightSizeClass = WindowSizeClass.MEDIUM
+ )
)
}
}
@@ -93,8 +105,16 @@ class PaneSynchronizationTest {
composeTestRule.setContent {
ComposeGalleryTheme {
ComposeGalleryApp(
- foldableState = FoldableState(hasFold = false, isFoldHorizontal = false),
- widthSizeClass = WindowSizeClass.Compact
+ WindowState(
+ hasFold = false,
+ isFoldHorizontal = false,
+ foldBounds = Rect(),
+ foldState = FoldState.HALF_OPENED,
+ foldSeparates = true,
+ foldOccludes = true,
+ widthSizeClass = WindowSizeClass.COMPACT,
+ heightSizeClass = WindowSizeClass.MEDIUM
+ )
)
}
}
@@ -126,8 +146,16 @@ class PaneSynchronizationTest {
composeTestRule.setContent {
ComposeGalleryTheme {
ComposeGalleryApp(
- foldableState = FoldableState(hasFold = true, isFoldHorizontal = true),
- widthSizeClass = WindowSizeClass.Compact
+ WindowState(
+ hasFold = true,
+ isFoldHorizontal = true,
+ foldBounds = Rect(),
+ foldState = FoldState.HALF_OPENED,
+ foldSeparates = true,
+ foldOccludes = true,
+ widthSizeClass = WindowSizeClass.COMPACT,
+ heightSizeClass = WindowSizeClass.MEDIUM
+ )
)
}
}
diff --git a/ComposeGallery/src/androidTest/java/com/microsoft/device/display/samples/composegallery/TopAppBarTest.kt b/ComposeGallery/src/androidTest/java/com/microsoft/device/display/samples/composegallery/TopAppBarTest.kt
index 9be8104..5b69ee8 100644
--- a/ComposeGallery/src/androidTest/java/com/microsoft/device/display/samples/composegallery/TopAppBarTest.kt
+++ b/ComposeGallery/src/androidTest/java/com/microsoft/device/display/samples/composegallery/TopAppBarTest.kt
@@ -5,6 +5,7 @@
package com.microsoft.device.display.samples.composegallery
+import android.graphics.Rect
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
@@ -21,6 +22,9 @@ import com.microsoft.device.display.samples.composegallery.ui.view.ComposeGaller
import com.microsoft.device.display.samples.composegallery.ui.view.DetailPane
import com.microsoft.device.display.samples.composegallery.ui.view.ListPane
import com.microsoft.device.dualscreen.testutils.getString
+import com.microsoft.device.dualscreen.windowstate.FoldState
+import com.microsoft.device.dualscreen.windowstate.WindowSizeClass
+import com.microsoft.device.dualscreen.windowstate.WindowState
import org.junit.Rule
import org.junit.Test
@@ -178,8 +182,16 @@ class TopAppBarTest {
composeTestRule.setContent {
ComposeGalleryTheme {
ComposeGalleryApp(
- foldableState = FoldableState(hasFold = false, isFoldHorizontal = false),
- widthSizeClass = WindowSizeClass.Compact
+ WindowState(
+ hasFold = false,
+ isFoldHorizontal = false,
+ foldBounds = Rect(),
+ foldState = FoldState.HALF_OPENED,
+ foldSeparates = true,
+ foldOccludes = true,
+ widthSizeClass = WindowSizeClass.COMPACT,
+ heightSizeClass = WindowSizeClass.MEDIUM
+ )
)
}
}
diff --git a/ComposeGallery/src/main/java/com/microsoft/device/display/samples/composegallery/MainActivity.kt b/ComposeGallery/src/main/java/com/microsoft/device/display/samples/composegallery/MainActivity.kt
index 2a15ff9..5e1e75b 100644
--- a/ComposeGallery/src/main/java/com/microsoft/device/display/samples/composegallery/MainActivity.kt
+++ b/ComposeGallery/src/main/java/com/microsoft/device/display/samples/composegallery/MainActivity.kt
@@ -5,102 +5,26 @@
package com.microsoft.device.display.samples.composegallery
-import android.app.Activity
import android.os.Bundle
import androidx.activity.compose.setContent
-import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository
import com.microsoft.device.display.samples.composegallery.ui.ComposeGalleryTheme
import com.microsoft.device.display.samples.composegallery.ui.view.ComposeGalleryApp
-import kotlinx.coroutines.flow.collect
-
-enum class WindowSizeClass { Compact, Medium, Expanded }
-data class FoldableState(val hasFold: Boolean, val isFoldHorizontal: Boolean)
+import com.microsoft.device.dualscreen.windowstate.WindowState
+import com.microsoft.device.dualscreen.windowstate.rememberWindowState
class MainActivity : AppCompatActivity() {
+ private lateinit var windowState: WindowState
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
- val widthSizeClass = rememberWidthSizeClass()
- val foldableState = rememberFoldableState()
+ windowState = rememberWindowState()
ComposeGalleryTheme {
- ComposeGalleryApp(foldableState, widthSizeClass)
+ ComposeGalleryApp(windowState)
}
}
}
}
-
-@Composable
-fun Activity.rememberFoldableState(): FoldableState {
- val windowInfoRepo = windowInfoRepository()
- var hasFold by remember { mutableStateOf(false) }
- var isFoldHorizontal by remember { mutableStateOf(false) }
-
- LaunchedEffect(windowInfoRepo) {
- windowInfoRepo.windowLayoutInfo.collect { newLayoutInfo ->
- hasFold = newLayoutInfo.displayFeatures.isNotEmpty()
- if (hasFold) {
- val fold = newLayoutInfo.displayFeatures.firstOrNull() as? FoldingFeature
- fold?.let {
- isFoldHorizontal = it.orientation == FoldingFeature.Orientation.HORIZONTAL
- }
- }
- }
- }
-
- return FoldableState(hasFold, isFoldHorizontal)
-}
-
-/**
- * Implementation taken from JetNews sample
- * https://github.com/android/compose-samples/blob/main/JetNews/app/src/main/java/com/example/jetnews/utils/WindowSize.kt
- *
- * Remembers the [WindowSizeClass] class for the window corresponding to the current window metrics.
- */
-@Composable
-fun rememberWidthSizeClass(): WindowSizeClass {
- // Get the size (in pixels) of the window
- val windowSize = rememberWindowSize()
-
- // Calculate the width window size class
- return getWindowSizeClass(windowSize)
-}
-
-/**
- * Remembers the [Size] in pixels of the window corresponding to the current window metrics.
- */
-@Composable
-private fun rememberWindowSize(): Dp {
- val configuration = LocalConfiguration.current
-
- val windowMetrics = remember(configuration) {
- configuration.smallestScreenWidthDp.dp
- }
- return windowMetrics
-}
-
-/**
- * Partitions a [Dp] into a enumerated [WindowSizeClass] class.
- */
-@VisibleForTesting
-fun getWindowSizeClass(windowDpWidth: Dp): WindowSizeClass = when {
- windowDpWidth < 0.dp -> throw IllegalArgumentException("Dp value cannot be negative")
- windowDpWidth < 600.dp -> WindowSizeClass.Compact
- windowDpWidth < 840.dp -> WindowSizeClass.Medium
- else -> WindowSizeClass.Expanded
-}
diff --git a/ComposeGallery/src/main/java/com/microsoft/device/display/samples/composegallery/ui/view/HomePage.kt b/ComposeGallery/src/main/java/com/microsoft/device/display/samples/composegallery/ui/view/HomePage.kt
index cd7f9bb..c347ce5 100644
--- a/ComposeGallery/src/main/java/com/microsoft/device/display/samples/composegallery/ui/view/HomePage.kt
+++ b/ComposeGallery/src/main/java/com/microsoft/device/display/samples/composegallery/ui/view/HomePage.kt
@@ -5,7 +5,6 @@
package com.microsoft.device.display.samples.composegallery.ui.view
-import android.content.res.Configuration
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
@@ -16,27 +15,22 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import com.microsoft.device.display.samples.composegallery.FoldableState
import com.microsoft.device.display.samples.composegallery.R
-import com.microsoft.device.display.samples.composegallery.WindowSizeClass
import com.microsoft.device.display.samples.composegallery.models.DataProvider
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneLayout
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneMode
+import com.microsoft.device.dualscreen.windowstate.WindowState
@Composable
-fun ComposeGalleryApp(foldableState: FoldableState, widthSizeClass: WindowSizeClass) {
+fun ComposeGalleryApp(windowState: WindowState) {
// Check if app should be in dual mode
- val isDualPortraitFoldable = foldableState.hasFold && !foldableState.isFoldHorizontal
- val isLargeScreen = !foldableState.hasFold && widthSizeClass != WindowSizeClass.Compact
- val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
- val isDualMode = isDualPortraitFoldable || (isLargeScreen && isLandscape)
+ val isDualMode = windowState.isDualPortrait()
// Get relevant image data for the panes
val models = DataProvider.imageModels
diff --git a/DualView/build.gradle b/DualView/build.gradle
index 9ac5f4b..2fa9eec 100644
--- a/DualView/build.gradle
+++ b/DualView/build.gradle
@@ -35,7 +35,6 @@ dependencies {
implementation androidxDependencies.ktxCore
implementation androidxDependencies.appCompat
- implementation androidxDependencies.window
implementation composeDependencies.composeUI
implementation composeDependencies.composeRuntime
@@ -46,4 +45,5 @@ dependencies {
implementation googleDependencies.material
implementation microsoftDependencies.twoPaneLayout
+ implementation project(':WindowState')
}
\ No newline at end of file
diff --git a/DualView/src/main/java/com/microsoft/device/display/samples/dualview/MainActivity.kt b/DualView/src/main/java/com/microsoft/device/display/samples/dualview/MainActivity.kt
index b64c838..bbb1e96 100644
--- a/DualView/src/main/java/com/microsoft/device/display/samples/dualview/MainActivity.kt
+++ b/DualView/src/main/java/com/microsoft/device/display/samples/dualview/MainActivity.kt
@@ -9,25 +9,26 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
-import androidx.window.layout.WindowInfoRepository
-import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository
import com.microsoft.device.display.samples.dualview.models.AppStateViewModel
-import com.microsoft.device.display.samples.dualview.ui.home.SetupUI
+import com.microsoft.device.display.samples.dualview.ui.home.DualViewApp
import com.microsoft.device.display.samples.dualview.ui.theme.DualViewComposeSampleTheme
+import com.microsoft.device.dualscreen.windowstate.WindowState
+import com.microsoft.device.dualscreen.windowstate.rememberWindowState
class MainActivity : AppCompatActivity() {
- private lateinit var windowInfoRep: WindowInfoRepository
+ private lateinit var windowState: WindowState
private lateinit var appStateViewModel: AppStateViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- windowInfoRep = windowInfoRepository()
appStateViewModel = ViewModelProvider(this).get(AppStateViewModel::class.java)
setContent {
+ windowState = rememberWindowState()
+
DualViewComposeSampleTheme {
- SetupUI(appStateViewModel, windowInfoRep)
+ DualViewApp(appStateViewModel, windowState)
}
}
}
diff --git a/DualView/src/main/java/com/microsoft/device/display/samples/dualview/ui/home/HomePage.kt b/DualView/src/main/java/com/microsoft/device/display/samples/dualview/ui/home/HomePage.kt
index a049947..afc939c 100644
--- a/DualView/src/main/java/com/microsoft/device/display/samples/dualview/ui/home/HomePage.kt
+++ b/DualView/src/main/java/com/microsoft/device/display/samples/dualview/ui/home/HomePage.kt
@@ -7,58 +7,18 @@ package com.microsoft.device.display.samples.dualview.ui.home
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowInfoRepository
import com.microsoft.device.display.samples.dualview.models.AppStateViewModel
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneLayout
-import kotlinx.coroutines.flow.collect
-
-private lateinit var appStateViewModel: AppStateViewModel
-const val SMALLEST_TABLET_SCREEN_WIDTH_DP = 585
+import com.microsoft.device.dualscreen.windowstate.WindowState
@Composable
-fun SetupUI(viewModel: AppStateViewModel, windowInfoRep: WindowInfoRepository) {
- appStateViewModel = viewModel
-
- var isAppSpanned by remember { mutableStateOf(false) }
- LaunchedEffect(windowInfoRep) {
- windowInfoRep.windowLayoutInfo
- .collect { newLayoutInfo ->
- val displayFeatures = newLayoutInfo.displayFeatures
- isAppSpanned = displayFeatures.isNotEmpty()
- var viewWidth = 0
- if (isAppSpanned) {
- val foldingFeature = displayFeatures.first() as FoldingFeature
- viewWidth = if (foldingFeature.orientation == FoldingFeature.Orientation.VERTICAL) {
- foldingFeature.bounds.left
- } else {
- foldingFeature.bounds.top
- }
- }
- appStateViewModel.viewWidth = viewWidth
- }
- }
-
- val smallestScreenWidthDp = LocalConfiguration.current.smallestScreenWidthDp
- val isTablet = smallestScreenWidthDp > SMALLEST_TABLET_SCREEN_WIDTH_DP
- val isDualScreen = isAppSpanned || isTablet
- DualScreenUI(isDualScreen)
-}
-
-@Composable
-fun DualScreenUI(isDualScreen: Boolean) {
+fun DualViewApp(viewModel: AppStateViewModel, windowState: WindowState) {
TwoPaneLayout(
- pane1 = { RestaurantViewWithTopBar(isDualScreen = isDualScreen, appStateViewModel = appStateViewModel) },
- pane2 = { MapViewWithTopBar(isDualScreen = isDualScreen, appStateViewModel = appStateViewModel) }
+ pane1 = { RestaurantViewWithTopBar(windowState.isDualScreen(), viewModel) },
+ pane2 = { MapViewWithTopBar(windowState.isDualScreen(), viewModel) }
)
}
diff --git a/ExtendedCanvas/build.gradle b/ExtendedCanvas/build.gradle
index 5e55a8e..5718d1a 100644
--- a/ExtendedCanvas/build.gradle
+++ b/ExtendedCanvas/build.gradle
@@ -35,7 +35,6 @@ dependencies {
implementation androidxDependencies.ktxCore
implementation androidxDependencies.appCompat
- implementation androidxDependencies.window
implementation composeDependencies.composeUI
implementation composeDependencies.composeRuntime
diff --git a/ListDetail/build.gradle b/ListDetail/build.gradle
index 32bfab0..850b839 100644
--- a/ListDetail/build.gradle
+++ b/ListDetail/build.gradle
@@ -35,7 +35,6 @@ dependencies {
implementation androidxDependencies.ktxCore
implementation androidxDependencies.appCompat
- implementation androidxDependencies.window
implementation composeDependencies.composeUI
implementation composeDependencies.composeRuntime
@@ -46,4 +45,5 @@ dependencies {
implementation googleDependencies.material
implementation microsoftDependencies.twoPaneLayout
+ implementation project(':WindowState')
}
\ No newline at end of file
diff --git a/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/MainActivity.kt b/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/MainActivity.kt
index b602e5b..e8de7c2 100644
--- a/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/MainActivity.kt
+++ b/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/MainActivity.kt
@@ -9,25 +9,26 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
-import androidx.window.layout.WindowInfoRepository
-import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository
import com.microsoft.device.display.samples.listdetail.models.AppStateViewModel
import com.microsoft.device.display.samples.listdetail.ui.theme.ListDetailComposeSampleTheme
-import com.microsoft.device.display.samples.listdetail.ui.view.SetupUI
+import com.microsoft.device.display.samples.listdetail.ui.view.ListDetailApp
+import com.microsoft.device.dualscreen.windowstate.WindowState
+import com.microsoft.device.dualscreen.windowstate.rememberWindowState
class MainActivity : AppCompatActivity() {
- private lateinit var windowInfoRep: WindowInfoRepository
+ private lateinit var windowState: WindowState
private lateinit var appStateViewModel: AppStateViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- windowInfoRep = windowInfoRepository()
appStateViewModel = ViewModelProvider(this).get(AppStateViewModel::class.java)
setContent {
+ windowState = rememberWindowState()
+
ListDetailComposeSampleTheme {
- SetupUI(appStateViewModel, windowInfoRep)
+ ListDetailApp(appStateViewModel, windowState)
}
}
}
diff --git a/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/ui/view/DetailView.kt b/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/ui/view/DetailView.kt
index 3cad70f..539c4c4 100644
--- a/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/ui/view/DetailView.kt
+++ b/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/ui/view/DetailView.kt
@@ -44,13 +44,13 @@ private val verticalPadding = 35.dp
private val horizontalPadding = 20.dp
@Composable
-fun DetailViewWithTopBar(isAppSpanned: Boolean, appStateViewModel: AppStateViewModel) {
+fun DetailViewWithTopBar(isDualScreen: Boolean, appStateViewModel: AppStateViewModel) {
Scaffold(
topBar = {
TopAppBar(
title = { },
navigationIcon = {
- if (!isAppSpanned) {
+ if (!isDualScreen) {
DetailViewTopBar()
}
}
diff --git a/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/ui/view/MainPage.kt b/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/ui/view/MainPage.kt
index ddc3f6c..35d61bd 100644
--- a/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/ui/view/MainPage.kt
+++ b/ListDetail/src/main/java/com/microsoft/device/display/samples/listdetail/ui/view/MainPage.kt
@@ -5,53 +5,21 @@
package com.microsoft.device.display.samples.listdetail.ui.view
-import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
-import androidx.window.layout.WindowInfoRepository
import com.microsoft.device.display.samples.listdetail.models.AppStateViewModel
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneLayout
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneMode
-import kotlinx.coroutines.flow.collect
-
-private lateinit var appStateViewModel: AppStateViewModel
-const val SMALLEST_TABLET_SCREEN_WIDTH_DP = 585
+import com.microsoft.device.dualscreen.windowstate.WindowState
@Composable
-fun SetupUI(viewModel: AppStateViewModel, windowInfoRep: WindowInfoRepository) {
- var isAppSpanned by remember { mutableStateOf(false) }
-
- LaunchedEffect(windowInfoRep) {
- windowInfoRep.windowLayoutInfo
- .collect { newLayoutInfo ->
- val displayFeatures = newLayoutInfo.displayFeatures
- isAppSpanned = displayFeatures.isNotEmpty()
- }
- }
-
- appStateViewModel = viewModel
-
- val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
- val smallestScreenWidthDp = LocalConfiguration.current.smallestScreenWidthDp
- val isTablet = smallestScreenWidthDp > SMALLEST_TABLET_SCREEN_WIDTH_DP
- val isDualScreen = (isAppSpanned || isTablet) && !isPortrait
- DualScreenUI(isDualScreen)
-}
-
-@Composable
-fun DualScreenUI(isDualScreen: Boolean) {
+fun ListDetailApp(viewModel: AppStateViewModel, windowState: WindowState) {
TwoPaneLayout(
paneMode = TwoPaneMode.HorizontalSingle,
- pane1 = { ListViewWithTopBar(appStateViewModel = appStateViewModel) },
- pane2 = { DetailViewWithTopBar(isAppSpanned = isDualScreen, appStateViewModel = appStateViewModel) }
+ pane1 = { ListViewWithTopBar(viewModel) },
+ pane2 = { DetailViewWithTopBar(windowState.isDualPortrait(), viewModel) }
)
}
diff --git a/NavigationRail/build.gradle b/NavigationRail/build.gradle
index 6b81585..967f2f1 100644
--- a/NavigationRail/build.gradle
+++ b/NavigationRail/build.gradle
@@ -41,9 +41,9 @@ dependencies {
implementation androidxDependencies.ktxCore
implementation androidxDependencies.appCompat
- implementation androidxDependencies.window
implementation microsoftDependencies.twoPaneLayout
+ implementation project(':WindowState')
implementation composeDependencies.composeMaterialForNavRail
implementation composeDependencies.composeRuntime
diff --git a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/MainActivity.kt b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/MainActivity.kt
index 9ab9213..7168090 100644
--- a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/MainActivity.kt
+++ b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/MainActivity.kt
@@ -12,27 +12,26 @@ import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.ui.unit.ExperimentalUnitApi
-import androidx.window.layout.WindowInfoRepository
-import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository
import com.microsoft.device.display.samples.navigationrail.ui.theme.ComposeSamplesTheme
-import com.microsoft.device.display.samples.navigationrail.ui.view.SetupUI
+import com.microsoft.device.display.samples.navigationrail.ui.view.NavigationRailApp
+import com.microsoft.device.dualscreen.windowstate.WindowState
+import com.microsoft.device.dualscreen.windowstate.rememberWindowState
@ExperimentalAnimationApi
@ExperimentalMaterialApi
@ExperimentalFoundationApi
@ExperimentalUnitApi
class MainActivity : AppCompatActivity() {
- private lateinit var windowInfoRep: WindowInfoRepository
+ private lateinit var windowState: WindowState
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- windowInfoRep = windowInfoRepository()
-
setContent {
+ windowState = rememberWindowState()
+
ComposeSamplesTheme {
- // Set up app UI
- SetupUI(windowInfoRep)
+ NavigationRailApp(windowState)
}
}
}
diff --git a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/components/ContentDrawer.kt b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/components/ContentDrawer.kt
index b0b81da..45ea751 100644
--- a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/components/ContentDrawer.kt
+++ b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/components/ContentDrawer.kt
@@ -50,7 +50,7 @@ private enum class DrawerState { Collapsed, Expanded }
* @param collapseHeight: height of the drawer when collpased (in dp)
* @param hingeOccludes: optional param for foldable support, indicates whether there is a hinge
* that occludes content in the current layout
- * @param hingeSize: optional param for foldable support, indicates the size of a hinge
+ * @param foldSize: optional param for foldable support, indicates the size of a fold
* @param hiddenContent: the content that will only be shown when the drawer is expanded
* @param peekContent: the content that will be shown even when the drawer is collapsed
*/
@@ -61,7 +61,7 @@ fun ContentDrawer(
expandHeight: Dp,
collapseHeight: Dp,
hingeOccludes: Boolean = false,
- hingeSize: Dp = 0.dp,
+ foldSize: Dp = 0.dp,
hiddenContent: @Composable ColumnScope.() -> Unit,
peekContent: @Composable ColumnScope.() -> Unit,
) {
@@ -81,11 +81,11 @@ fun ContentDrawer(
// Check if a spacer needs to be included to render content around an occluding hinge
val spacerHeight = if (hingeOccludes) {
val isExpanding = swipeableState.progress.to == DrawerState.Expanded
- val progressHeight = (hingeSize.value * swipeableState.progress.fraction).dp
+ val progressHeight = (foldSize.value * swipeableState.progress.fraction).dp
if (isExpanding)
progressHeight
else
- hingeSize - progressHeight
+ foldSize - progressHeight
} else {
0.dp
}
diff --git a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/HomePage.kt b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/HomePage.kt
index 246c157..42aa674 100644
--- a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/HomePage.kt
+++ b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/HomePage.kt
@@ -5,90 +5,38 @@
package com.microsoft.device.display.samples.navigationrail.ui.view
-import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.ExperimentalUnitApi
import androidx.compose.ui.unit.dp
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowInfoRepository
import com.microsoft.device.display.samples.navigationrail.models.DataProvider
import com.microsoft.device.display.samples.navigationrail.ui.components.ItemTopBar
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneLayout
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneMode
import com.microsoft.device.dualscreen.twopanelayout.navigateToPane1
import com.microsoft.device.dualscreen.twopanelayout.navigateToPane2
-import kotlinx.coroutines.flow.collect
-
-const val SMALLEST_TABLET_SCREEN_WIDTH_DP = 585
+import com.microsoft.device.dualscreen.windowstate.WindowState
@ExperimentalAnimationApi
@ExperimentalUnitApi
@ExperimentalMaterialApi
@ExperimentalFoundationApi
@Composable
-fun SetupUI(windowInfoRep: WindowInfoRepository) {
- // Create variables to track foldable device layout information
- var isAppSpanned by remember { mutableStateOf(false) }
- var isHingeVertical by remember { mutableStateOf(false) }
- var hingeSize by remember { mutableStateOf(0) }
+fun NavigationRailApp(windowState: WindowState) {
+ // Extract window state information
+ val isDualScreen = windowState.isDualScreen()
+ val isDualPortrait = windowState.isDualPortrait()
+ val isDualLandscape = windowState.isDualLandscape()
+ val foldSize = windowState.foldSize.dp
- LaunchedEffect(windowInfoRep) {
- windowInfoRep.windowLayoutInfo
- .collect { newLayoutInfo ->
- val displayFeatures = newLayoutInfo.displayFeatures
- isAppSpanned = displayFeatures.isNotEmpty()
- if (isAppSpanned) {
- val foldingFeature = displayFeatures.first() as FoldingFeature
- isHingeVertical =
- foldingFeature.orientation == FoldingFeature.Orientation.VERTICAL
- hingeSize = if (isHingeVertical)
- foldingFeature.bounds.width()
- else
- foldingFeature.bounds.height()
- }
- }
- }
-
- val smallestScreenWidthDp = LocalConfiguration.current.smallestScreenWidthDp
- val isTablet = smallestScreenWidthDp > SMALLEST_TABLET_SCREEN_WIDTH_DP
- val isDualScreen = (isAppSpanned || isTablet)
- val isDualPortrait = when {
- isAppSpanned -> isHingeVertical
- isTablet -> LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
- else -> false
- }
- val isDualLandscape = when {
- isAppSpanned -> !isHingeVertical
- isTablet -> LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
- else -> false
- }
-
- DualScreenUI(isDualScreen, isDualPortrait, isDualLandscape, hingeSize.dp)
-}
-
-@ExperimentalAnimationApi
-@ExperimentalUnitApi
-@ExperimentalFoundationApi
-@ExperimentalMaterialApi
-@Composable
-fun DualScreenUI(
- isDualScreen: Boolean,
- isDualPortrait: Boolean,
- isDualLandscape: Boolean,
- hingeSize: Dp,
-) {
// Set up starting route for navigation in pane 1
var currentRoute by rememberSaveable { mutableStateOf(navDestinations[0].route) }
val updateRoute: (String) -> Unit = { newRoute -> currentRoute = newRoute }
@@ -103,7 +51,7 @@ fun DualScreenUI(
Pane1(isDualScreen, isDualPortrait, imageId, updateImageId, currentRoute, updateRoute)
},
pane2 = {
- Pane2(isDualPortrait, isDualLandscape, hingeSize, imageId, updateImageId, currentRoute)
+ Pane2(isDualPortrait, isDualLandscape, foldSize, imageId, updateImageId, currentRoute)
},
)
@@ -137,7 +85,7 @@ fun Pane1(
fun Pane2(
isDualPortrait: Boolean,
isDualLandscape: Boolean,
- hingeSize: Dp,
+ foldSize: Dp,
imageId: Int?,
updateImageId: (Int?) -> Unit,
currentRoute: String,
@@ -152,7 +100,7 @@ fun Pane2(
}
BackHandler { if (!isDualPortrait) onBackPressed() }
- ItemDetailView(isDualPortrait, isDualLandscape, hingeSize, selectedImage, currentRoute)
+ ItemDetailView(isDualPortrait, isDualLandscape, foldSize, selectedImage, currentRoute)
// If only one pane is being displayed, show a "back" icon
if (!isDualPortrait) {
ItemTopBar(
diff --git a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/ItemDetailView.kt b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/ItemDetailView.kt
index ffbf140..bc4a1b9 100644
--- a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/ItemDetailView.kt
+++ b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/ItemDetailView.kt
@@ -26,7 +26,7 @@ import com.microsoft.device.dualscreen.twopanelayout.navigateToPane1
*
* @param isDualPortrait: true if device is in dual portrait mode
* @param isDualLandscape: true if device is in dual landscape mode
- * @param hingeSize: size of hinge in dp (0 if no hinge)
+ * @param foldSize: size of fold in dp (0 if no fold)
* @param selectedImage: currently selected image
* @param currentRoute: current route in gallery NavHost
*/
@@ -36,7 +36,7 @@ import com.microsoft.device.dualscreen.twopanelayout.navigateToPane1
fun ItemDetailView(
isDualPortrait: Boolean,
isDualLandscape: Boolean,
- hingeSize: Dp,
+ foldSize: Dp,
selectedImage: Image? = null,
currentRoute: String,
) {
@@ -61,7 +61,7 @@ fun ItemDetailView(
modifier = Modifier.align(Alignment.BottomCenter),
image = selectedImage,
isDualLandscape = isDualLandscape,
- hingeSize = hingeSize,
+ foldSize = foldSize,
gallerySection = gallerySection,
)
}
diff --git a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/ItemDetailsDrawer.kt b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/ItemDetailsDrawer.kt
index 534fbaf..2bcf4a4 100644
--- a/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/ItemDetailsDrawer.kt
+++ b/NavigationRail/src/main/java/com/microsoft/device/display/samples/navigationrail/ui/view/ItemDetailsDrawer.kt
@@ -60,7 +60,7 @@ fun BoxWithConstraintsScope.ItemDetailsDrawer(
modifier: Modifier,
image: Image,
isDualLandscape: Boolean,
- hingeSize: Dp,
+ foldSize: Dp,
gallerySection: GallerySections?,
) {
// Set max/min height for drawer based on orientation
@@ -84,7 +84,7 @@ fun BoxWithConstraintsScope.ItemDetailsDrawer(
expandHeight = expandedHeight,
collapseHeight = collapsedHeight,
hingeOccludes = isDualLandscape,
- hingeSize = hingeSize,
+ foldSize = foldSize,
hiddenContent = { ItemDetailsLong(image.details) }
) {
DrawerPill()
diff --git a/TestUtils/src/main/AndroidManifest.xml b/TestUtils/src/main/AndroidManifest.xml
index 05bf12d..cf85b36 100644
--- a/TestUtils/src/main/AndroidManifest.xml
+++ b/TestUtils/src/main/AndroidManifest.xml
@@ -1,6 +1,7 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/TwoPage/build.gradle b/TwoPage/build.gradle
index 2e9c5e0..809d219 100644
--- a/TwoPage/build.gradle
+++ b/TwoPage/build.gradle
@@ -37,13 +37,13 @@ dependencies {
implementation androidxDependencies.ktxCore
implementation androidxDependencies.appCompat
- implementation androidxDependencies.window
implementation composeDependencies.composeUI
implementation composeDependencies.composeRuntime
implementation composeDependencies.composeMaterial
implementation composeDependencies.composeUITooling
implementation composeDependencies.activityCompose
+ implementation project(':WindowState')
implementation googleDependencies.material
}
\ No newline at end of file
diff --git a/TwoPage/src/main/java/com/microsoft/device/display/samples/twopage/MainActivity.kt b/TwoPage/src/main/java/com/microsoft/device/display/samples/twopage/MainActivity.kt
index c45468d..ef1ec43 100644
--- a/TwoPage/src/main/java/com/microsoft/device/display/samples/twopage/MainActivity.kt
+++ b/TwoPage/src/main/java/com/microsoft/device/display/samples/twopage/MainActivity.kt
@@ -8,22 +8,22 @@ package com.microsoft.device.display.samples.twopage
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
-import androidx.window.layout.WindowInfoRepository
-import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository
-import com.microsoft.device.display.samples.twopage.ui.home.SetupUI
+import com.microsoft.device.display.samples.twopage.ui.home.TwoPageApp
import com.microsoft.device.display.samples.twopage.ui.theme.TwoPageComposeSamplesTheme
+import com.microsoft.device.dualscreen.windowstate.WindowState
+import com.microsoft.device.dualscreen.windowstate.rememberWindowState
class MainActivity : AppCompatActivity() {
- private lateinit var windowInfoRep: WindowInfoRepository
+ private lateinit var windowState: WindowState
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- windowInfoRep = windowInfoRepository()
-
setContent {
+ windowState = rememberWindowState()
+
TwoPageComposeSamplesTheme {
- SetupUI(windowInfoRep)
+ TwoPageApp(windowState)
}
}
}
diff --git a/TwoPage/src/main/java/com/microsoft/device/display/samples/twopage/ui/home/HomePage.kt b/TwoPage/src/main/java/com/microsoft/device/display/samples/twopage/ui/home/HomePage.kt
index 502a539..d9b4ca7 100644
--- a/TwoPage/src/main/java/com/microsoft/device/display/samples/twopage/ui/home/HomePage.kt
+++ b/TwoPage/src/main/java/com/microsoft/device/display/samples/twopage/ui/home/HomePage.kt
@@ -5,12 +5,10 @@
package com.microsoft.device.display.samples.twopage.ui.home
-import android.content.res.Configuration
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -20,64 +18,36 @@ import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowInfoRepository
import com.microsoft.device.display.samples.twopage.utils.PagerState
import com.microsoft.device.display.samples.twopage.utils.ViewPager
-import kotlinx.coroutines.flow.collect
-
-const val SMALLEST_TABLET_SCREEN_WIDTH_DP = 585
+import com.microsoft.device.dualscreen.windowstate.WindowState
@Composable
-fun SetupUI(windowInfoRep: WindowInfoRepository) {
+fun TwoPageApp(windowState: WindowState) {
val density = LocalDensity.current.density
- var isAppSpanned by remember { mutableStateOf(false) }
var viewWidth by remember { mutableStateOf(0) }
- var hingeThickness by remember { mutableStateOf(0) }
- var isHingeHorizontal by remember { mutableStateOf(false) }
- LaunchedEffect(windowInfoRep) {
- windowInfoRep.windowLayoutInfo
- .collect { newLayoutInfo ->
- val displayFeatures = newLayoutInfo.displayFeatures
- isAppSpanned = displayFeatures.isNotEmpty()
- if (isAppSpanned) {
- val foldingFeature = displayFeatures.first() as FoldingFeature
- val vWidth: Int
+ val vWidth = if (windowState.hasFold) {
+ when (windowState.isFoldHorizontal) {
+ true -> windowState.foldBounds.right
+ false -> windowState.foldBounds.left
+ }
+ } else 0
+ viewWidth = if (windowState.isDualPortrait() && !windowState.hasFold)
+ LocalConfiguration.current.screenWidthDp / 2
+ else
+ (vWidth / density).toInt()
- if (foldingFeature.orientation == FoldingFeature.Orientation.VERTICAL) {
- isHingeHorizontal = false
- vWidth = foldingFeature.bounds.left
- hingeThickness = foldingFeature.bounds.width()
- } else {
- isHingeHorizontal = true
- vWidth = foldingFeature.bounds.right
- hingeThickness = foldingFeature.bounds.height()
- }
-
- viewWidth = (vWidth / density).toInt()
- }
- }
- }
-
- val smallestScreenWidthDp = LocalConfiguration.current.smallestScreenWidthDp
- val isTablet = smallestScreenWidthDp > SMALLEST_TABLET_SCREEN_WIDTH_DP
- val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
- val isTabletDualMode = isTablet && isLandscape
- if (isTabletDualMode) {
- viewWidth = LocalConfiguration.current.screenWidthDp / 2
- }
- val isDualPortraitMode = isAppSpanned && !isHingeHorizontal
-
- val isDualScreen = (isDualPortraitMode) || isTabletDualMode
+ val isDualScreen = windowState.isDualPortrait()
val pages = setupPages(viewWidth)
- PageViews(pages, isDualScreen, hingeThickness / 2)
+ PageViews(pages, isDualScreen, windowState.foldSize / 2)
}
@Composable
fun PageViews(pages: List<@Composable () -> Unit>, isDualScreen: Boolean, pagePadding: Int) {
val maxPage = (pages.size - 1).coerceAtLeast(0)
- val pagerState: PagerState = remember { PagerState(currentPage = 0, minPage = 0, maxPage = maxPage) }
+ val pagerState: PagerState =
+ remember { PagerState(currentPage = 0, minPage = 0, maxPage = maxPage) }
pagerState.isDualMode = isDualScreen
ViewPager(
state = pagerState,
@@ -94,17 +64,9 @@ fun setupPages(width: Int): List<@Composable () -> Unit> {
.fillMaxHeight()
.clipToBounds() else Modifier.fillMaxSize()
return listOf<@Composable () -> Unit>(
- {
- FirstPage(modifier = modifier)
- },
- {
- SecondPage(modifier = modifier)
- },
- {
- ThirdPage(modifier = modifier)
- },
- {
- FourthPage(modifier = modifier)
- }
+ { FirstPage(modifier = modifier) },
+ { SecondPage(modifier = modifier) },
+ { ThirdPage(modifier = modifier) },
+ { FourthPage(modifier = modifier) }
)
}
diff --git a/WindowState/.gitignore b/WindowState/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/WindowState/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/WindowState/build.gradle b/WindowState/build.gradle
new file mode 100644
index 0000000..422836f
--- /dev/null
+++ b/WindowState/build.gradle
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ testInstrumentationRunner rootProject.ext.config.testInstrumentationRunner
+
+ // REVISIT: can uncomment if we release this
+// versionCode 1
+// versionName "1.0"
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion composeVersion
+ }
+}
+
+dependencies {
+ implementation kotlinDependencies.kotlinStdlib
+
+ implementation androidxDependencies.ktxCore
+ implementation androidxDependencies.appCompat
+ implementation androidxDependencies.window
+
+ implementation composeDependencies.composeUI
+ implementation composeDependencies.composeRuntime
+ implementation composeDependencies.composeMaterial
+ implementation composeDependencies.composeUITooling
+ implementation composeDependencies.activityCompose
+
+ testImplementation testDependencies.androidJunit
+ testImplementation testDependencies.androidxTestCore
+ testImplementation testDependencies.mockitoCore
+
+ androidTestImplementation testDependencies.androidxTestCore
+ androidTestImplementation testDependencies.androidxTestRules
+ androidTestImplementation testDependencies.androidxTestRunner
+ androidTestImplementation testDependencies.espressoCore
+}
\ No newline at end of file
diff --git a/WindowState/src/androidTest/java/com/microsoft/device/dualscreen/windowstate/WindowStateTest.kt b/WindowState/src/androidTest/java/com/microsoft/device/dualscreen/windowstate/WindowStateTest.kt
new file mode 100644
index 0000000..5059140
--- /dev/null
+++ b/WindowState/src/androidTest/java/com/microsoft/device/dualscreen/windowstate/WindowStateTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+import android.graphics.Rect
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class WindowStateTest {
+ private val hasFold = true
+ private val foldBounds = Rect(20, 20, 60, 100)
+ private val foldState = FoldState.HALF_OPENED
+ private val foldSeparates = true
+ private val foldOccludes = true
+ private val widthSizeClass = WindowSizeClass.MEDIUM
+ private val heightSizeClass = WindowSizeClass.EXPANDED
+
+ private val horizontalFold = WindowState(
+ hasFold,
+ true,
+ foldBounds,
+ foldState,
+ foldSeparates,
+ foldOccludes,
+ widthSizeClass,
+ heightSizeClass
+ )
+ private val verticalFold = WindowState(
+ hasFold,
+ false,
+ foldBounds,
+ foldState,
+ foldSeparates,
+ foldOccludes,
+ widthSizeClass,
+ heightSizeClass
+ )
+ private val noFoldLargeScreen = WindowState(
+ hasFold = false,
+ isFoldHorizontal = true,
+ foldBounds = Rect(),
+ foldState = FoldState.FLAT,
+ foldSeparates = false,
+ foldOccludes = false,
+ widthSizeClass = WindowSizeClass.EXPANDED,
+ heightSizeClass = WindowSizeClass.EXPANDED
+ )
+ private val noFoldCompact = WindowState(
+ hasFold = false,
+ isFoldHorizontal = true,
+ foldBounds = Rect(),
+ foldState = FoldState.FLAT,
+ foldSeparates = false,
+ foldOccludes = false,
+ widthSizeClass = WindowSizeClass.COMPACT,
+ heightSizeClass = WindowSizeClass.MEDIUM
+ )
+
+ @Test
+ fun returns_correct_fold_size() {
+ assertEquals(foldBounds.height(), horizontalFold.foldSize)
+ assertEquals(foldBounds.width(), verticalFold.foldSize)
+ assertEquals(0, noFoldLargeScreen.foldSize)
+ }
+
+ @Test
+ fun portrait_large_screen_returns_dual_land() {
+ assertEquals(WindowMode.DUAL_LANDSCAPE, noFoldLargeScreen.calculateWindowMode(true))
+ }
+
+ @Test
+ fun landscape_large_screen_returns_dual_port() {
+ assertEquals(WindowMode.DUAL_PORTRAIT, noFoldLargeScreen.calculateWindowMode(false))
+ }
+
+ @Test
+ fun portrait_compact_returns_single_port() {
+ assertEquals(WindowMode.SINGLE_PORTRAIT, noFoldCompact.calculateWindowMode(true))
+ }
+
+ @Test
+ fun landscape_compact_returns_single_land() {
+ assertEquals(WindowMode.SINGLE_LANDSCAPE, noFoldCompact.calculateWindowMode(false))
+ }
+
+ @Test
+ fun vertical_fold_returns_dual_port() {
+ assertEquals(WindowMode.DUAL_PORTRAIT, verticalFold.calculateWindowMode(true))
+ assertEquals(WindowMode.DUAL_PORTRAIT, verticalFold.calculateWindowMode(false))
+ }
+
+ @Test
+ fun horizontal_fold_returns_dual_land() {
+ assertEquals(WindowMode.DUAL_LANDSCAPE, horizontalFold.calculateWindowMode(true))
+ assertEquals(WindowMode.DUAL_LANDSCAPE, horizontalFold.calculateWindowMode(false))
+ }
+}
diff --git a/WindowState/src/main/AndroidManifest.xml b/WindowState/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..09d9625
--- /dev/null
+++ b/WindowState/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/Dimension.kt b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/Dimension.kt
new file mode 100644
index 0000000..314695b
--- /dev/null
+++ b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/Dimension.kt
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+enum class Dimension { WIDTH, HEIGHT }
diff --git a/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/FoldState.kt b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/FoldState.kt
new file mode 100644
index 0000000..9964c19
--- /dev/null
+++ b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/FoldState.kt
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+enum class FoldState { FLAT, HALF_OPENED }
diff --git a/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowMode.kt b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowMode.kt
new file mode 100644
index 0000000..b23a1c9
--- /dev/null
+++ b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowMode.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+/**
+ * Class that represents the different modes in which content can be displayed in a window,
+ * depending on window size and orientation
+ *
+ * Window modes:
+ * - single portrait
+ * - single landscape
+ * - dual portrait
+ * - dual landscape
+ */
+enum class WindowMode {
+ SINGLE_PORTRAIT,
+ SINGLE_LANDSCAPE,
+ DUAL_PORTRAIT,
+ DUAL_LANDSCAPE;
+
+ val isDualScreen: Boolean get() = this == DUAL_PORTRAIT || this == DUAL_LANDSCAPE
+}
diff --git a/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowSizeClass.kt b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowSizeClass.kt
new file mode 100644
index 0000000..c5fc19d
--- /dev/null
+++ b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowSizeClass.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+enum class WindowSizeClass { COMPACT, MEDIUM, EXPANDED }
+
+/**
+ * Calculates size class for a given dimension
+ *
+ * @param dimenDp: size of dimension in Dp
+ * @param dimen: which dimension is being measured (width or height)
+ */
+fun getWindowSizeClass(dimenDp: Dp, dimen: Dimension = Dimension.WIDTH): WindowSizeClass =
+ when (dimen) {
+ Dimension.WIDTH -> getSizeClass(dimenDp, 600.dp, 840.dp)
+ Dimension.HEIGHT -> getSizeClass(dimenDp, 480.dp, 900.dp)
+ }
+
+private fun getSizeClass(size: Dp, medium: Dp, expanded: Dp): WindowSizeClass = when {
+ size < 0.dp -> throw IllegalArgumentException("Dp value cannot be negative")
+ size < medium -> WindowSizeClass.COMPACT
+ size < expanded -> WindowSizeClass.MEDIUM
+ else -> WindowSizeClass.EXPANDED
+}
diff --git a/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowState.kt b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowState.kt
new file mode 100644
index 0000000..01d53d2
--- /dev/null
+++ b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowState.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+import android.content.res.Configuration
+import android.graphics.Rect
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalConfiguration
+
+/**
+ * Data class that contains foldable and large screen information extracted from the Jetpack
+ * Window Manager library
+ *
+ * @param hasFold: true if window contains a FoldingFeature,
+ * @param isFoldHorizontal: true if window contains a FoldingFeature with a horizontal orientation
+ * @param foldBounds: Rect object that describes the bound of the FoldingFeature
+ * @param foldState: state of the fold, based on state property of FoldingFeature
+ * @param foldSeparates: based on isSeparating property of FoldingFeature
+ * @param foldOccludes: true if FoldingFeature occlusion type is full
+ * @param widthSizeClass: size class (compact, medium, or expanded) for window width
+ * @param heightSizeClass: size class (compact, medium, or expanded) for window height
+ */
+data class WindowState(
+ val hasFold: Boolean,
+ val isFoldHorizontal: Boolean,
+ val foldBounds: Rect,
+ val foldState: FoldState,
+ val foldSeparates: Boolean,
+ val foldOccludes: Boolean,
+ val widthSizeClass: WindowSizeClass,
+ val heightSizeClass: WindowSizeClass,
+) {
+ private val foldableFoldSize = when (isFoldHorizontal) {
+ true -> foldBounds.height()
+ false -> foldBounds.width()
+ }
+
+ val foldSize = if (hasFold) foldableFoldSize else 0
+
+ val windowMode: WindowMode
+ @Composable get() {
+ // REVISIT: should width/height ratio be used instead of orientation?
+ val isPortrait =
+ LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
+
+ return calculateWindowMode(isPortrait)
+ }
+
+ @VisibleForTesting
+ fun calculateWindowMode(isPortrait: Boolean): WindowMode {
+ // REVISIT: should height class also be considered?
+ // Also, right now we are considering large screens + foldables mutually exclusive
+ // (which seems necessary for dualscreen apps), but we may want to think about this
+ // more and change our approach if we think there are cases where we want an app to
+ // know about both properties
+ val isLargeScreen = !hasFold && widthSizeClass == WindowSizeClass.EXPANDED
+
+ return when {
+ hasFold -> {
+ if (isFoldHorizontal)
+ WindowMode.DUAL_LANDSCAPE
+ else
+ WindowMode.DUAL_PORTRAIT
+ }
+ isLargeScreen -> {
+ if (isPortrait)
+ WindowMode.DUAL_LANDSCAPE
+ else
+ WindowMode.DUAL_PORTRAIT
+ }
+ isPortrait -> WindowMode.SINGLE_PORTRAIT
+ else -> WindowMode.SINGLE_LANDSCAPE
+ }
+ }
+
+ @Composable
+ fun isDualScreen(): Boolean {
+ return windowMode.isDualScreen
+ }
+
+ @Composable
+ fun isDualPortrait(): Boolean {
+ return windowMode == WindowMode.DUAL_PORTRAIT
+ }
+
+ @Composable
+ fun isDualLandscape(): Boolean {
+ return windowMode == WindowMode.DUAL_LANDSCAPE
+ }
+
+ @Composable
+ fun isSinglePortrait(): Boolean {
+ return windowMode == WindowMode.SINGLE_PORTRAIT
+ }
+
+ @Composable
+ fun isSingleLandscape(): Boolean {
+ return windowMode == WindowMode.SINGLE_LANDSCAPE
+ }
+}
diff --git a/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowStateHelper.kt b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowStateHelper.kt
new file mode 100644
index 0000000..eb1190f
--- /dev/null
+++ b/WindowState/src/main/java/com/microsoft/device/dualscreen/windowstate/WindowStateHelper.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+import android.app.Activity
+import android.graphics.Rect
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowInfoTracker
+import androidx.window.layout.WindowMetricsCalculator
+import kotlinx.coroutines.flow.collect
+
+@Composable
+fun Activity.rememberWindowState(): WindowState {
+ val activity = this
+ val windowInfoRepo = WindowInfoTracker.getOrCreate(activity)
+
+ var hasFold by remember { mutableStateOf(false) }
+ var isFoldHorizontal by remember { mutableStateOf(false) }
+ var foldBounds by remember { mutableStateOf(Rect()) }
+ var foldState by remember { mutableStateOf(FoldState.FLAT) }
+ var foldSeparates by remember { mutableStateOf(false) }
+ var foldOccludes by remember { mutableStateOf(false) }
+
+ LaunchedEffect(windowInfoRepo) {
+ windowInfoRepo.windowLayoutInfo(activity).collect { newLayoutInfo ->
+ hasFold = newLayoutInfo.displayFeatures.isNotEmpty()
+ if (hasFold) {
+ val fold = newLayoutInfo.displayFeatures.firstOrNull() as? FoldingFeature
+ fold?.let {
+ isFoldHorizontal = it.orientation == FoldingFeature.Orientation.HORIZONTAL
+ foldBounds = it.bounds
+ foldState = when (it.state) {
+ FoldingFeature.State.HALF_OPENED -> FoldState.HALF_OPENED
+ else -> FoldState.FLAT
+ }
+ foldSeparates = it.isSeparating
+ foldOccludes = it.occlusionType == FoldingFeature.OcclusionType.FULL
+ }
+ }
+ }
+ }
+
+ val config = LocalConfiguration.current
+ val windowMetrics = remember(config) {
+ WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this).bounds
+ }
+
+ val windowWidth = with(LocalDensity.current) { windowMetrics.width().toDp() }
+ val windowHeight = with(LocalDensity.current) { windowMetrics.height().toDp() }
+ val widthSizeClass = getWindowSizeClass(windowWidth)
+ val heightSizeClass = getWindowSizeClass(windowHeight, Dimension.HEIGHT)
+
+ return WindowState(
+ hasFold,
+ isFoldHorizontal,
+ foldBounds,
+ foldState,
+ foldSeparates,
+ foldOccludes,
+ widthSizeClass,
+ heightSizeClass
+ )
+}
diff --git a/WindowState/src/test/java/com/microsoft/device/dualscreen/windowstate/WindowModeTest.kt b/WindowState/src/test/java/com/microsoft/device/dualscreen/windowstate/WindowModeTest.kt
new file mode 100644
index 0000000..56e8158
--- /dev/null
+++ b/WindowState/src/test/java/com/microsoft/device/dualscreen/windowstate/WindowModeTest.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+import org.junit.Assert.assertFalse
+import org.junit.Test
+
+class WindowModeTest {
+ @Test
+ fun single_modes_are_not_dualscreen() {
+ assertFalse(WindowMode.SINGLE_LANDSCAPE.isDualScreen)
+ assertFalse(WindowMode.SINGLE_PORTRAIT.isDualScreen)
+ }
+
+ @Test
+ fun dual_modes_are_dualscreen() {
+ assert(WindowMode.DUAL_LANDSCAPE.isDualScreen)
+ assert(WindowMode.DUAL_PORTRAIT.isDualScreen)
+ }
+}
diff --git a/WindowState/src/test/java/com/microsoft/device/dualscreen/windowstate/WindowSizeClassTest.kt b/WindowState/src/test/java/com/microsoft/device/dualscreen/windowstate/WindowSizeClassTest.kt
new file mode 100644
index 0000000..f29f8d9
--- /dev/null
+++ b/WindowState/src/test/java/com/microsoft/device/dualscreen/windowstate/WindowSizeClassTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+package com.microsoft.device.dualscreen.windowstate
+
+import androidx.compose.ui.unit.dp
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class WindowSizeClassTest {
+ @Test
+ fun width_returns_compact() {
+ assertEquals(WindowSizeClass.COMPACT, getWindowSizeClass(500.dp, Dimension.WIDTH))
+ }
+
+ @Test
+ fun width_returns_medium() {
+ assertEquals(WindowSizeClass.MEDIUM, getWindowSizeClass(700.dp, Dimension.WIDTH))
+ }
+
+ @Test
+ fun width_returns_expanded() {
+ assertEquals(WindowSizeClass.EXPANDED, getWindowSizeClass(900.dp, Dimension.WIDTH))
+ }
+
+ @Test
+ fun height_returns_compact() {
+ assertEquals(WindowSizeClass.COMPACT, getWindowSizeClass(300.dp, Dimension.HEIGHT))
+ }
+
+ @Test
+ fun height_returns_medium() {
+ assertEquals(WindowSizeClass.MEDIUM, getWindowSizeClass(700.dp, Dimension.HEIGHT))
+ }
+
+ @Test
+ fun height_returns_expanded() {
+ assertEquals(WindowSizeClass.EXPANDED, getWindowSizeClass(1000.dp, Dimension.HEIGHT))
+ }
+}
diff --git a/dependencies.gradle b/dependencies.gradle
index 00e6c7a..905ba3b 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -4,7 +4,7 @@
*/
ext {
- gradlePluginVersion = '7.0.3'
+ gradlePluginVersion = '7.0.4'
kotlinVersion = "1.5.31"
compileSdkVersion = 31
targetSdkVersion = compileSdkVersion
@@ -23,7 +23,7 @@ ext {
// AndroidX versions
appCompatVersion = "1.3.0"
ktxCoreVersion = "1.5.0"
- windowVersion = "1.0.0-beta03"
+ windowVersion = "1.0.0-beta04"
androidxDependencies = [
appCompat : "androidx.appcompat:appcompat:$appCompatVersion",
ktxCore : "androidx.core:core-ktx:$ktxCoreVersion",
@@ -49,6 +49,8 @@ ext {
androidxTestVersion = "1.4.0"
uiAutomatorVersion = "2.2.0"
espressoVersion = "3.4.0"
+ junitVersion = "4.12"
+ mockitoVersion = "4.1.0"
testDependencies = [
androidxTestCore : "androidx.test:core:$androidxTestVersion",
androidxTestRules : "androidx.test:rules:$androidxTestVersion",
@@ -59,6 +61,8 @@ ext {
uiAutomator : "androidx.test.uiautomator:uiautomator:$uiAutomatorVersion",
windowTest : "androidx.window:window-testing:$windowVersion",
espressoCore : "androidx.test.espresso:espresso-core:$espressoVersion",
+ androidJunit : "junit:junit:$junitVersion",
+ mockitoCore : "org.mockito:mockito-core:$mockitoVersion",
]
// Google dependencies
@@ -68,7 +72,7 @@ ext {
]
// Microsoft dependencies
- twoPaneLayoutVersion = "1.0.0-alpha09"
+ twoPaneLayoutVersion = "1.0.0-alpha10"
microsoftDependencies = [
twoPaneLayout: "com.microsoft.device.dualscreen:twopanelayout:$twoPaneLayoutVersion",
]
diff --git a/settings.gradle b/settings.gradle
index 43bdcb6..75a320a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,4 +4,4 @@
*/
rootProject.name = "ComposeSamples"
-include ':ListDetail', ':CompanionPane', ':DualView', ':ExtendedCanvas', ':ComposeGallery', ':TwoPage', ':NavigationRail', ':TestUtils'
+include ':ListDetail', ':CompanionPane', ':DualView', ':ExtendedCanvas', ':ComposeGallery', ':TwoPage', ':NavigationRail', ':TestUtils', ':WindowState'