Remove bad usage of ElementSurface

This commit is contained in:
Benoit Marty 2023-01-26 18:01:27 +01:00 committed by Benoit Marty
parent 8d87320531
commit 10763dcecc
5 changed files with 384 additions and 407 deletions

View file

@ -18,6 +18,7 @@ package io.element.android.x
import android.os.Bundle import android.os.Bundle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@ -25,7 +26,6 @@ import androidx.core.view.WindowCompat
import com.bumble.appyx.core.integration.NodeHost import com.bumble.appyx.core.integration.NodeHost
import com.bumble.appyx.core.integrationpoint.NodeComponentActivity import com.bumble.appyx.core.integrationpoint.NodeComponentActivity
import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.designsystem.theme.components.ElementSurface
import io.element.android.libraries.designsystem.theme.ElementTheme import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.x.di.AppBindings import io.element.android.x.di.AppBindings
@ -41,7 +41,7 @@ class MainActivity : NodeComponentActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
setContent { setContent {
ElementTheme { ElementTheme {
ElementSurface( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) { ) {
NodeHost(integrationPoint = appyxIntegrationPoint) { NodeHost(integrationPoint = appyxIntegrationPoint) {

View file

@ -33,9 +33,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -58,7 +56,6 @@ import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.designsystem.theme.components.ElementButton import io.element.android.libraries.designsystem.theme.components.ElementButton
import io.element.android.libraries.designsystem.theme.components.ElementCircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.ElementCircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.ElementOutlinedTextField import io.element.android.libraries.designsystem.theme.components.ElementOutlinedTextField
import io.element.android.libraries.designsystem.theme.components.ElementSurface
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
@ -68,118 +65,114 @@ fun ChangeServerView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onChangeServerSuccess: () -> Unit = {}, onChangeServerSuccess: () -> Unit = {},
) { ) {
ElementSurface( val eventSink = state.eventSink
modifier = modifier, val scrollState = rememberScrollState()
Box(
modifier = modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding()
) { ) {
val eventSink = state.eventSink Column(
val scrollState = rememberScrollState()
Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .verticalScroll(
.systemBarsPadding() state = scrollState,
.imePadding() )
.padding(horizontal = 16.dp)
) { ) {
Column( val isError = state.changeServerAction is Async.Failure
Box(
modifier = Modifier modifier = Modifier
.verticalScroll( .padding(top = 99.dp)
state = scrollState, .size(width = 81.dp, height = 73.dp)
.align(Alignment.CenterHorizontally)
.background(
color = ElementTheme.colors.surfaceVariant,
shape = RoundedCornerShape(32.dp)
) )
.padding(horizontal = 16.dp)
) { ) {
val isError = state.changeServerAction is Async.Failure VectorIcon(
Box(
modifier = Modifier modifier = Modifier
.padding(top = 99.dp) .align(Alignment.Center)
.size(width = 81.dp, height = 73.dp) .size(width = 48.dp, height = 48.dp),
.align(Alignment.CenterHorizontally) // TODO Update with design input
.background( resourceId = R.drawable.ic_baseline_dataset_24,
color = ElementTheme.colors.surfaceVariant,
shape = RoundedCornerShape(32.dp)
)
) {
VectorIcon(
modifier = Modifier
.align(Alignment.Center)
.size(width = 48.dp, height = 48.dp),
// TODO Update with design input
resourceId = R.drawable.ic_baseline_dataset_24,
)
}
Text(
text = "Your server",
modifier = Modifier
.fillMaxWidth()
.defaultMinSize(minHeight = 56.dp)
.align(Alignment.CenterHorizontally)
.padding(top = 38.dp),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 24.sp,
) )
Text( }
text = "A server is a home for all your data.\n" + Text(
"You choose your server and its easy to make one.", // TODO "Learn more.", text = "Your server",
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.CenterHorizontally) .defaultMinSize(minHeight = 56.dp)
.padding(top = 16.dp), .align(Alignment.CenterHorizontally)
textAlign = TextAlign.Center, .padding(top = 38.dp),
fontSize = 16.sp, textAlign = TextAlign.Center,
color = ElementTheme.colors.secondary fontWeight = FontWeight.Bold,
fontSize = 24.sp,
)
Text(
text = "A server is a home for all your data.\n" +
"You choose your server and its easy to make one.", // TODO "Learn more.",
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally)
.padding(top = 16.dp),
textAlign = TextAlign.Center,
fontSize = 16.sp,
color = ElementTheme.colors.secondary
)
var homeserverFieldState by textFieldState(stateValue = state.homeserver)
ElementOutlinedTextField(
value = homeserverFieldState,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.changeServerServer)
.padding(top = 200.dp),
onValueChange = {
homeserverFieldState = it
eventSink(ChangeServerEvents.SetServer(it))
},
label = {
Text(text = "Server")
},
isError = isError,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done,
),
keyboardActions = KeyboardActions(
onDone = { eventSink(ChangeServerEvents.Submit) }
) )
var homeserverFieldState by textFieldState(stateValue = state.homeserver) )
ElementOutlinedTextField( if (state.changeServerAction is Async.Failure) {
value = homeserverFieldState, Text(
modifier = Modifier text = changeServerError(
.fillMaxWidth() state.homeserver,
.testTag(TestTags.changeServerServer) state.changeServerAction.error
.padding(top = 200.dp),
onValueChange = {
homeserverFieldState = it
eventSink(ChangeServerEvents.SetServer(it))
},
label = {
Text(text = "Server")
},
isError = isError,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done,
), ),
keyboardActions = KeyboardActions( color = ElementTheme.colors.error,
onDone = { eventSink(ChangeServerEvents.Submit) } style = ElementTheme.typography.bodySmall,
) modifier = Modifier.padding(start = 16.dp)
)
if (state.changeServerAction is Async.Failure) {
Text(
text = changeServerError(
state.homeserver,
state.changeServerAction.error
),
color = ElementTheme.colors.error,
style = ElementTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp)
)
}
ElementButton(
onClick = { eventSink(ChangeServerEvents.Submit) },
enabled = state.submitEnabled,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.changeServerContinue)
.padding(top = 44.dp)
) {
Text(text = "Continue")
}
if (state.changeServerAction is Async.Success) {
onChangeServerSuccess()
}
}
if (state.changeServerAction is Async.Loading) {
ElementCircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
) )
} }
ElementButton(
onClick = { eventSink(ChangeServerEvents.Submit) },
enabled = state.submitEnabled,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.changeServerContinue)
.padding(top = 44.dp)
) {
Text(text = "Continue")
}
if (state.changeServerAction is Async.Success) {
onChangeServerSuccess()
}
}
if (state.changeServerAction is Async.Loading) {
ElementCircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
} }
} }
} }

