Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More error handling #196

Merged
merged 6 commits into from
Oct 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ package app.dapk.st.core.extensions
inline fun <T> T?.ifNull(block: () -> T): T = this ?: block()
inline fun <T> ifOrNull(condition: Boolean, block: () -> T): T? = if (condition) block() else null

inline fun <reified T> Any.takeAs(): T? {
return when (this) {
is T -> this
else -> null
}
}

@Suppress("UNCHECKED_CAST")
inline fun <T, T1 : T, T2 : T> Iterable<T>.firstOrNull(predicate: (T) -> Boolean, predicate2: (T) -> Boolean): Pair<T1, T2>? {
var firstValue: T1? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
package app.dapk.st.design.components

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun GenericError(retryAction: () -> Unit) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
fun GenericError(message: String = "Something went wrong...", label: String = "Retry", cause: Throwable? = null, action: () -> Unit) {
val moreDetails = cause?.let { "${it::class.java.simpleName}: ${it.message}" }

val openDetailsDialog = remember { mutableStateOf(false) }
if (openDetailsDialog.value) {
AlertDialog(
onDismissRequest = { openDetailsDialog.value = false },
confirmButton = {
Button(onClick = { openDetailsDialog.value = false }) {
Text("OK")
}
},
title = { Text("Details") },
text = {
Text(moreDetails!!)
}
)
}
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Something went wrong...")
Button(onClick = { retryAction() }) {
Text("Retry")
Text(message)
if (moreDetails != null) {
Text("Tap for more details".uppercase(), fontSize = 12.sp, modifier = Modifier.clickable { openDetailsDialog.value = true }.padding(12.dp))
}
Spacer(modifier = Modifier.height(12.dp))
Button(onClick = { action() }) {
Text(label.uppercase())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,27 @@ fun <T : Any> Spider(currentPage: SpiderPage<T>, onNavigate: (SpiderPage<out T>?
val pageCache = remember { mutableMapOf<Route<*>, SpiderPage<out T>>() }
pageCache[currentPage.route] = currentPage

val navigateAndPopStack = {
pageCache.remove(currentPage.route)
onNavigate(pageCache[currentPage.parent])
}
val itemScope = object : SpiderItemScope {
override fun goBack() {
navigateAndPopStack()
}
}

val computedWeb = remember(true) {
mutableMapOf<Route<*>, @Composable (T) -> Unit>().also { computedWeb ->
val scope = object : SpiderScope {
override fun <T> item(route: Route<T>, content: @Composable (T) -> Unit) {
computedWeb[route] = { content(it as T) }
override fun <T> item(route: Route<T>, content: @Composable SpiderItemScope.(T) -> Unit) {
computedWeb[route] = { content(itemScope, it as T) }
}
}
graph.invoke(scope)
}
}

val navigateAndPopStack = {
pageCache.remove(currentPage.route)
onNavigate(pageCache[currentPage.parent])
}

Column {
if (currentPage.hasToolbar) {
Toolbar(
Expand All @@ -40,7 +45,11 @@ fun <T : Any> Spider(currentPage: SpiderPage<T>, onNavigate: (SpiderPage<out T>?


interface SpiderScope {
fun <T> item(route: Route<T>, content: @Composable (T) -> Unit)
fun <T> item(route: Route<T>, content: @Composable SpiderItemScope.(T) -> Unit)
}

interface SpiderItemScope {
fun goBack()
}

data class SpiderPage<T>(
Expand Down
1 change: 1 addition & 0 deletions features/login/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ dependencies {
implementation project(":matrix:services:auth")
implementation project(":matrix:services:profile")
implementation project(":matrix:services:crypto")
implementation project(":design-library")
implementation project(":core")
}
41 changes: 4 additions & 37 deletions features/login/src/main/kotlin/app/dapk/st/login/LoginScreen.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package app.dapk.st.login

import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
Expand Down Expand Up @@ -32,6 +31,8 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import app.dapk.st.core.StartObserving
import app.dapk.st.core.components.CenteredLoading
import app.dapk.st.design.components.GenericError
import app.dapk.st.login.LoginEvent.LoginComplete
import app.dapk.st.login.LoginScreenState.*

Expand All @@ -49,42 +50,8 @@ fun LoginScreen(loginViewModel: LoginViewModel, onLoggedIn: () -> Unit) {
val keyboardController = LocalSoftwareKeyboardController.current

when (val state = loginViewModel.state) {
is Error -> {
val openDetailsDialog = remember { mutableStateOf(false) }

if (openDetailsDialog.value) {
AlertDialog(
onDismissRequest = { openDetailsDialog.value = false },
confirmButton = {
Button(onClick = { openDetailsDialog.value = false }) {
Text("OK")
}
},
title = { Text("Details") },
text = {
Text(state.cause.message ?: "Unknown")
}
)
}
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Something went wrong")
Text("Tap for more details".uppercase(), fontSize = 12.sp, modifier = Modifier.clickable { openDetailsDialog.value = true }.padding(12.dp))
Spacer(modifier = Modifier.height(12.dp))
Button(onClick = {
loginViewModel.start()
}) {
Text("Retry".uppercase())
}
}
}
}

Loading -> {
Box(contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
is Error -> GenericError(cause = state.cause, action = { loginViewModel.start() })
Loading -> CenteredLoading()

is Content ->
Row {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ import app.dapk.st.core.LifecycleEffect
import app.dapk.st.core.StartObserving
import app.dapk.st.core.components.CenteredLoading
import app.dapk.st.core.extensions.takeIfContent
import app.dapk.st.design.components.MessengerUrlIcon
import app.dapk.st.design.components.MissingAvatarIcon
import app.dapk.st.design.components.SmallTalkTheme
import app.dapk.st.design.components.Toolbar
import app.dapk.st.design.components.*
import app.dapk.st.matrix.common.RoomId
import app.dapk.st.matrix.common.UserId
import app.dapk.st.matrix.sync.MessageMeta
Expand Down Expand Up @@ -95,7 +92,7 @@ internal fun MessengerScreen(
})
when (state.composerState) {
is ComposerState.Text -> {
Room(state.roomState, replyActions)
Room(state.roomState, replyActions, onRetry = { viewModel.post(MessengerAction.OnMessengerVisible(roomId, attachments)) })
TextComposer(
state.composerState,
onTextChange = { viewModel.post(MessengerAction.ComposerTextUpdate(it)) },
Expand Down Expand Up @@ -132,7 +129,7 @@ private fun MessengerViewModel.ObserveEvents(galleryLauncher: ActivityResultLaun
}

@Composable
private fun ColumnScope.Room(roomStateLce: Lce<MessengerState>, replyActions: ReplyActions) {
private fun ColumnScope.Room(roomStateLce: Lce<MessengerState>, replyActions: ReplyActions, onRetry: () -> Unit) {
when (val state = roomStateLce) {
is Lce.Loading -> CenteredLoading()
is Lce.Content -> {
Expand Down Expand Up @@ -165,16 +162,7 @@ private fun ColumnScope.Room(roomStateLce: Lce<MessengerState>, replyActions: Re
}
}

is Lce.Error -> {
Box(contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Something went wrong...")
Button(onClick = {}) {
Text("Retry")
}
}
}
}
is Lce.Error -> GenericError(cause = state.cause, action = onRetry)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.lifecycleScope
import app.dapk.st.core.*
import app.dapk.st.core.extensions.unsafeLazy
import app.dapk.st.design.components.GenericError
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize

Expand Down Expand Up @@ -59,7 +60,10 @@ fun Activity.PermissionGuard(state: State<Lce<PermissionResult>>, onGranted: @Co
PermissionResult.ShowRational -> finish()
}

is Lce.Error -> finish()
is Lce.Error -> GenericError(message = "Store permission required", label = "Close") {
finish()
}

is Lce.Loading -> {
// loading should be quick, let's avoid displaying anything
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,17 @@ fun ImageGalleryScreen(viewModel: ImageGalleryViewModel, onTopLevelBack: () -> U

Spider(currentPage = viewModel.state.page, onNavigate = onNavigate) {
item(ImageGalleryPage.Routes.folders) {
ImageGalleryFolders(it) { folder ->
viewModel.selectFolder(folder)
}
ImageGalleryFolders(it, onClick = { viewModel.selectFolder(it) }, onRetry = { viewModel.start() })
}
item(ImageGalleryPage.Routes.files) {
ImageGalleryMedia(it, onImageSelected)
ImageGalleryMedia(it, onImageSelected, onRetry = { viewModel.selectFolder(it.folder) })
}
}

}


@Composable
fun ImageGalleryFolders(state: ImageGalleryPage.Folders, onClick: (Folder) -> Unit) {
fun ImageGalleryFolders(state: ImageGalleryPage.Folders, onClick: (Folder) -> Unit, onRetry: () -> Unit) {
val screenWidth = LocalConfiguration.current.screenWidthDp

val gradient = Brush.verticalGradient(
Expand Down Expand Up @@ -106,12 +103,12 @@ fun ImageGalleryFolders(state: ImageGalleryPage.Folders, onClick: (Folder) -> Un
}
}

is Lce.Error -> GenericError { }
is Lce.Error -> GenericError(cause = content.cause, action = onRetry)
}
}

@Composable
fun ImageGalleryMedia(state: ImageGalleryPage.Files, onFileSelected: (Media) -> Unit) {
fun ImageGalleryMedia(state: ImageGalleryPage.Files, onFileSelected: (Media) -> Unit, onRetry: () -> Unit) {
val screenWidth = LocalConfiguration.current.screenWidthDp

Column {
Expand Down Expand Up @@ -149,7 +146,7 @@ fun ImageGalleryMedia(state: ImageGalleryPage.Files, onFileSelected: (Media) ->
}
}

is Lce.Error -> GenericError { }
is Lce.Error -> GenericError(cause = content.cause, action = onRetry)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class ImageGalleryViewModel(
route = ImageGalleryPage.Routes.files,
label = page.label,
parent = ImageGalleryPage.Routes.folders,
state = ImageGalleryPage.Files(Lce.Loading())
state = ImageGalleryPage.Files(Lce.Loading(), folder)
)
)
}
Expand Down Expand Up @@ -78,7 +78,7 @@ data class ImageGalleryState(

sealed interface ImageGalleryPage {
data class Folders(val content: Lce<List<Folder>>) : ImageGalleryPage
data class Files(val content: Lce<List<Media>>) : ImageGalleryPage
data class Files(val content: Lce<List<Media>>, val folder: Folder) : ImageGalleryPage

object Routes {
val folders = Route<Folders>("Folders")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private fun ProfilePage(context: Context, viewModel: ProfileViewModel, profile:
}

@Composable
private fun Invitations(viewModel: ProfileViewModel, invitations: Page.Invitations) {
private fun SpiderItemScope.Invitations(viewModel: ProfileViewModel, invitations: Page.Invitations) {
when (val state = invitations.content) {
is Lce.Loading -> CenteredLoading()
is Lce.Content -> {
Expand Down Expand Up @@ -147,7 +147,7 @@ private fun Invitations(viewModel: ProfileViewModel, invitations: Page.Invitatio
}
}

is Lce.Error -> TODO()
is Lce.Error -> GenericError(label = "Go back", cause = state.cause) { goBack() }
}
}

Expand Down
Loading