[#1096] Change brightness adjusting feature

* [#1096] Change brightness adjusting feature

- Closes #1096
- As reported by testers, the automatic brightness adjustment could be too invasive
- Switched to the on-demand feature after a new button click
- Tests aligned

* Align Screenshot tests

* Changelog update
This commit is contained in:
Honza Rychnovský 2023-12-07 16:35:27 +01:00 committed by GitHub
parent 201cdbbd07
commit 06ca665fca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 174 additions and 118 deletions

View File

@ -12,6 +12,10 @@ directly impact users rather than highlighting other key architectural updates.*
### Added
- Unfinished features show a "Not implemented yet" message after accessing in the app UI
### Changed
- Home and Receive screens have their Top app bar UI changed
- Automatic brightness adjustment switched to an on-demand feature after a new button is clicked on the Receive screen
### Removed
- Home screen side menu navigation was removed in favor of the Settings screen

View File

@ -1,55 +1,40 @@
package co.electriccoin.zcash.ui.screen.receive.view
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.common.LocalScreenBrightness
import co.electriccoin.zcash.ui.common.ScreenBrightness
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class ReceiveViewScreenBrightnessTest : UiTestPrerequisites() {
@get:Rule
val composeTestRule = createComposeRule()
@Test
@MediumTest
fun testFullBrightness() = runTest {
fun testBrightnessDefaultState() = runTest {
val testSetup = newTestSetup(WalletAddressFixture.unified())
assertEquals(1, testSetup.getSecureBrightnessCount())
assertEquals(0, testSetup.getScreenBrightnessCount())
}
private fun newTestSetup(walletAddress: WalletAddress) = TestSetup(composeTestRule, walletAddress)
@Test
@MediumTest
fun testBrightnessOnState() = runTest {
val testSetup = newTestSetup(WalletAddressFixture.unified())
private class TestSetup(private val composeTestRule: ComposeContentTestRule, walletAddress: WalletAddress) {
private val screenBrightness = ScreenBrightness()
assertEquals(false, testSetup.getOnAdjustBrightness())
assertEquals(0, testSetup.getScreenBrightnessCount())
fun getSecureBrightnessCount() = screenBrightness.referenceCount.value
composeTestRule.clickAdjustBrightness()
init {
composeTestRule.setContent {
CompositionLocalProvider(LocalScreenBrightness provides screenBrightness) {
ZcashTheme {
ZcashTheme {
Receive(
walletAddress,
onBack = { },
onAddressDetails = { },
)
}
}
}
}
}
assertEquals(true, testSetup.getOnAdjustBrightness())
assertEquals(1, testSetup.getScreenBrightnessCount())
}
private fun newTestSetup(walletAddress: WalletAddress) = ReceiveViewTestSetup(composeTestRule, walletAddress)
}

View File

@ -1,56 +1,40 @@
package co.electriccoin.zcash.ui.screen.receive.view
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.common.LocalScreenTimeout
import co.electriccoin.zcash.ui.common.ScreenTimeout
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class ReceiveViewScreenTimeoutTest : UiTestPrerequisites() {
@get:Rule
val composeTestRule = createComposeRule()
@Test
@MediumTest
fun testFullBrightness() = runTest {
fun testTimeoutDefaultState() = runTest {
val testSetup = newTestSetup(WalletAddressFixture.unified())
assertEquals(0, testSetup.getScreenTimeoutCount())
}
@Test
@MediumTest
fun testTimeoutOnState() = runTest {
val testSetup = newTestSetup(WalletAddressFixture.unified())
assertEquals(false, testSetup.getOnAdjustBrightness())
assertEquals(0, testSetup.getScreenTimeoutCount())
composeTestRule.clickAdjustBrightness()
assertEquals(true, testSetup.getOnAdjustBrightness())
assertEquals(1, testSetup.getScreenTimeoutCount())
}
private fun newTestSetup(walletAddress: WalletAddress) = TestSetup(composeTestRule, walletAddress)
private class TestSetup(private val composeTestRule: ComposeContentTestRule, walletAddress: WalletAddress) {
private val screenTimeout = ScreenTimeout()
fun getScreenTimeoutCount() = screenTimeout.referenceCount.value
init {
composeTestRule.setContent {
CompositionLocalProvider(LocalScreenTimeout provides screenTimeout) {
ZcashTheme {
ZcashTheme {
Receive(
walletAddress,
onBack = { },
onAddressDetails = { },
)
}
}
}
}
}
}
private fun newTestSetup(walletAddress: WalletAddress) = ReceiveViewTestSetup(composeTestRule, walletAddress)
}

View File

@ -1,6 +1,5 @@
package co.electriccoin.zcash.ui.screen.receive.view
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
@ -9,13 +8,11 @@ import androidx.test.filters.MediumTest
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.test.getStringResource
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
/*
* Note: It is difficult to test the QR code from automated tests. There is a manual test case
@ -71,37 +68,5 @@ class ReceiveViewTest {
assertEquals(1, testSetup.getOnAddressDetailsCount())
}
private fun newTestSetup(walletAddress: WalletAddress) = TestSetup(composeTestRule, walletAddress)
private class TestSetup(private val composeTestRule: ComposeContentTestRule, walletAddress: WalletAddress) {
private val onBackCount = AtomicInteger(0)
private val onAddressDetailsCount = AtomicInteger(0)
fun getOnBackCount(): Int {
composeTestRule.waitForIdle()
return onBackCount.get()
}
fun getOnAddressDetailsCount(): Int {
composeTestRule.waitForIdle()
return onAddressDetailsCount.get()
}
init {
composeTestRule.setContent {
ZcashTheme {
Receive(
walletAddress,
onBack = {
onBackCount.getAndIncrement()
},
onAddressDetails = {
onAddressDetailsCount.getAndIncrement()
}
)
}
}
}
}
private fun newTestSetup(walletAddress: WalletAddress) = ReceiveViewTestSetup(composeTestRule, walletAddress)
}

View File

@ -0,0 +1,78 @@
package co.electriccoin.zcash.ui.screen.receive.view
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
import cash.z.ecc.android.sdk.model.WalletAddress
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.LocalScreenBrightness
import co.electriccoin.zcash.ui.common.LocalScreenTimeout
import co.electriccoin.zcash.ui.common.ScreenBrightness
import co.electriccoin.zcash.ui.common.ScreenTimeout
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.test.getStringResource
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
class ReceiveViewTestSetup(
private val composeTestRule: ComposeContentTestRule,
walletAddress: WalletAddress
) {
private val onBackCount = AtomicInteger(0)
private val onAddressDetailsCount = AtomicInteger(0)
private val screenBrightness = ScreenBrightness()
private val screenTimeout = ScreenTimeout()
private val onAdjustBrightness = AtomicBoolean(false)
fun getScreenBrightnessCount() = screenBrightness.referenceCount.value
fun getScreenTimeoutCount() = screenTimeout.referenceCount.value
fun getOnAdjustBrightness(): Boolean {
composeTestRule.waitForIdle()
return onAdjustBrightness.get()
}
fun getOnBackCount(): Int {
composeTestRule.waitForIdle()
return onBackCount.get()
}
fun getOnAddressDetailsCount(): Int {
composeTestRule.waitForIdle()
return onAddressDetailsCount.get()
}
init {
composeTestRule.setContent {
CompositionLocalProvider(
LocalScreenBrightness provides screenBrightness,
LocalScreenTimeout provides screenTimeout
) {
ZcashTheme {
ZcashTheme {
Receive(
walletAddress,
onBack = {
onBackCount.getAndIncrement()
},
onAddressDetails = {
onAddressDetailsCount.getAndIncrement()
},
onAdjustBrightness = {
onAdjustBrightness.getAndSet(it)
},
)
}
}
}
}
}
}
fun ComposeContentTestRule.clickAdjustBrightness() {
onNodeWithContentDescription(getStringResource(R.string.receive_brightness_content_description)).also {
it.performClick()
}
}

View File

@ -17,7 +17,7 @@ class ScreenBrightness {
mutableReferenceCount.update { it + 1 }
}
fun restore() {
fun restoreBrightness() {
val after = mutableReferenceCount.updateAndGet { it - 1 }
if (after < 0) {
@ -34,6 +34,6 @@ fun BrightenScreen() {
val screenBrightness = LocalScreenBrightness.current
DisposableEffect(screenBrightness) {
screenBrightness.fullBrightness()
onDispose { screenBrightness.restore() }
onDispose { screenBrightness.restoreBrightness() }
}
}

View File

@ -55,6 +55,7 @@ internal fun WrapReceive(
walletAddresses.unified,
onBack = onBack,
onAddressDetails = onAddressDetails,
onAdjustBrightness = { /* Just for testing */ }
)
}
}

View File

@ -10,14 +10,15 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material.icons.filled.BrightnessHigh
import androidx.compose.material.icons.filled.BrightnessLow
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
@ -35,6 +36,7 @@ import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.screen.receive.util.AndroidQrCodeImageGenerator
import co.electriccoin.zcash.ui.screen.receive.util.JvmQrCodeGenerator
@ -50,24 +52,35 @@ private fun ComposablePreview() {
walletAddress = runBlocking { WalletAddressFixture.unified() },
onBack = {},
onAddressDetails = {},
onAdjustBrightness = {},
)
}
}
}
@Composable
@Suppress("LongParameterList")
fun Receive(
walletAddress: WalletAddress,
onBack: () -> Unit,
onAddressDetails: () -> Unit,
onAdjustBrightness: (Boolean) -> Unit,
) {
val (brightness, setBrightness) = rememberSaveable { mutableStateOf(false) }
// Rework this into Scaffold
Column {
ReceiveTopAppBar(onBack = onBack)
ReceiveTopAppBar(
adjustBrightness = brightness,
onBack = onBack,
onBrightness = {
onAdjustBrightness(!brightness)
setBrightness(!brightness)
}
)
ReceiveContents(
walletAddress = walletAddress,
onAddressDetails = onAddressDetails,
adjustBrightness = brightness,
modifier = Modifier
.fillMaxHeight()
.verticalScroll(rememberScrollState())
@ -81,17 +94,27 @@ fun Receive(
}
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun ReceiveTopAppBar(onBack: () -> Unit) {
TopAppBar(
title = { Text(text = stringResource(id = R.string.receive_title)) },
navigationIcon = {
private fun ReceiveTopAppBar(
adjustBrightness: Boolean,
onBack: () -> Unit,
onBrightness: () -> Unit
) {
SmallTopAppBar(
titleText = stringResource(id = R.string.receive_title),
backText = stringResource(id = R.string.receive_back),
backContentDescriptionText = stringResource(id = R.string.receive_back_content_description),
onBack = onBack,
regularActions = {
IconButton(
onClick = onBack
onClick = onBrightness
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(R.string.receive_back_content_description)
imageVector = if (adjustBrightness) {
Icons.Default.BrightnessLow
} else {
Icons.Default.BrightnessHigh
},
contentDescription = stringResource(R.string.receive_brightness_content_description)
)
}
}
@ -101,17 +124,22 @@ private fun ReceiveTopAppBar(onBack: () -> Unit) {
private val DEFAULT_QR_CODE_SIZE = 320.dp
@Composable
@Suppress("LongParameterList")
private fun ReceiveContents(
walletAddress: WalletAddress,
onAddressDetails: () -> Unit,
modifier: Modifier = Modifier
adjustBrightness: Boolean,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
QrCode(data = walletAddress.address, DEFAULT_QR_CODE_SIZE, Modifier.align(Alignment.CenterHorizontally))
QrCode(
data = walletAddress.address,
size = DEFAULT_QR_CODE_SIZE,
adjustBrightness = adjustBrightness,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
@ -151,10 +179,18 @@ private fun ReceiveContents(
}
@Composable
private fun QrCode(data: String, size: Dp, modifier: Modifier = Modifier) {
private fun QrCode(
data: String,
size: Dp,
modifier: Modifier = Modifier,
adjustBrightness: Boolean = false,
) {
Column(modifier = modifier) {
BrightenScreen()
DisableScreenTimeout()
if (adjustBrightness) {
BrightenScreen()
DisableScreenTimeout()
}
val sizePixels = with(LocalDensity.current) { size.toPx() }.roundToInt()
// In the future, use actual/expect to switch QR code generator implementations for multiplatform

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="receive_title">Receive</string>
<string name="receive_back">Back</string>
<string name="receive_back_content_description">Back</string>
<string name="receive_brightness_content_description">Adjust brightness</string>
<string name="receive_qr_code_content_description">QR code for address</string>
<string name="receive_caption">Your Address</string>
<string name="receive_see_address_details">See Address Details</string>

View File

@ -155,6 +155,7 @@ class ScreenshotTest : UiTestPrerequisites() {
),
ignoreCase = true
).also {
it.performScrollTo()
it.assertExists()
it.performClick()
}
@ -408,7 +409,7 @@ private fun receiveZecScreenshots(
composeTestRule.activity.walletViewModel.addresses.value != null
}
composeTestRule.onNode(hasText(resContext.getString(R.string.receive_title))).also {
composeTestRule.onNode(hasText(resContext.getString(R.string.receive_title), ignoreCase = true)).also {
it.assertExists()
}