View file

@ -18,6 +18,7 @@
package io.element.android.features.login.root package io.element.android.features.login.root
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -59,7 +60,6 @@ import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.designsystem.theme.components.ElementButton import io.element.android.libraries.designsystem.theme.components.ElementButton
import io.element.android.libraries.designsystem.theme.components.ElementCircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.ElementCircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.ElementOutlinedTextField import io.element.android.libraries.designsystem.theme.components.ElementOutlinedTextField
import io.element.android.libraries.designsystem.theme.components.ElementSurface
import io.element.android.libraries.matrix.core.SessionId import io.element.android.libraries.matrix.core.SessionId
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
@ -73,153 +73,149 @@ fun LoginRootScreen(
onLoginWithSuccess: (SessionId) -> Unit = {}, onLoginWithSuccess: (SessionId) -> Unit = {},
) { ) {
val eventSink = state.eventSink val eventSink = state.eventSink
ElementSurface( Box(
modifier = modifier, modifier = modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding()
) { ) {
Box( val scrollState = rememberScrollState()
var loginFieldState by textFieldState(stateValue = state.formState.login)
var passwordFieldState by textFieldState(stateValue = state.formState.password)
Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .verticalScroll(
.systemBarsPadding() state = scrollState,
.imePadding() )
.padding(horizontal = 16.dp),
) { ) {
val scrollState = rememberScrollState() val isError = state.loggedInState is LoggedInState.ErrorLoggingIn
var loginFieldState by textFieldState(stateValue = state.formState.login) // Title
var passwordFieldState by textFieldState(stateValue = state.formState.password) Text(
text = stringResource(id = StringR.string.ftue_auth_welcome_back_title),
Column(
modifier = Modifier modifier = Modifier
.verticalScroll( .fillMaxWidth()
state = scrollState, .padding(horizontal = 16.dp, vertical = 48.dp),
) textAlign = TextAlign.Center,
.padding(horizontal = 16.dp), fontWeight = FontWeight.Bold,
fontSize = 24.sp,
)
// Form
Column(
// modifier = Modifier.weight(1f),
) { ) {
val isError = state.loggedInState is LoggedInState.ErrorLoggingIn Box(
// Title modifier = Modifier.fillMaxWidth()
Text(
text = stringResource(id = StringR.string.ftue_auth_welcome_back_title),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 48.dp),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 24.sp,
)
// Form
Column(
// modifier = Modifier.weight(1f),
) { ) {
Box(
modifier = Modifier.fillMaxWidth()
) {
ElementOutlinedTextField(
value = state.homeserver,
modifier = Modifier.fillMaxWidth(),
onValueChange = { /* no op */ },
enabled = false,
label = {
Text(text = "Server")
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Uri,
),
)
ElementButton(
onClick = onChangeServer,
modifier = Modifier
.align(Alignment.CenterEnd)
.testTag(TestTags.loginChangeServer)
.padding(top = 8.dp, end = 8.dp),
content = {
Text(text = "Change")
}
)
}
ElementOutlinedTextField( ElementOutlinedTextField(
value = loginFieldState, value = state.homeserver,
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxWidth() onValueChange = { /* no op */ },
.testTag(TestTags.loginEmailUsername) enabled = false,
.padding(top = 60.dp),
label = { label = {
Text(text = stringResource(id = StringR.string.login_signin_username_hint)) Text(text = "Server")
},
onValueChange = {
loginFieldState = it
eventSink(LoginRootEvents.SetLogin(it))
}, },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email, keyboardType = KeyboardType.Uri,
imeAction = ImeAction.Next
), ),
) )
var passwordVisible by remember { mutableStateOf(false) } ElementButton(
if (state.loggedInState is LoggedInState.LoggingIn) { onClick = onChangeServer,
// Ensure password is hidden when user submits the form
passwordVisible = false
}
ElementOutlinedTextField(
value = passwordFieldState,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .align(Alignment.CenterEnd)
.testTag(TestTags.loginPassword) .testTag(TestTags.loginChangeServer)
.padding(top = 24.dp), .padding(top = 8.dp, end = 8.dp),
onValueChange = { content = {
passwordFieldState = it Text(text = "Change")
eventSink(LoginRootEvents.SetPassword(it)) }
}, )
label = { }
Text(text = "Password") ElementOutlinedTextField(
}, value = loginFieldState,
isError = isError, modifier = Modifier
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), .fillMaxWidth()
trailingIcon = { .testTag(TestTags.loginEmailUsername)
val image = .padding(top = 60.dp),
if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff label = {
val description = Text(text = stringResource(id = StringR.string.login_signin_username_hint))
if (passwordVisible) "Hide password" else "Show password" },
onValueChange = {
loginFieldState = it
eventSink(LoginRootEvents.SetLogin(it))
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
)
var passwordVisible by remember { mutableStateOf(false) }
if (state.loggedInState is LoggedInState.LoggingIn) {
// Ensure password is hidden when user submits the form
passwordVisible = false
}
ElementOutlinedTextField(
value = passwordFieldState,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginPassword)
.padding(top = 24.dp),
onValueChange = {
passwordFieldState = it
eventSink(LoginRootEvents.SetPassword(it))
},
label = {
Text(text = "Password")
},
isError = isError,
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val image =
if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
val description =
if (passwordVisible) "Hide password" else "Show password"
IconButton(onClick = { passwordVisible = !passwordVisible }) { IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, description) Icon(imageVector = image, description)
} }
}, },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password, keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done, imeAction = ImeAction.Done,
), ),
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
onDone = { eventSink(LoginRootEvents.Submit) } onDone = { eventSink(LoginRootEvents.Submit) }
), ),
)
if (state.loggedInState is LoggedInState.ErrorLoggingIn) {
Text(
text = loginError(state.formState, state.loggedInState.failure),
color = ElementTheme.colors.error,
style = ElementTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp)
)
}
}
// Submit
ElementButton(
onClick = { eventSink(LoginRootEvents.Submit) },
enabled = state.submitEnabled,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginContinue)
.padding(vertical = 32.dp)
) {
Text(text = "Continue")
}
when (val loggedInState = state.loggedInState) {
is LoggedInState.LoggedIn -> onLoginWithSuccess(loggedInState.sessionId)
else -> Unit
}
}
if (state.loggedInState is LoggedInState.LoggingIn) {
ElementCircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
) )
if (state.loggedInState is LoggedInState.ErrorLoggingIn) {
Text(
text = loginError(state.formState, state.loggedInState.failure),
color = ElementTheme.colors.error,
style = ElementTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp)
)
}
} }
// Submit
ElementButton(
onClick = { eventSink(LoginRootEvents.Submit) },
enabled = state.submitEnabled,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginContinue)
.padding(vertical = 32.dp)
) {
Text(text = "Continue")
}
when (val loggedInState = state.loggedInState) {
is LoggedInState.LoggedIn -> onLoginWithSuccess(loggedInState.sessionId)
else -> Unit
}
}
if (state.loggedInState is LoggedInState.LoggingIn) {
ElementCircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
} }
} }
} }

