diff --git a/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt b/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt index 6e9ce6b..904842d 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt @@ -53,11 +53,10 @@ class MainActivity : ComponentActivity() { if (result.resultCode == RESULT_OK) { result.data?.data?.let { uri -> parseRecipes(this, uri) - importError = "" } } else { - importError = "Failed to import recipes (${result.resultCode})" + importError = "Import cancelled" } importFinished = true diff --git a/app/src/main/java/xyz/pixelatedw/recipe/ui/components/LoadingIndicator.kt b/app/src/main/java/xyz/pixelatedw/recipe/ui/components/LoadingIndicator.kt new file mode 100644 index 0000000..bb6f955 --- /dev/null +++ b/app/src/main/java/xyz/pixelatedw/recipe/ui/components/LoadingIndicator.kt @@ -0,0 +1,26 @@ +package xyz.pixelatedw.recipe.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.width +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun LoadingIndicator(isLoading: Boolean) { + if (!isLoading) return + + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + CircularProgressIndicator( + modifier = Modifier.width(128.dp), + ) + } +} diff --git a/app/src/main/java/xyz/pixelatedw/recipe/ui/components/MainScreen.kt b/app/src/main/java/xyz/pixelatedw/recipe/ui/components/MainScreen.kt index 2aee0e4..46be3fa 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/ui/components/MainScreen.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/ui/components/MainScreen.kt @@ -93,15 +93,15 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) { Column(modifier = Modifier.padding(padding)) { Row( modifier = Modifier - .fillMaxWidth() - .padding(8.dp), + .fillMaxWidth() + .padding(8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { OutlinedTextField( modifier = Modifier - .weight(0.7f) - .padding(end = 4.dp), + .weight(0.7f) + .padding(end = 4.dp), value = search.value.orEmpty(), onValueChange = { search -> view.setSearch(search) }, ) @@ -133,7 +133,10 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) { // Options IconButton( modifier = Modifier.weight(0.1f), - onClick = { navController.navigate("settings") }, + onClick = { + ctx.importError = "" + navController.navigate("settings") + }, ) { Icon( imageVector = Icons.Default.Settings, diff --git a/app/src/main/java/xyz/pixelatedw/recipe/ui/components/SettingsScreen.kt b/app/src/main/java/xyz/pixelatedw/recipe/ui/components/SettingsScreen.kt index a7dc46c..3c4b9cd 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/ui/components/SettingsScreen.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/ui/components/SettingsScreen.kt @@ -18,8 +18,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import xyz.pixelatedw.recipe.MainActivity import xyz.pixelatedw.recipe.utils.DEFAULT_SYNC_SERVER_IP @@ -48,6 +51,28 @@ fun SettingsScreen(ctx: MainActivity, nav: NavHostController) { ) } + val (isLoading, setLoading) = remember { mutableStateOf(false) } + + LoadingIndicator(isLoading) + + // Error text field + Row( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 128.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.Bottom + ) { + Text( + ctx.importError, + color = Color.Red, + fontSize = 32.sp, + softWrap = true, + textAlign = TextAlign.Center, + lineHeight = 32.sp + ) + } + Column( modifier = Modifier.padding(start = 12.dp, top = 48.dp), verticalArrangement = Arrangement.spacedBy(16.dp) @@ -76,13 +101,14 @@ fun SettingsScreen(ctx: MainActivity, nav: NavHostController) { horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Button(onClick = { - import(ctx, nav) -// ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) + import(ctx, nav, setLoading) }) { Text("Import") } - Button(onClick = { sync(ctx, nav) }) { + Button(onClick = { + sync(ctx, nav, setLoading) + }) { Text("Sync") } } diff --git a/app/src/main/java/xyz/pixelatedw/recipe/utils/ImportRecipes.kt b/app/src/main/java/xyz/pixelatedw/recipe/utils/ImportRecipes.kt index 57a5987..630d799 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/utils/ImportRecipes.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/utils/ImportRecipes.kt @@ -7,11 +7,13 @@ import androidx.navigation.NavHostController import xyz.pixelatedw.recipe.MainActivity import java.util.concurrent.Executors -fun import(ctx: MainActivity, nav: NavHostController) { +fun import(ctx: MainActivity, nav: NavHostController, setLoading: (Boolean) -> Unit) { val executor = Executors.newSingleThreadExecutor() val handler = Handler(Looper.getMainLooper()) executor.execute { + setLoading(true) + ctx.importError = "" ctx.importFinished = false try { @@ -20,6 +22,9 @@ fun import(ctx: MainActivity, nav: NavHostController) { // TODO This feels dirty but it doesn't block the main thread and I can't figure out a better way while(!ctx.importFinished) {} } catch (e: Exception) { + ctx.importError = when (e) { + else -> "Exception occurred: ${e.javaClass.canonicalName}" + } e.printStackTrace() } @@ -28,11 +33,9 @@ fun import(ctx: MainActivity, nav: NavHostController) { val recipes = ctx.db.recipeWithTagsDao().getAll() ctx.recipeView.setRecipes(recipes) nav.navigate("list") -// println("importing complete") - } - else { - println(ctx.importError) } + + setLoading(false) } } } diff --git a/app/src/main/java/xyz/pixelatedw/recipe/utils/SyncRecipes.kt b/app/src/main/java/xyz/pixelatedw/recipe/utils/SyncRecipes.kt index ad323a6..0c726fa 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/utils/SyncRecipes.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/utils/SyncRecipes.kt @@ -20,11 +20,14 @@ const val DEFAULT_SYNC_SERVER_IP = "192.168.0.100" const val DEFAULT_SYNC_SERVER_PORT = 9696 const val CONNECTION_TIMEOUT = 1_000 // 1 seconds -fun sync(ctx: MainActivity, nav: NavHostController) { +fun sync(ctx: MainActivity, nav: NavHostController, setLoading: (Boolean) -> Unit) { val executor = Executors.newSingleThreadExecutor() val handler = Handler(Looper.getMainLooper()) executor.execute { + setLoading(true) + ctx.importError = "" + try { val prefs = ctx.getPreferences(Context.MODE_PRIVATE) val syncServerIp = prefs.getString("syncServerIp", DEFAULT_SYNC_SERVER_IP) @@ -85,6 +88,7 @@ fun sync(ctx: MainActivity, nav: NavHostController) { // println("new file: ${newFile.absolutePath} | $contentBufLen | ${newFile.length()}") } + inputStream.close() val docDir = DocumentFile.fromFile(ctx.filesDir) parseDir(ctx, docDir, "") @@ -93,7 +97,7 @@ fun sync(ctx: MainActivity, nav: NavHostController) { } catch (e: Exception) { ctx.importError = when (e) { is SocketTimeoutException -> "Connection timed out" - else -> "Error occurred: ${e.javaClass.canonicalName}" + else -> "Exception occurred: ${e.javaClass.canonicalName}" } e.printStackTrace() } @@ -103,11 +107,9 @@ fun sync(ctx: MainActivity, nav: NavHostController) { val recipes = ctx.db.recipeWithTagsDao().getAll() ctx.recipeView.setRecipes(recipes) nav.navigate("list") -// println("syncing complete") - } - else { - println(ctx.importError) } + + setLoading(false) } } }