Multi-shade foundation - data layer (1/5).
Early foundation for the multi-shade framework. Data layer for the multi-shade foundation. Includes cross-layer shared models, a proxy for sending touch events into the framework, and a repository to store and retrieve application state. Bug: 272130181 Test: includes unit tests for code in this layer. Tested end-to-end in the last CL in this chain. Change-Id: Ic1dfd8deb0bdde95288d824f91986e4749810ca2
This commit is contained in:
committed by
Ale Nijamkin
parent
fb818bedbe
commit
00b49c070a
@@ -844,4 +844,44 @@
|
||||
|
||||
<!-- Configuration to set Learn more in device logs as URL link -->
|
||||
<bool name="log_access_confirmation_learn_more_as_link">true</bool>
|
||||
|
||||
<!-- [START] MULTI SHADE -->
|
||||
<!-- Whether the device should use dual shade. If false, the device uses single shade. -->
|
||||
<bool name="dual_shade_enabled">true</bool>
|
||||
<!--
|
||||
When in dual shade, where should the horizontal split be on the screen to help determine whether
|
||||
the user is pulling down the left shade or the right shade. Must be between 0.0 and 1.0,
|
||||
inclusive. In other words: how much of the left-hand side of the screen, when pulled down on,
|
||||
would reveal the left-hand side shade.
|
||||
|
||||
More concretely:
|
||||
A value of 0.67 means that the left two-thirds of the screen are dedicated to the left-hand side
|
||||
shade and the remaining one-third of the screen on the right is dedicated to the right-hand side
|
||||
shade.
|
||||
-->
|
||||
<dimen name="dual_shade_split_fraction">0.67</dimen>
|
||||
<!-- Width of the left-hand side shade. -->
|
||||
<dimen name="left_shade_width">436dp</dimen>
|
||||
<!-- Width of the right-hand side shade. -->
|
||||
<dimen name="right_shade_width">436dp</dimen>
|
||||
<!--
|
||||
Opaque version of the scrim that shows up behind dual shades. The alpha channel is driven
|
||||
programmatically.
|
||||
-->
|
||||
<color name="opaque_scrim">#D9D9D9</color>
|
||||
<!-- Maximum opacity when the scrim that shows up behind the dual shades is fully visible. -->
|
||||
<dimen name="dual_shade_scrim_alpha">0.1</dimen>
|
||||
<!--
|
||||
The amount that the user must swipe down when the shade is fully collapsed to automatically
|
||||
expand once the user lets go of the shade. If the user swipes less than this amount, the shade
|
||||
will automatically revert back to fully collapsed once the user stops swiping.
|
||||
-->
|
||||
<dimen name="shade_swipe_expand_threshold">0.5</dimen>
|
||||
<!--
|
||||
The amount that the user must swipe up when the shade is fully expanded to automatically
|
||||
collapse once the user lets go of the shade. If the user swipes less than this amount, the shade
|
||||
will automatically revert back to fully expanded once the user stops swiping.
|
||||
-->
|
||||
<dimen name="shade_swipe_collapse_threshold">0.5</dimen>
|
||||
<!-- [END] MULTI SHADE -->
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.multishade.data.model
|
||||
|
||||
import com.android.systemui.multishade.shared.model.ShadeId
|
||||
|
||||
/** Models the current interaction with one of the shades. */
|
||||
data class MultiShadeInteractionModel(
|
||||
/** The ID of the shade that the user is currently interacting with. */
|
||||
val shadeId: ShadeId,
|
||||
/** Whether the interaction is proxied (as in: coming from an external app or different UI). */
|
||||
val isProxied: Boolean,
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.multishade.data.remoteproxy
|
||||
|
||||
import com.android.systemui.multishade.shared.model.ProxiedInputModel
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
||||
/**
|
||||
* Acts as a hub for routing proxied user input into the multi shade system.
|
||||
*
|
||||
* "Proxied" user input is coming through a proxy; typically from an external app or different UI.
|
||||
* In other words: it's not user input that's occurring directly on the shade UI itself. This class
|
||||
* is that proxy.
|
||||
*/
|
||||
@Singleton
|
||||
class MultiShadeInputProxy @Inject constructor() {
|
||||
private val _proxiedTouch =
|
||||
MutableSharedFlow<ProxiedInputModel>(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
val proxiedInput: Flow<ProxiedInputModel> = _proxiedTouch.asSharedFlow()
|
||||
|
||||
fun onProxiedInput(proxiedInput: ProxiedInputModel) {
|
||||
_proxiedTouch.tryEmit(proxiedInput)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.multishade.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.FloatRange
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
|
||||
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
|
||||
import com.android.systemui.multishade.shared.model.ProxiedInputModel
|
||||
import com.android.systemui.multishade.shared.model.ShadeConfig
|
||||
import com.android.systemui.multishade.shared.model.ShadeId
|
||||
import com.android.systemui.multishade.shared.model.ShadeModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/** Encapsulates application state for all shades. */
|
||||
@SysUISingleton
|
||||
class MultiShadeRepository
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val applicationContext: Context,
|
||||
inputProxy: MultiShadeInputProxy,
|
||||
) {
|
||||
/**
|
||||
* Remote input coming from sources outside of system UI (for example, swiping down on the
|
||||
* Launcher or from the status bar).
|
||||
*/
|
||||
val proxiedInput: Flow<ProxiedInputModel> = inputProxy.proxiedInput
|
||||
|
||||
/** Width of the left-hand side shade, in pixels. */
|
||||
private val leftShadeWidthPx =
|
||||
applicationContext.resources.getDimensionPixelSize(R.dimen.left_shade_width)
|
||||
|
||||
/** Width of the right-hand side shade, in pixels. */
|
||||
private val rightShadeWidthPx =
|
||||
applicationContext.resources.getDimensionPixelSize(R.dimen.right_shade_width)
|
||||
|
||||
/**
|
||||
* The amount that the user must swipe up when the shade is fully expanded to automatically
|
||||
* collapse once the user lets go of the shade. If the user swipes less than this amount, the
|
||||
* shade will automatically revert back to fully expanded once the user stops swiping.
|
||||
*
|
||||
* This is a fraction between `0` and `1`.
|
||||
*/
|
||||
private val swipeCollapseThreshold =
|
||||
checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_collapse_threshold))
|
||||
|
||||
/**
|
||||
* The amount that the user must swipe down when the shade is fully collapsed to automatically
|
||||
* expand once the user lets go of the shade. If the user swipes less than this amount, the
|
||||
* shade will automatically revert back to fully collapsed once the user stops swiping.
|
||||
*
|
||||
* This is a fraction between `0` and `1`.
|
||||
*/
|
||||
private val swipeExpandThreshold =
|
||||
checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_expand_threshold))
|
||||
|
||||
/**
|
||||
* Maximum opacity when the scrim that shows up behind the dual shades is fully visible.
|
||||
*
|
||||
* This is a fraction between `0` and `1`.
|
||||
*/
|
||||
private val dualShadeScrimAlpha =
|
||||
checkInBounds(applicationContext.resources.getFloat(R.dimen.dual_shade_scrim_alpha))
|
||||
|
||||
/** The current configuration of the shade system. */
|
||||
val shadeConfig: StateFlow<ShadeConfig> =
|
||||
MutableStateFlow(
|
||||
if (applicationContext.resources.getBoolean(R.bool.dual_shade_enabled)) {
|
||||
ShadeConfig.DualShadeConfig(
|
||||
leftShadeWidthPx = leftShadeWidthPx,
|
||||
rightShadeWidthPx = rightShadeWidthPx,
|
||||
swipeCollapseThreshold = swipeCollapseThreshold,
|
||||
swipeExpandThreshold = swipeExpandThreshold,
|
||||
splitFraction =
|
||||
applicationContext.resources.getFloat(
|
||||
R.dimen.dual_shade_split_fraction
|
||||
),
|
||||
scrimAlpha = dualShadeScrimAlpha,
|
||||
)
|
||||
} else {
|
||||
ShadeConfig.SingleShadeConfig(
|
||||
swipeCollapseThreshold = swipeCollapseThreshold,
|
||||
swipeExpandThreshold = swipeExpandThreshold,
|
||||
)
|
||||
}
|
||||
)
|
||||
.asStateFlow()
|
||||
|
||||
private val _forceCollapseAll = MutableStateFlow(false)
|
||||
/** Whether all shades should be collapsed. */
|
||||
val forceCollapseAll: StateFlow<Boolean> = _forceCollapseAll.asStateFlow()
|
||||
|
||||
private val _shadeInteraction = MutableStateFlow<MultiShadeInteractionModel?>(null)
|
||||
/** The current shade interaction or `null` if no shade is interacted with currently. */
|
||||
val shadeInteraction: StateFlow<MultiShadeInteractionModel?> = _shadeInteraction.asStateFlow()
|
||||
|
||||
private val stateByShade = mutableMapOf<ShadeId, MutableStateFlow<ShadeModel>>()
|
||||
|
||||
/** The model for the shade with the given ID. */
|
||||
fun getShade(
|
||||
shadeId: ShadeId,
|
||||
): StateFlow<ShadeModel> {
|
||||
return getMutableShade(shadeId).asStateFlow()
|
||||
}
|
||||
|
||||
/** Sets the expansion amount for the shade with the given ID. */
|
||||
fun setExpansion(
|
||||
shadeId: ShadeId,
|
||||
@FloatRange(from = 0.0, to = 1.0) expansion: Float,
|
||||
) {
|
||||
getMutableShade(shadeId).let { mutableState ->
|
||||
mutableState.value = mutableState.value.copy(expansion = expansion)
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets whether all shades should be immediately forced to collapse. */
|
||||
fun setForceCollapseAll(isForced: Boolean) {
|
||||
_forceCollapseAll.value = isForced
|
||||
}
|
||||
|
||||
/** Sets the current shade interaction; use `null` if no shade is interacted with currently. */
|
||||
fun setShadeInteraction(shadeInteraction: MultiShadeInteractionModel?) {
|
||||
_shadeInteraction.value = shadeInteraction
|
||||
}
|
||||
|
||||
private fun getMutableShade(id: ShadeId): MutableStateFlow<ShadeModel> {
|
||||
return stateByShade.getOrPut(id) { MutableStateFlow(ShadeModel(id)) }
|
||||
}
|
||||
|
||||
/** Asserts that the given [Float] is in the range of `0` and `1`, inclusive. */
|
||||
private fun checkInBounds(float: Float): Float {
|
||||
check(float in 0f..1f) { "$float isn't between 0 and 1." }
|
||||
return float
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.multishade.shared.model
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
|
||||
/**
|
||||
* Models a part of an ongoing proxied user input gesture.
|
||||
*
|
||||
* "Proxied" user input is coming through a proxy; typically from an external app or different UI.
|
||||
* In other words: it's not user input that's occurring directly on the shade UI itself.
|
||||
*/
|
||||
sealed class ProxiedInputModel {
|
||||
/** The user is dragging their pointer. */
|
||||
data class OnDrag(
|
||||
/**
|
||||
* The relative position of the pointer as a fraction of its container width where `0` is
|
||||
* all the way to the left and `1` is all the way to the right.
|
||||
*/
|
||||
@FloatRange(from = 0.0, to = 1.0) val xFraction: Float,
|
||||
/** The amount that the pointer was dragged, in pixels. */
|
||||
val yDragAmountPx: Float,
|
||||
) : ProxiedInputModel()
|
||||
|
||||
/** The user finished dragging by lifting up their pointer. */
|
||||
object OnDragEnd : ProxiedInputModel()
|
||||
|
||||
/**
|
||||
* The drag gesture has been canceled. Usually because the pointer exited the draggable area.
|
||||
*/
|
||||
object OnDragCancel : ProxiedInputModel()
|
||||
|
||||
/** The user has tapped (clicked). */
|
||||
object OnTap : ProxiedInputModel()
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.multishade.shared.model
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
|
||||
/** Enumerates the various possible configurations of the shade system. */
|
||||
sealed class ShadeConfig(
|
||||
|
||||
/** IDs of the shade(s) in this configuration. */
|
||||
open val shadeIds: List<ShadeId>,
|
||||
|
||||
/**
|
||||
* The amount that the user must swipe up when the shade is fully expanded to automatically
|
||||
* collapse once the user lets go of the shade. If the user swipes less than this amount, the
|
||||
* shade will automatically revert back to fully expanded once the user stops swiping.
|
||||
*/
|
||||
@FloatRange(from = 0.0, to = 1.0) open val swipeCollapseThreshold: Float,
|
||||
|
||||
/**
|
||||
* The amount that the user must swipe down when the shade is fully collapsed to automatically
|
||||
* expand once the user lets go of the shade. If the user swipes less than this amount, the
|
||||
* shade will automatically revert back to fully collapsed once the user stops swiping.
|
||||
*/
|
||||
@FloatRange(from = 0.0, to = 1.0) open val swipeExpandThreshold: Float,
|
||||
) {
|
||||
|
||||
/** There is a single shade. */
|
||||
data class SingleShadeConfig(
|
||||
@FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
|
||||
@FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
|
||||
) :
|
||||
ShadeConfig(
|
||||
shadeIds = listOf(ShadeId.SINGLE),
|
||||
swipeCollapseThreshold = swipeCollapseThreshold,
|
||||
swipeExpandThreshold = swipeExpandThreshold,
|
||||
)
|
||||
|
||||
/** There are two shades arranged side-by-side. */
|
||||
data class DualShadeConfig(
|
||||
/** Width of the left-hand side shade. */
|
||||
val leftShadeWidthPx: Int,
|
||||
/** Width of the right-hand side shade. */
|
||||
val rightShadeWidthPx: Int,
|
||||
@FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
|
||||
@FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
|
||||
/**
|
||||
* The position of the "split" between interaction areas for each of the shades, as a
|
||||
* fraction of the width of the container.
|
||||
*
|
||||
* Interactions that occur on the start-side (left-hand side in left-to-right languages like
|
||||
* English) affect the start-side shade. Interactions that occur on the end-side (right-hand
|
||||
* side in left-to-right languages like English) affect the end-side shade.
|
||||
*/
|
||||
@FloatRange(from = 0.0, to = 1.0) val splitFraction: Float,
|
||||
/** Maximum opacity when the scrim that shows up behind the dual shades is fully visible. */
|
||||
@FloatRange(from = 0.0, to = 1.0) val scrimAlpha: Float,
|
||||
) :
|
||||
ShadeConfig(
|
||||
shadeIds = listOf(ShadeId.LEFT, ShadeId.RIGHT),
|
||||
swipeCollapseThreshold = swipeCollapseThreshold,
|
||||
swipeExpandThreshold = swipeExpandThreshold,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.multishade.shared.model
|
||||
|
||||
/** Enumerates all known shade IDs. */
|
||||
enum class ShadeId {
|
||||
/** ID of the shade on the left in dual shade configurations. */
|
||||
LEFT,
|
||||
/** ID of the shade on the right in dual shade configurations. */
|
||||
RIGHT,
|
||||
/** ID of the single shade in single shade configurations. */
|
||||
SINGLE,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.multishade.shared.model
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
|
||||
/** Models the current state of a shade. */
|
||||
data class ShadeModel(
|
||||
val id: ShadeId,
|
||||
@FloatRange(from = 0.0, to = 1.0) val expansion: Float = 0f,
|
||||
)
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.multishade.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
|
||||
import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
|
||||
import com.android.systemui.multishade.shared.model.ProxiedInputModel
|
||||
import com.android.systemui.multishade.shared.model.ShadeConfig
|
||||
import com.android.systemui.multishade.shared.model.ShadeId
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.google.common.truth.Truth.assertWithMessage
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class MultiShadeRepositoryTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var inputProxy: MultiShadeInputProxy
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
inputProxy = MultiShadeInputProxy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun proxiedInput() = runTest {
|
||||
val underTest = create()
|
||||
val latest: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
|
||||
|
||||
assertWithMessage("proxiedInput should start with null").that(latest).isNull()
|
||||
|
||||
inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
|
||||
assertThat(latest).isEqualTo(ProxiedInputModel.OnTap)
|
||||
|
||||
inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 100f))
|
||||
assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 100f))
|
||||
|
||||
inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 120f))
|
||||
assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 120f))
|
||||
|
||||
inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
|
||||
assertThat(latest).isEqualTo(ProxiedInputModel.OnDragEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shadeConfig_dualShadeEnabled() = runTest {
|
||||
overrideResource(R.bool.dual_shade_enabled, true)
|
||||
val underTest = create()
|
||||
val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
|
||||
|
||||
assertThat(shadeConfig).isInstanceOf(ShadeConfig.DualShadeConfig::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shadeConfig_dualShadeNotEnabled() = runTest {
|
||||
overrideResource(R.bool.dual_shade_enabled, false)
|
||||
val underTest = create()
|
||||
val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
|
||||
|
||||
assertThat(shadeConfig).isInstanceOf(ShadeConfig.SingleShadeConfig::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun forceCollapseAll() = runTest {
|
||||
val underTest = create()
|
||||
val forceCollapseAll: Boolean? by collectLastValue(underTest.forceCollapseAll)
|
||||
|
||||
assertWithMessage("forceCollapseAll should start as false!")
|
||||
.that(forceCollapseAll)
|
||||
.isFalse()
|
||||
|
||||
underTest.setForceCollapseAll(true)
|
||||
assertThat(forceCollapseAll).isTrue()
|
||||
|
||||
underTest.setForceCollapseAll(false)
|
||||
assertThat(forceCollapseAll).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun shadeInteraction() = runTest {
|
||||
val underTest = create()
|
||||
val shadeInteraction: MultiShadeInteractionModel? by
|
||||
collectLastValue(underTest.shadeInteraction)
|
||||
|
||||
assertWithMessage("shadeInteraction should start as null!").that(shadeInteraction).isNull()
|
||||
|
||||
underTest.setShadeInteraction(
|
||||
MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false)
|
||||
)
|
||||
assertThat(shadeInteraction)
|
||||
.isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false))
|
||||
|
||||
underTest.setShadeInteraction(
|
||||
MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true)
|
||||
)
|
||||
assertThat(shadeInteraction)
|
||||
.isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true))
|
||||
|
||||
underTest.setShadeInteraction(null)
|
||||
assertThat(shadeInteraction).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun expansion() = runTest {
|
||||
val underTest = create()
|
||||
val leftExpansion: Float? by
|
||||
collectLastValue(underTest.getShade(ShadeId.LEFT).map { it.expansion })
|
||||
val rightExpansion: Float? by
|
||||
collectLastValue(underTest.getShade(ShadeId.RIGHT).map { it.expansion })
|
||||
val singleExpansion: Float? by
|
||||
collectLastValue(underTest.getShade(ShadeId.SINGLE).map { it.expansion })
|
||||
|
||||
assertWithMessage("expansion should start as 0!").that(leftExpansion).isZero()
|
||||
assertWithMessage("expansion should start as 0!").that(rightExpansion).isZero()
|
||||
assertWithMessage("expansion should start as 0!").that(singleExpansion).isZero()
|
||||
|
||||
underTest.setExpansion(
|
||||
shadeId = ShadeId.LEFT,
|
||||
0.4f,
|
||||
)
|
||||
assertThat(leftExpansion).isEqualTo(0.4f)
|
||||
assertThat(rightExpansion).isEqualTo(0f)
|
||||
assertThat(singleExpansion).isEqualTo(0f)
|
||||
|
||||
underTest.setExpansion(
|
||||
shadeId = ShadeId.RIGHT,
|
||||
0.73f,
|
||||
)
|
||||
assertThat(leftExpansion).isEqualTo(0.4f)
|
||||
assertThat(rightExpansion).isEqualTo(0.73f)
|
||||
assertThat(singleExpansion).isEqualTo(0f)
|
||||
|
||||
underTest.setExpansion(
|
||||
shadeId = ShadeId.LEFT,
|
||||
0.1f,
|
||||
)
|
||||
underTest.setExpansion(
|
||||
shadeId = ShadeId.SINGLE,
|
||||
0.88f,
|
||||
)
|
||||
assertThat(leftExpansion).isEqualTo(0.1f)
|
||||
assertThat(rightExpansion).isEqualTo(0.73f)
|
||||
assertThat(singleExpansion).isEqualTo(0.88f)
|
||||
}
|
||||
|
||||
private fun create(): MultiShadeRepository {
|
||||
return create(
|
||||
context = context,
|
||||
inputProxy = inputProxy,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(
|
||||
context: Context,
|
||||
inputProxy: MultiShadeInputProxy,
|
||||
): MultiShadeRepository {
|
||||
return MultiShadeRepository(
|
||||
applicationContext = context,
|
||||
inputProxy = inputProxy,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user