View file

@ -47,9 +47,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.HorizontalPagerIndicator import com.google.accompanist.pager.HorizontalPagerIndicator
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.designsystem.theme.components.ElementButton import io.element.android.libraries.designsystem.theme.components.ElementButton
import io.element.android.libraries.designsystem.theme.components.ElementSurface
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -67,60 +65,55 @@ fun OnBoardingScreen(
val carrouselState = remember { SplashCarouselStateFactory().create() } val carrouselState = remember { SplashCarouselStateFactory().create() }
val nbOfPages = carrouselState.items.size val nbOfPages = carrouselState.items.size
var key by remember { mutableStateOf(false) } var key by remember { mutableStateOf(false) }
ElementSurface( Box(
modifier = modifier, modifier = modifier
color = ElementTheme.colors.background, .fillMaxSize()
.systemBarsPadding()
.padding(vertical = 16.dp)
) { ) {
Box( Column(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize()
.systemBarsPadding()
.padding(vertical = 16.dp)
) { ) {
Column( val pagerState = rememberPagerState()
modifier = Modifier.fillMaxSize(), LaunchedEffect(key) {
launch {
delay(3_000)
pagerState.animateScrollToPage((pagerState.currentPage + 1) % nbOfPages)
// https://stackoverflow.com/questions/73714228/accompanist-pager-animatescrolltopage-doesnt-scroll-to-next-page-correctly
key = !key
}
}
LaunchedEffect(pagerState) {
// Collect from the pager state a snapshotFlow reading the currentPage
snapshotFlow { pagerState.currentPage }.collect { page ->
onPageChanged(page)
}
}
HorizontalPager(
modifier = Modifier.weight(1f),
count = nbOfPages,
state = pagerState,
) { page ->
// Our page content
OnBoardingPage(carrouselState.items[page])
}
HorizontalPagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(CenterHorizontally)
.padding(16.dp),
)
ElementButton(
onClick = {
onSignIn()
},
enabled = true,
modifier = Modifier
.align(CenterHorizontally)
.testTag(TestTags.onBoardingSignIn)
.padding(top = 16.dp)
) { ) {
val pagerState = rememberPagerState() Text(text = stringResource(id = StringR.string.login_splash_submit))
LaunchedEffect(key) {
launch {
delay(3_000)
pagerState.animateScrollToPage((pagerState.currentPage + 1) % nbOfPages)
// https://stackoverflow.com/questions/73714228/accompanist-pager-animatescrolltopage-doesnt-scroll-to-next-page-correctly
key = !key
}
}
LaunchedEffect(pagerState) {
// Collect from the pager state a snapshotFlow reading the currentPage
snapshotFlow { pagerState.currentPage }.collect { page ->
onPageChanged(page)
}
}
HorizontalPager(
modifier = Modifier.weight(1f),
count = nbOfPages,
state = pagerState,
) { page ->
// Our page content
OnBoardingPage(carrouselState.items[page])
}
HorizontalPagerIndicator(
pagerState = pagerState,
modifier = Modifier
.align(CenterHorizontally)
.padding(16.dp),
)
ElementButton(
onClick = {
onSignIn()
},
enabled = true,
modifier = Modifier
.align(CenterHorizontally)
.testTag(TestTags.onBoardingSignIn)
.padding(top = 16.dp)
) {
Text(text = stringResource(id = StringR.string.login_splash_submit))
}
} }
} }
} }

View file

@ -51,7 +51,6 @@ import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.theme.components.ElementButton import io.element.android.libraries.designsystem.theme.components.ElementButton
import io.element.android.libraries.designsystem.theme.components.ElementCircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.ElementCircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.ElementOutlinedTextField import io.element.android.libraries.designsystem.theme.components.ElementOutlinedTextField
import io.element.android.libraries.designsystem.theme.components.ElementSurface
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.R as StringR
@ -70,139 +69,135 @@ fun BugReportView(
} }
return return
} }
ElementSurface( Box(
modifier = modifier, modifier = modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding()
) { ) {
Box( val scrollState = rememberScrollState()
Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .verticalScroll(
.systemBarsPadding() state = scrollState,
.imePadding() )
.padding(horizontal = 16.dp),
) { ) {
val scrollState = rememberScrollState() val isError = state.sending is Async.Failure
Column( val isFormEnabled = state.sending !is Async.Loading
// Title
Text(
text = stringResource(id = StringR.string.send_bug_report),
modifier = Modifier modifier = Modifier
.verticalScroll( .fillMaxWidth()
state = scrollState, .padding(horizontal = 16.dp, vertical = 16.dp),
) textAlign = TextAlign.Center,
.padding(horizontal = 16.dp), fontWeight = FontWeight.Bold,
fontSize = 24.sp,
)
// Form
Text(
text = stringResource(id = StringR.string.send_bug_report_description),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp),
fontSize = 16.sp,
)
var descriptionFieldState by textFieldState(
stateValue = state.formState.description
)
Column(
// modifier = Modifier.weight(1f),
) { ) {
val isError = state.sending is Async.Failure ElementOutlinedTextField(
val isFormEnabled = state.sending !is Async.Loading value = descriptionFieldState,
// Title
Text(
text = stringResource(id = StringR.string.send_bug_report),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp), .padding(top = 16.dp),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 24.sp,
)
// Form
Text(
text = stringResource(id = StringR.string.send_bug_report_description),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp),
fontSize = 16.sp,
)
var descriptionFieldState by textFieldState(
stateValue = state.formState.description
)
Column(
// modifier = Modifier.weight(1f),
) {
ElementOutlinedTextField(
value = descriptionFieldState,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
enabled = isFormEnabled,
label = {
Text(text = stringResource(id = StringR.string.send_bug_report_placeholder))
},
supportingText = {
Text(text = stringResource(id = StringR.string.send_bug_report_description_in_english))
},
onValueChange = {
descriptionFieldState = it
eventSink(BugReportEvents.SetDescription(it))
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next
),
// TODO Error text too short
)
}
LabelledCheckbox(
checked = state.formState.sendLogs,
onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) },
enabled = isFormEnabled, enabled = isFormEnabled,
text = stringResource(id = StringR.string.send_bug_report_include_logs) label = {
Text(text = stringResource(id = StringR.string.send_bug_report_placeholder))
},
supportingText = {
Text(text = stringResource(id = StringR.string.send_bug_report_description_in_english))
},
onValueChange = {
descriptionFieldState = it
eventSink(BugReportEvents.SetDescription(it))
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next
),
// TODO Error text too short
) )
if (state.hasCrashLogs) { }
LabelledCheckbox( LabelledCheckbox(
checked = state.formState.sendCrashLogs, checked = state.formState.sendLogs,
onCheckedChange = { eventSink(BugReportEvents.SetSendCrashLog(it)) }, onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) },
enabled = isFormEnabled, enabled = isFormEnabled,
text = stringResource(id = StringR.string.send_bug_report_include_crash_logs) text = stringResource(id = StringR.string.send_bug_report_include_logs)
) )
} if (state.hasCrashLogs) {
LabelledCheckbox( LabelledCheckbox(
checked = state.formState.canContact, checked = state.formState.sendCrashLogs,
onCheckedChange = { eventSink(BugReportEvents.SetCanContact(it)) }, onCheckedChange = { eventSink(BugReportEvents.SetSendCrashLog(it)) },
enabled = isFormEnabled, enabled = isFormEnabled,
text = stringResource(id = StringR.string.you_may_contact_me) text = stringResource(id = StringR.string.send_bug_report_include_crash_logs)
) )
if (state.screenshotUri != null) { }
LabelledCheckbox( LabelledCheckbox(
checked = state.formState.sendScreenshot, checked = state.formState.canContact,
onCheckedChange = { eventSink(BugReportEvents.SetSendScreenshot(it)) }, onCheckedChange = { eventSink(BugReportEvents.SetCanContact(it)) },
enabled = isFormEnabled, enabled = isFormEnabled,
text = stringResource(id = StringR.string.send_bug_report_include_screenshot) text = stringResource(id = StringR.string.you_may_contact_me)
) )
if (state.formState.sendScreenshot) { if (state.screenshotUri != null) {
Box( LabelledCheckbox(
modifier = Modifier.fillMaxWidth(), checked = state.formState.sendScreenshot,
contentAlignment = Alignment.Center onCheckedChange = { eventSink(BugReportEvents.SetSendScreenshot(it)) },
) { enabled = isFormEnabled,
val context = LocalContext.current text = stringResource(id = StringR.string.send_bug_report_include_screenshot)
val model = ImageRequest.Builder(context) )
.data(state.screenshotUri) if (state.formState.sendScreenshot) {
.build() Box(
AsyncImage( modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(fraction = 0.5f), contentAlignment = Alignment.Center
model = model, ) {
contentDescription = null val context = LocalContext.current
) val model = ImageRequest.Builder(context)
} .data(state.screenshotUri)
.build()
AsyncImage(
modifier = Modifier.fillMaxWidth(fraction = 0.5f),
model = model,
contentDescription = null
)
} }
} }
// Submit
ElementButton(
onClick = { eventSink(BugReportEvents.SendBugReport) },
enabled = state.submitEnabled,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 32.dp)
) {
Text(text = stringResource(id = StringR.string.action_send))
}
} }
when (state.sending) { // Submit
is Async.Loading -> { ElementButton(
ElementCircularProgressIndicator( onClick = { eventSink(BugReportEvents.SendBugReport) },
progress = state.sendingProgress, enabled = state.submitEnabled,
modifier = Modifier.align(Alignment.Center) modifier = Modifier
) .fillMaxWidth()
} .padding(vertical = 32.dp)
is Async.Failure -> ErrorDialog( ) {
content = state.sending.error.toString(), Text(text = stringResource(id = StringR.string.action_send))
}
}
when (state.sending) {
is Async.Loading -> {
ElementCircularProgressIndicator(
progress = state.sendingProgress,
modifier = Modifier.align(Alignment.Center)
) )
else -> Unit
} }
is Async.Failure -> ErrorDialog(
content = state.sending.error.toString(),
)
else -> Unit
} }
} }
} }