diff --git a/.ideaBackup/.gitignore b/.ideaBackup/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.ideaBackup/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.ideaBackup/.name b/.ideaBackup/.name
new file mode 100644
index 0000000..1e12bee
--- /dev/null
+++ b/.ideaBackup/.name
@@ -0,0 +1 @@
+EnglishBender
\ No newline at end of file
diff --git a/.ideaBackup/appInsightsSettings.xml b/.ideaBackup/appInsightsSettings.xml
new file mode 100644
index 0000000..371f2e2
--- /dev/null
+++ b/.ideaBackup/appInsightsSettings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.ideaBackup/compiler.xml b/.ideaBackup/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/.ideaBackup/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.ideaBackup/deploymentTargetDropDown.xml b/.ideaBackup/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..b1d56a7
--- /dev/null
+++ b/.ideaBackup/deploymentTargetDropDown.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.ideaBackup/gradle.xml b/.ideaBackup/gradle.xml
new file mode 100644
index 0000000..ca5fba8
--- /dev/null
+++ b/.ideaBackup/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.ideaBackup/inspectionProfiles/Project_Default.xml b/.ideaBackup/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..44ca2d9
--- /dev/null
+++ b/.ideaBackup/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.ideaBackup/kotlinc.xml b/.ideaBackup/kotlinc.xml
new file mode 100644
index 0000000..ae3f30a
--- /dev/null
+++ b/.ideaBackup/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.ideaBackup/migrations.xml b/.ideaBackup/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.ideaBackup/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.ideaBackup/misc.xml b/.ideaBackup/misc.xml
new file mode 100644
index 0000000..8978d23
--- /dev/null
+++ b/.ideaBackup/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.ideaBackup/vcs.xml b/.ideaBackup/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.ideaBackup/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts
index 4db9b6f..b2c89a6 100644
--- a/androidApp/build.gradle.kts
+++ b/androidApp/build.gradle.kts
@@ -18,7 +18,8 @@ android {
}
composeOptions {
// kotlinCompilerExtensionVersion = "1.4.0"
- kotlinCompilerExtensionVersion = "1.4.8"
+// kotlinCompilerExtensionVersion = "1.4.8"
+ kotlinCompilerExtensionVersion = "1.5.9"
}
packagingOptions {
resources {
@@ -50,8 +51,8 @@ android {
// }
}
-val composeVersion = "1.6.0"
-val material3Version = "1.1.2"
+val composeVersion = "1.6.1"
+val material3Version = "1.2.0"
val pagingRuntimeVersion = "3.1.1"
val pagingComposeVersion = "1.0.0-alpha18"
val koinCoreVersion = "3.4.0"
@@ -96,7 +97,7 @@ dependencies {
// SplashScreen
implementation("androidx.core:core-splashscreen:1.0.1")
- implementation("androidx.navigation:navigation-compose:2.7.6")
+ implementation("androidx.navigation:navigation-compose:2.7.7")
// Color picker
implementation("com.github.skydoves:colorpicker-compose:1.0.5")
@@ -111,6 +112,8 @@ dependencies {
implementation("androidx.work:work-runtime-ktx:2.9.0")
+ implementation("com.wajahatkarim:flippable:1.5.4")
+
implementation("androidx.compose.ui:ui:1.5.0")
implementation("androidx.compose.ui:ui-tooling:1.5.0")
implementation("androidx.compose.ui:ui-tooling-preview:1.5.0")
diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml
index b3d7b14..6aaa8f6 100644
--- a/androidApp/src/main/AndroidManifest.xml
+++ b/androidApp/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
+
+ val route = Screens.FLASHCARDS_SCREEN.let { route ->
+ boardId?.let { "$route?boardId=$it" } ?: route
+ }
+ navigator.navigateTo(route)
+ },
+ openDrawer = { coroutineScope.launch { drawerState.open() } }
+ )
+ }
+ )
+ }
+
+ composable(
+ route = Destinations.FLASHCARDS_ROUTE,
+ arguments = listOf(
+ navArgument(BOARD_ID_ARG) {
+ nullable = true
+ defaultValue = null
+ type = NavType.StringType
+ },
+ ),
+ ) { entry ->
+ val boardId = entry.arguments?.getString(BOARD_ID_ARG)
+
+ BoardScreen(
+ boardId,
+ onBackClick = { navigator.popBackStack() }
+ )
+ }
}
}
\ No newline at end of file
diff --git a/androidApp/src/main/java/com/san/englishbender/android/navigation/NavigationActions.kt b/androidApp/src/main/java/com/san/englishbender/android/navigation/NavigationActions.kt
index 9b06dd7..e5568d0 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/navigation/NavigationActions.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/navigation/NavigationActions.kt
@@ -1,6 +1,8 @@
package com.san.englishbender.android.navigation
import androidx.navigation.NavHostController
+import com.san.englishbender.core.navigation.Destinations.BOARDS_ROUTE
+import com.san.englishbender.core.navigation.Destinations.FLASHCARDS_ROUTE
import com.san.englishbender.core.navigation.Destinations.RECORD_DETAIL_ROUTE
import com.san.englishbender.core.navigation.Destinations.STATS_ROUTE
import com.san.englishbender.core.navigation.Screens.RECORDS_SCREEN
@@ -14,6 +16,14 @@ class EBNavigationActions(private val navController: NavHostController) {
navController.navigate(STATS_ROUTE)
}
+ fun navigateToBoards() {
+ navController.navigate(BOARDS_ROUTE)
+ }
+
+ fun navigateToFlashCards() {
+ navController.navigate(FLASHCARDS_ROUTE)
+ }
+
fun navigateToRecords() {
navController.navigate(RECORDS_SCREEN)
// navController.navigate(RECORDS_SCREEN) {
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/common/AppDrawer.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/AppDrawer.kt
index 80c3471..768571a 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/common/AppDrawer.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/AppDrawer.kt
@@ -6,8 +6,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ViewList
import androidx.compose.material.icons.filled.Analytics
-import androidx.compose.material.icons.filled.ViewList
+import androidx.compose.material.icons.filled.ViewCarousel
import androidx.compose.material3.DrawerState
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalDrawerSheet
@@ -34,16 +35,21 @@ data class DrawerNavOptions(
)
private val drawerNavOptions = listOf(
+ DrawerNavOptions(
+ name = "Records",
+ route = Destinations.RECORD_ROUTE,
+ icon = Icons.AutoMirrored.Filled.ViewList
+ ),
+ DrawerNavOptions(
+ name = "Flash-cards",
+ route = Destinations.BOARDS_ROUTE,
+ icon = Icons.Default.ViewCarousel
+ ),
DrawerNavOptions(
name = "Stats",
route = Destinations.STATS_ROUTE,
icon = Icons.Default.Analytics
),
- DrawerNavOptions(
- name = "Records",
- route = Destinations.RECORD_ROUTE,
- icon = Icons.Default.ViewList
- ),
)
@Composable
@@ -79,6 +85,8 @@ fun AppDrawer(
when (item.route) {
Destinations.STATS_ROUTE -> navActions.navigateToStats()
Destinations.RECORD_ROUTE -> navActions.navigateToRecords()
+ Destinations.BOARDS_ROUTE -> navActions.navigateToBoards()
+ Destinations.FLASHCARDS_ROUTE -> navActions.navigateToFlashCards()
}
// navController.navigate(item.route)
},
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/common/BackgroundColorPicker.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/BackgroundColorPicker.kt
new file mode 100644
index 0000000..23cb95e
--- /dev/null
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/BackgroundColorPicker.kt
@@ -0,0 +1,79 @@
+package com.san.englishbender.android.ui.common
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.san.englishbender.android.core.extensions.noRippleClickable
+import com.san.englishbender.android.ui.theme.backgroundColors
+import com.san.englishbender.android.ui.theme.selectedLabelColor
+import com.san.englishbender.core.extensions.ifNotEmpty
+import io.github.aakira.napier.log
+
+@Composable
+fun BackgroundColorPicker(
+ modifier: Modifier = Modifier,
+ label: String = "Background Color",
+ listState: LazyListState = rememberLazyListState(),
+ onClick: (color: Color) -> Unit
+) {
+ var selectedColor by remember { mutableStateOf(backgroundColors.first()) }
+
+ Column(modifier = modifier) {
+ label.ifNotEmpty {
+ Text(
+ modifier = Modifier.padding(8.dp),
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color.Black,
+ text = label
+ )
+ }
+
+ LazyRow(
+ state = listState,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(backgroundColors.size) { index ->
+ val color = backgroundColors[index]
+ val border = when (selectedColor == color) {
+ true -> Modifier.border(2.dp, selectedLabelColor, RoundedCornerShape(4.dp))
+ false -> Modifier.border(1.dp, Color.LightGray, RoundedCornerShape(4.dp))
+ }
+ Card(
+ modifier = Modifier
+ .size(60.dp)
+ .then(border)
+ .noRippleClickable {
+ selectedColor = color
+ log(tag = "containerColor") { "onClick: $color" }
+ onClick(color)
+ },
+ colors = CardDefaults.cardColors(containerColor = color)
+ ) {}
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/common/BaseDialogContent.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/BaseDialogContent.kt
index cfb5ac4..38c7839 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/common/BaseDialogContent.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/BaseDialogContent.kt
@@ -4,6 +4,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
@@ -18,6 +19,7 @@ import com.san.englishbender.android.core.extensions.noRippleClickable
@Composable
fun BaseDialogContent(
+ modifier: Modifier = Modifier,
width: Dp = 300.dp,
height: Dp = 450.dp,
shape: Shape = RoundedCornerShape(12.dp),
@@ -25,6 +27,8 @@ fun BaseDialogContent(
dismiss: () -> Unit = {},
content: @Composable () -> Unit,
) {
+// val heightDp = height?.let { Modifier.height(height) }
+
Box(
modifier = Modifier
.fillMaxSize()
@@ -32,13 +36,14 @@ fun BaseDialogContent(
.noRippleClickable { dismiss() }
) {
Box(
- modifier = Modifier
+ modifier = modifier
.align(Alignment.Center)
+// .then(heightDp)
.width(width)
.height(height)
.clip(shape)
.background(containerColor)
- .noRippleClickable { }
+ .noRippleClickable {}
) {
content()
}
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/BottomNavigationBar.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/BottomNavBar.kt
similarity index 59%
rename from androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/BottomNavigationBar.kt
rename to androidApp/src/main/java/com/san/englishbender/android/ui/common/BottomNavBar.kt
index 8e83bc7..900e150 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/BottomNavigationBar.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/BottomNavBar.kt
@@ -1,10 +1,9 @@
-package com.san.englishbender.android.ui.recordDetails
+package com.san.englishbender.android.ui.common
-import androidx.compose.foundation.layout.navigationBarsPadding
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.Icon
-import androidx.compose.material.Text
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Archive
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Spellcheck
import androidx.compose.material.icons.outlined.Translate
@@ -12,37 +11,36 @@ import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.unit.dp
-sealed class BottomNavItem(var title: String, var icon: ImageVector) {
- object GrammarCheck : BottomNavItem("GrammarCheck", Icons.Outlined.Spellcheck)
- object Translate : BottomNavItem("Translate", Icons.Outlined.Translate)
- object Settings : BottomNavItem("Settings", Icons.Outlined.Settings)
+sealed class BottomNavItem(var label: String, var icon: ImageVector)
+
+sealed class DeckNavItem(label: String, icon: ImageVector) : BottomNavItem(label, icon) {
+ data object SendToArchive : DeckNavItem("Archive", Icons.Outlined.Archive)
+}
+
+sealed class RecordDetailsNavItem(label: String, icon: ImageVector) : BottomNavItem(label, icon) {
+ data object GrammarCheck : RecordDetailsNavItem("GrammarCheck", Icons.Outlined.Spellcheck)
+ data object Translate : RecordDetailsNavItem("Translate", Icons.Outlined.Translate)
+ data object Settings : RecordDetailsNavItem("Settings", Icons.Outlined.Settings)
}
@Composable
-fun NavigationBar(
+fun BottomNavBar(
hasLabel: Boolean = false,
containerColor: Color = Color.White,
+ navItems: List,
navItemClicked: (navItem: BottomNavItem) -> Unit
) {
- val navItems = listOf(
- BottomNavItem.GrammarCheck,
- BottomNavItem.Translate,
- BottomNavItem.Settings
- )
-
NavigationBar(
contentColor = Color.Black,
containerColor = containerColor
) {
- navItems.forEachIndexed { index, navItem ->
+ navItems.forEach{ navItem ->
NavigationBarItem(
icon = { Icon(navItem.icon, contentDescription = null) },
- label = { if (hasLabel) Text(navItem.title) },
+ label = { if (hasLabel) Text(navItem.label) },
selected = false,
colors = NavigationBarItemDefaults.colors(
selectedIconColor = Color.Black,
@@ -56,4 +54,4 @@ fun NavigationBar(
)
}
}
-}
+}
\ No newline at end of file
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/common/ButtonComposables.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/ButtonComposables.kt
index 706c718..1ec4059 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/common/ButtonComposables.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/ButtonComposables.kt
@@ -15,13 +15,16 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.FontDownload
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -30,7 +33,9 @@ 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.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
@@ -40,6 +45,30 @@ import androidx.compose.ui.unit.sp
import com.san.englishbender.android.core.extensions.noRippleClickable
+@Composable
+fun EBTextButton(
+ modifier: Modifier = Modifier,
+ text: String,
+ fontSize: TextUnit = 14.sp,
+ textColor: Color = Color.DarkGray,
+ contentPadding: PaddingValues = PaddingValues(0.dp),
+ colors: ButtonColors = ButtonDefaults.outlinedButtonColors(contentColor = Color.DarkGray),
+ onClick: () -> Unit,
+) {
+ TextButton(
+ modifier = modifier,
+ colors = colors,
+ contentPadding = contentPadding,
+ onClick = onClick
+ ) {
+ Text(
+ text = text,
+ fontSize = fontSize,
+ color = textColor
+ )
+ }
+}
+
@Composable
fun EBOutlinedButton(
modifier: Modifier = Modifier,
@@ -141,4 +170,21 @@ fun FontColorChangeButton(
// tint = if (state) Color.White else Color.Black
// )
// }
+}
+
+@Composable
+fun EBIcon(
+ modifier: Modifier = Modifier,
+ imageVector: ImageVector,
+ contentDescription: String? = null,
+ onClick: () -> Unit = {}
+) {
+ Icon(
+ painter = rememberVectorPainter(imageVector),
+ contentDescription = contentDescription,
+ tint = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = modifier
+ .padding(8.dp)
+ .clickable { onClick() }
+ )
}
\ No newline at end of file
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/common/DialogHeader.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/DialogHeader.kt
index a241742..c11f8f6 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/common/DialogHeader.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/DialogHeader.kt
@@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -16,15 +16,33 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
-fun DialogHeader(
+fun DialogHeader(title: String) {
+ Row(modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 16.dp)
+ ) {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = title,
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center
+ )
+ }
+}
+
+@Composable
+fun DialogNavHeader(
title: String,
- onClick: () -> Unit
+ onClick: () -> Unit = {}
) {
- Row(modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp)) {
+ Row(modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 16.dp)) {
Row(Modifier.weight(1f)) {
Icon(
modifier = Modifier.clickable { onClick() },
- imageVector = Icons.Filled.ArrowBack,
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/common/TextComposables.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/TextComposables.kt
new file mode 100644
index 0000000..9505b21
--- /dev/null
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/TextComposables.kt
@@ -0,0 +1,33 @@
+package com.san.englishbender.android.ui.common
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+
+@Composable
+fun EBOutlinedTextField(
+ modifier: Modifier = Modifier,
+ value: String,
+ placeholder: String = "",
+ singleLine: Boolean = false,
+ keyboardOptions: KeyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Text,
+ imeAction = ImeAction.Next
+ ),
+ onValueChange: (String) -> Unit = {}
+) {
+ OutlinedTextField(
+ modifier = modifier,
+ value = value,
+ singleLine = singleLine,
+ placeholder = { Text(placeholder) },
+ keyboardOptions = keyboardOptions,
+ onValueChange = onValueChange
+ )
+}
\ No newline at end of file
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/common/richText/RichTextStyleRow.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/richText/RichTextStyleRow.kt
index 3bfceab..c1bc52d 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/common/richText/RichTextStyleRow.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/richText/RichTextStyleRow.kt
@@ -6,30 +6,33 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft
-import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight
import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
import androidx.compose.material.icons.filled.Circle
-import androidx.compose.material.icons.outlined.*
+import androidx.compose.material.icons.outlined.Circle
+import androidx.compose.material.icons.outlined.Code
+import androidx.compose.material.icons.outlined.FormatBold
+import androidx.compose.material.icons.outlined.FormatItalic
+import androidx.compose.material.icons.outlined.FormatListNumbered
+import androidx.compose.material.icons.outlined.FormatSize
+import androidx.compose.material.icons.outlined.FormatStrikethrough
+import androidx.compose.material.icons.outlined.FormatUnderlined
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.mohamedrejeb.richeditor.model.RichTextState
-import io.github.aakira.napier.log
+
@Composable
fun RichTextStyleRow(
modifier: Modifier = Modifier,
- state: RichTextState,
+ state: RichTextState
) {
// val currentParagraphStyle = state.currentParagraphStyle
// val isCentered = currentParagraphStyle.textAlign == TextAlign.Center
@@ -83,7 +86,6 @@ fun RichTextStyleRow(
icon = Icons.Outlined.FormatBold
)
}
-
item {
RichTextStyleButton(
onClick = {
@@ -93,79 +95,53 @@ fun RichTextStyleRow(
icon = Icons.Outlined.FormatItalic
)
}
-
item {
RichTextStyleButton(
onClick = {
- state.toggleSpanStyle(
- SpanStyle(
- textDecoration = TextDecoration.Underline
- )
- )
+ state.toggleSpanStyle(SpanStyle(textDecoration = TextDecoration.Underline))
},
isSelected = state.currentSpanStyle.textDecoration?.contains(TextDecoration.Underline) == true,
icon = Icons.Outlined.FormatUnderlined
)
}
-
item {
RichTextStyleButton(
onClick = {
- state.toggleSpanStyle(
- SpanStyle(
- textDecoration = TextDecoration.LineThrough
- )
- )
+ state.toggleSpanStyle(SpanStyle(textDecoration = TextDecoration.LineThrough))
},
isSelected = state.currentSpanStyle.textDecoration?.contains(TextDecoration.LineThrough) == true,
icon = Icons.Outlined.FormatStrikethrough
)
}
-
item {
RichTextStyleButton(
onClick = {
- state.toggleSpanStyle(
- SpanStyle(
- fontSize = 28.sp
- )
- )
+ state.toggleSpanStyle(SpanStyle(fontSize = 28.sp))
},
isSelected = state.currentSpanStyle.fontSize == 28.sp,
icon = Icons.Outlined.FormatSize
)
}
-
item {
RichTextStyleButton(
onClick = {
- state.toggleSpanStyle(
- SpanStyle(
- color = Color.Red
- )
- )
+ state.toggleSpanStyle(SpanStyle(color = Color.Red))
},
isSelected = state.currentSpanStyle.color == Color.Red,
icon = Icons.Filled.Circle,
tint = Color.Red
)
}
-
item {
RichTextStyleButton(
onClick = {
- state.toggleSpanStyle(
- SpanStyle(
- background = Color.Yellow
- )
- )
+ state.toggleSpanStyle(SpanStyle(background = Color.Yellow))
},
isSelected = state.currentSpanStyle.background == Color.Yellow,
icon = Icons.Outlined.Circle,
tint = Color.Yellow
)
}
-
item {
Box(
Modifier
@@ -174,29 +150,20 @@ fun RichTextStyleRow(
.background(Color(0xFF393B3D))
)
}
-
item {
RichTextStyleButton(
- onClick = {
- state.toggleUnorderedList()
- },
+ onClick = { state.toggleUnorderedList() },
isSelected = state.isUnorderedList,
icon = Icons.AutoMirrored.Outlined.FormatListBulleted,
)
}
-
item {
RichTextStyleButton(
- onClick = {
- state.toggleOrderedList()
- },
+ onClick = { state.toggleOrderedList() },
isSelected = state.isOrderedList,
icon = Icons.Outlined.FormatListNumbered,
)
}
-
-
-
item {
Box(
Modifier
@@ -205,12 +172,9 @@ fun RichTextStyleRow(
.background(Color(0xFF393B3D))
)
}
-
item {
RichTextStyleButton(
- onClick = {
- state.toggleCodeSpan()
- },
+ onClick = { state.toggleCodeSpan() },
isSelected = state.isCodeSpan,
icon = Icons.Outlined.Code,
)
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/common/richText/RichTextToolsRow.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/common/richText/RichTextToolsRow.kt
new file mode 100644
index 0000000..c991ee3
--- /dev/null
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/common/richText/RichTextToolsRow.kt
@@ -0,0 +1,192 @@
+package com.san.englishbender.android.ui.common.richText
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft
+import androidx.compose.material.icons.automirrored.outlined.FormatAlignRight
+import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
+import androidx.compose.material.icons.filled.Circle
+import androidx.compose.material.icons.outlined.*
+import androidx.compose.material3.VerticalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.ParagraphStyle
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.mohamedrejeb.richeditor.model.RichTextState
+
+enum class RichTextTools {
+ FormatAlignLeft,
+ FormatAlignCenter,
+ FormatAlignRight,
+ Bold,
+ Italic,
+ Underline,
+ FormatStrikethrough,
+ FormatSize,
+ FontColor,
+ BackgroundColor,
+ FormatListBulleted,
+ FormatListNumbered,
+ Code,
+ Divider
+}
+
+val fullRichTextToolsPanel = RichTextTools.values().toList()
+val shortRichTextToolsPanel = listOf(
+ RichTextTools.Bold,
+ RichTextTools.Italic,
+ RichTextTools.Underline,
+ RichTextTools.Divider,
+ RichTextTools.FormatListBulleted,
+ RichTextTools.FormatListNumbered,
+)
+
+@Composable
+fun RichTextToolsRow(
+ modifier: Modifier = Modifier,
+ state: RichTextState,
+ richTextTools: List = fullRichTextToolsPanel
+) {
+// val currentParagraphStyle = state.currentParagraphStyle
+
+ LazyRow(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier
+ ) {
+ items(richTextTools) { item ->
+ when (item) {
+ RichTextTools.FormatAlignLeft -> RichTextStyleButton(
+ onClick = {
+ state.toggleParagraphStyle(ParagraphStyle(textAlign = TextAlign.Left))
+ },
+ isSelected = state.currentParagraphStyle.textAlign == TextAlign.Left,
+ icon = Icons.AutoMirrored.Outlined.FormatAlignLeft
+ )
+
+ RichTextTools.FormatAlignCenter -> RichTextStyleButton(
+ onClick = {
+ state.toggleParagraphStyle(ParagraphStyle(textAlign = TextAlign.Center))
+ },
+ isSelected = state.currentParagraphStyle.textAlign == TextAlign.Center,
+ icon = Icons.Outlined.FormatAlignCenter
+ )
+
+ RichTextTools.FormatAlignRight -> RichTextStyleButton(
+ onClick = {
+ state.toggleParagraphStyle(ParagraphStyle(textAlign = TextAlign.Right))
+ },
+ isSelected = state.currentParagraphStyle.textAlign == TextAlign.Right,
+ icon = Icons.AutoMirrored.Outlined.FormatAlignRight
+ )
+
+ RichTextTools.Bold -> RichTextStyleButton(
+ onClick = {
+ state.toggleSpanStyle(SpanStyle(fontWeight = FontWeight.Bold))
+ },
+ isSelected = state.currentSpanStyle.fontWeight == FontWeight.Bold,
+ icon = Icons.Outlined.FormatBold
+ )
+
+ RichTextTools.Italic -> RichTextStyleButton(
+ onClick = {
+ state.toggleSpanStyle(SpanStyle(fontStyle = FontStyle.Italic))
+ },
+ isSelected = state.currentSpanStyle.fontStyle == FontStyle.Italic,
+ icon = Icons.Outlined.FormatItalic
+ )
+
+ RichTextTools.Underline -> RichTextStyleButton(
+ onClick = {
+ state.toggleSpanStyle(SpanStyle(textDecoration = TextDecoration.Underline))
+ },
+ isSelected = state.currentSpanStyle.textDecoration?.contains(TextDecoration.Underline) == true,
+ icon = Icons.Outlined.FormatUnderlined
+ )
+
+ RichTextTools.FormatStrikethrough -> RichTextStyleButton(
+ onClick = {
+ state.toggleSpanStyle(SpanStyle(textDecoration = TextDecoration.LineThrough))
+ },
+ isSelected = state.currentSpanStyle.textDecoration?.contains(TextDecoration.LineThrough) == true,
+ icon = Icons.Outlined.FormatStrikethrough
+ )
+
+ RichTextTools.FormatSize -> RichTextStyleButton(
+ onClick = {
+ state.toggleSpanStyle(SpanStyle(fontSize = 28.sp))
+ },
+ isSelected = state.currentSpanStyle.fontSize == 28.sp,
+ icon = Icons.Outlined.FormatSize
+ )
+
+ RichTextTools.FontColor -> RichTextStyleButton(
+ onClick = {
+ state.toggleSpanStyle(SpanStyle(color = Color.Red))
+ },
+ isSelected = state.currentSpanStyle.color == Color.Red,
+ icon = Icons.Filled.Circle,
+ tint = Color.Red
+ )
+
+ RichTextTools.BackgroundColor -> RichTextStyleButton(
+ onClick = {
+ state.toggleSpanStyle(SpanStyle(background = Color.Yellow))
+ },
+ isSelected = state.currentSpanStyle.background == Color.Yellow,
+ icon = Icons.Outlined.Circle,
+ tint = Color.Yellow
+ )
+
+ RichTextTools.FormatListBulleted -> RichTextStyleButton(
+ onClick = { state.toggleUnorderedList() },
+ isSelected = state.isUnorderedList,
+ icon = Icons.AutoMirrored.Outlined.FormatListBulleted,
+ )
+
+ RichTextTools.FormatListNumbered -> RichTextStyleButton(
+ onClick = { state.toggleOrderedList() },
+ isSelected = state.isOrderedList,
+ icon = Icons.Outlined.FormatListNumbered,
+ )
+
+ RichTextTools.Code -> RichTextStyleButton(
+ onClick = { state.toggleCodeSpan() },
+ isSelected = state.isCodeSpan,
+ icon = Icons.Outlined.Code,
+ )
+
+ RichTextTools.Divider -> VerticalDivider(
+ Modifier
+ .height(24.dp)
+ .background(Color(0xFF393B3D))
+ )
+ }
+ }
+
+// item {
+// VerticalDivider(
+// Modifier
+// .height(24.dp)
+// .background(Color(0xFF393B3D))
+// )
+// Box(
+// Modifier
+// .height(24.dp)
+// .width(1.dp)
+// .background(Color(0xFF393B3D))
+// )
+ }
+}
\ No newline at end of file
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/flashcards/BoardScreen.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/flashcards/BoardScreen.kt
new file mode 100644
index 0000000..78b47a6
--- /dev/null
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/flashcards/BoardScreen.kt
@@ -0,0 +1,509 @@
+package com.san.englishbender.android.ui.flashcards
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.outlined.Archive
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+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.platform.LocalFocusManager
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
+import com.mohamedrejeb.richeditor.model.RichTextState
+import com.mohamedrejeb.richeditor.model.rememberRichTextState
+import com.mohamedrejeb.richeditor.ui.BasicRichTextEditor
+import com.san.englishbender.android.core.extensions.noRippleClickable
+import com.san.englishbender.android.core.extensions.toColor
+import com.san.englishbender.android.ui.common.BaseDialogContent
+import com.san.englishbender.android.ui.common.BottomNavBar
+import com.san.englishbender.android.ui.common.BottomNavItem
+import com.san.englishbender.android.ui.common.DeckNavItem
+import com.san.englishbender.android.ui.common.EBIcon
+import com.san.englishbender.android.ui.common.EBOutlinedButton
+import com.san.englishbender.android.ui.common.EBOutlinedIconButton
+import com.san.englishbender.android.ui.common.EBOutlinedTextField
+import com.san.englishbender.android.ui.common.EBTextButton
+import com.san.englishbender.android.ui.common.RecordDetailsNavItem
+import com.san.englishbender.android.ui.common.richText.RichTextToolsRow
+import com.san.englishbender.android.ui.common.richText.shortRichTextToolsPanel
+import com.san.englishbender.android.ui.common.widgets.ErrorView
+import com.san.englishbender.android.ui.common.widgets.LoadingView
+import com.san.englishbender.android.ui.theme.RedDark
+import com.san.englishbender.core.extensions.ifNotEmpty
+import com.san.englishbender.core.extensions.isNotNull
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.entities.FlashCardEntity
+import com.san.englishbender.ui.flashcards.FlashCardsUiState
+import com.san.englishbender.ui.flashcards.FlashCardsViewModel
+import com.wajahatkarim.flippable.FlipAnimationType
+import com.wajahatkarim.flippable.Flippable
+import com.wajahatkarim.flippable.rememberFlipController
+import org.koin.androidx.compose.getViewModel
+
+
+@Composable
+fun BoardScreen(
+ boardId: String?,
+ onBackClick: () -> Unit
+) {
+ val viewModel: FlashCardsViewModel = getViewModel()
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ LaunchedEffect(boardId) {
+ boardId?.let {
+ viewModel.observeBoard(it)
+// viewModel.getBoard(it)
+// viewModel.getFlashCards(it)
+ }
+ }
+
+ when {
+ uiState.isLoading -> LoadingView()
+ uiState.userMessage.isNotNull -> ErrorView(userMessage = uiState.userMessage)
+ else -> BoardContent(
+ uiState,
+ onCardCreate = { board, flashCard -> viewModel.addCardToBoard(board, flashCard) },
+ onCardUpdate = { flashCard -> viewModel.saveCard(flashCard) },
+ onCardDelete = { flashCardId -> viewModel.deleteFlashCard(flashCardId) },
+ onBackClick
+ )
+ }
+}
+
+@OptIn(
+ ExperimentalMaterial3Api::class,
+ ExperimentalFoundationApi::class,
+ ExperimentalRichTextApi::class
+)
+@Composable
+fun BoardContent(
+ uiState: FlashCardsUiState,
+ onCardCreate: (BoardEntity, FlashCardEntity) -> Unit,
+ onCardUpdate: (FlashCardEntity) -> Unit,
+ onCardDelete: (String) -> Unit,
+ onBackClick: () -> Unit = {},
+) {
+ val controller = rememberFlipController()
+ val focusManager = LocalFocusManager.current
+
+// val cards = uiState.board?.flashCards ?: emptyList()
+// val cards = uiState.flashCards ?: emptyList()
+ val pagerState = rememberPagerState(pageCount = { uiState.flashCards.size })
+
+ var addCardDialog by remember { mutableStateOf(false) }
+ var editCardDialog by remember { mutableStateOf(false) }
+ var cardDeletionDialog by remember { mutableStateOf(false) }
+
+// var bottomNavItem by remember { mutableStateOf(DeckNavItem.SendToArchive) }
+
+ val containerColor = uiState.board?.backgroundColor?.toColor
+ ?: MaterialTheme.colorScheme.surfaceVariant
+
+ val richTextState = rememberRichTextState()
+ richTextState.setConfig(
+ linkColor = Color.Blue,
+ linkTextDecoration = TextDecoration.Underline,
+ codeColor = Color.DarkGray,
+ codeBackgroundColor = Color.Transparent,
+ codeStrokeColor = Color.Transparent,
+ )
+
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ containerColor = containerColor,
+ topBar = {
+ key(containerColor) {
+ TopAppBar(
+ title = {},
+ modifier = Modifier.fillMaxWidth(),
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = containerColor
+ ),
+ navigationIcon = {
+ EBIcon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ modifier = Modifier.padding(start = 8.dp),
+ onClick = { onBackClick() }
+ )
+ },
+ actions = {
+ EBIcon(
+ imageVector = Icons.Filled.Add,
+ modifier = Modifier.padding(8.dp),
+ onClick = { addCardDialog = true }
+ )
+ EBIcon(
+ imageVector = Icons.Filled.Edit,
+ modifier = Modifier.padding(8.dp),
+ onClick = { editCardDialog = true }
+ )
+ EBIcon(
+ imageVector = Icons.Filled.Delete,
+ modifier = Modifier.padding(8.dp),
+ onClick = { cardDeletionDialog = true }
+ )
+ }
+ )
+ }
+ },
+ bottomBar = {
+ BottomNavBar(
+ navItems = listOf(
+ DeckNavItem.SendToArchive,
+ ),
+ containerColor = containerColor,
+ navItemClicked = { navItem ->
+// bottomNavItem = navItem
+
+ when (navItem) {
+ DeckNavItem.SendToArchive -> {
+ uiState.flashCards.getOrNull(pagerState.currentPage)?.let {
+ it.isArchived = true
+ onCardUpdate(it)
+ }
+ }
+ else -> {}
+ }
+ }
+ )
+ },
+ ) { paddingValues ->
+
+ Column(
+ Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(
+ start = 16.dp,
+ end = 16.dp,
+ bottom = 16.dp
+ )
+ ) {
+ if (uiState.flashCards.isEmpty()) {
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Text("Board is empty")
+ }
+ return@Scaffold
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = "${(pagerState.currentPage + 1)}/${uiState.flashCards.size}",
+ fontSize = 18.sp
+ )
+ }
+
+ HorizontalPager(
+ state = pagerState
+ ) { pageIndex ->
+ val card = uiState.flashCards.getOrNull(pageIndex) ?: return@HorizontalPager
+
+ LaunchedEffect(card.backText) {
+ card.backText.ifNotEmpty { richTextState.setHtml(it) }
+ }
+
+ Flippable(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(600.dp),
+ frontSide = {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .border(1.dp, Color.Gray, RoundedCornerShape(6.dp))
+ .background(Color.White, RoundedCornerShape(6.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ modifier = Modifier.noRippleClickable { controller.flipToBack() },
+ text = card.frontText,
+ color = Color.Black,
+ fontSize = 20.sp
+ )
+ }
+ },
+ backSide = {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .border(1.dp, Color.Gray, RoundedCornerShape(6.dp))
+ .background(Color.White, RoundedCornerShape(6.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ BasicRichTextEditor(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ .verticalScroll(state = rememberScrollState())
+ .noRippleClickable { controller.flip() },
+ state = richTextState,
+ textStyle = TextStyle(fontSize = 20.sp),
+ readOnly = true
+ )
+ }
+ },
+ flipController = controller,
+ flipAnimationType = FlipAnimationType.HORIZONTAL_CLOCKWISE
+ )
+ }
+
+// EBOutlinedIconButton(
+// imageVector = Icons.Outlined.Archive,
+// modifier = Modifier.padding(vertical = 16.dp),
+// onClick = {
+// cards.getOrNull(pagerState.currentPage)?.let {
+// it.isArchived = true
+// onCardUpdate(it)
+// }
+// }
+// )
+ }
+ }
+
+ when {
+ addCardDialog -> AddEditCardDialog(
+ onSave = { flashCard ->
+ focusManager.clearFocus()
+ uiState.board?.let { onCardCreate(it, flashCard) }
+ },
+ dismiss = { addCardDialog = false }
+ )
+ // ---
+ editCardDialog -> AddEditCardDialog(
+ flashCard = uiState.flashCards.getOrNull(pagerState.currentPage) ?: return,
+ onSave = { flashCard ->
+ focusManager.clearFocus()
+ onCardUpdate(flashCard)
+ },
+ dismiss = { editCardDialog = false }
+ )
+ // ---
+ cardDeletionDialog -> CardDeletionDialog(
+ flashCard = uiState.flashCards.getOrNull(pagerState.currentPage) ?: return,
+ confirm = { cardId -> onCardDelete(cardId) },
+ dismiss = { cardDeletionDialog = false }
+ )
+ }
+}
+
+@Preview
+@Composable
+fun DialogPreview() {
+ AddEditCardDialog(
+ onSave = {},
+ dismiss = {},
+ )
+}
+
+@OptIn(ExperimentalRichTextApi::class)
+@Composable
+fun AddEditCardDialog(
+ flashCard: FlashCardEntity? = null,
+ richTextState: RichTextState = rememberRichTextState(),
+ onSave: (FlashCardEntity) -> Unit,
+ dismiss: () -> Unit
+) {
+ var word by remember { mutableStateOf("") }
+ val card by remember { mutableStateOf(flashCard ?: FlashCardEntity()) }
+ richTextState.setConfig(
+ linkColor = Color.Blue,
+ linkTextDecoration = TextDecoration.Underline,
+ codeColor = Color.DarkGray,
+ codeBackgroundColor = Color.Transparent,
+ codeStrokeColor = Color.Transparent,
+ )
+ flashCard?.let {
+ LaunchedEffect(it) {
+ word = it.frontText
+ richTextState.setHtml(it.backText)
+ }
+ }
+
+ BaseDialogContent(
+ height = 450.dp,
+ dismiss = dismiss
+ ) {
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "New flashcard",
+ fontSize = 16.sp
+ )
+ EBOutlinedButton(
+ text = "Save",
+ onClick = {
+ if (word.isEmpty()) return@EBOutlinedButton
+
+ // TODO: delete it when RichTextEditor has onValueChanged callback
+ card.backText = richTextState.toHtml()
+
+ onSave(card)
+ dismiss()
+ }
+ )
+ }
+
+ EBOutlinedTextField(
+ modifier = Modifier.fillMaxWidth(),
+ value = word,
+ placeholder = "Word",
+ singleLine = true,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Text,
+ imeAction = ImeAction.Done
+ ),
+ onValueChange = {
+ word = it
+ card.frontText = it
+ }
+ )
+
+ Spacer(Modifier.height(16.dp))
+
+ RichTextToolsRow(
+ state = richTextState,
+ richTextTools = shortRichTextToolsPanel
+ )
+ BasicRichTextEditor(
+ modifier = Modifier
+ .fillMaxWidth()
+ .border(1.dp, Color.Gray, RoundedCornerShape(6.dp))
+ .verticalScroll(state = rememberScrollState()),
+ state = richTextState,
+ minLines = 12,
+ maxLines = 12,
+ decorationBox = { innerTextField ->
+ Box(Modifier.padding(12.dp)) {
+ if (richTextState.annotatedString.text.isEmpty()) {
+ Text(
+ text = "Description",
+ color = Color.LightGray
+ )
+ }
+ innerTextField()
+ }
+ }
+ )
+ }
+ }
+}
+
+@Composable
+fun CardDeletionDialog(
+ flashCard: FlashCardEntity,
+ confirm: (String) -> Unit,
+ dismiss: () -> Unit
+) {
+ BaseDialogContent(
+ width = 350.dp,
+ height = 200.dp,
+ dismiss = dismiss
+ ) {
+ Column(Modifier.padding(16.dp)) {
+ Text(
+ text = "Warning",
+ fontSize = 20.sp
+ )
+
+ Spacer(Modifier.height(16.dp))
+
+ Text(
+ modifier = Modifier.padding(top = 16.dp, bottom = 24.dp),
+ text = "Are you sure to delete the card \"${flashCard.frontText}\"?",
+ fontSize = 18.sp
+ )
+
+ Spacer(Modifier.height(24.dp))
+
+ Row(
+ Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Spacer(Modifier.weight(1f))
+ EBTextButton(
+ text = "Cancel",
+ onClick = dismiss,
+ fontSize = 18.sp
+ )
+ EBTextButton(
+ modifier = Modifier.padding(start = 32.dp),
+ text = "Delete",
+ textColor = RedDark,
+ fontSize = 18.sp,
+ onClick = { confirm(flashCard.id) }
+ )
+ }
+ }
+ }
+}
+
+//@Preview
+//@Composable
+//fun BoardContentPreview() {
+// BoardContent(
+// uiState = BoardUiState(),
+// onCardCreate = { board, card -> },
+// onCardUpdate = {},
+// onCardDelete = {}
+// )
+//}
\ No newline at end of file
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/flashcards/BoardsScreen.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/flashcards/BoardsScreen.kt
new file mode 100644
index 0000000..ccb1967
--- /dev/null
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/flashcards/BoardsScreen.kt
@@ -0,0 +1,288 @@
+package com.san.englishbender.android.ui.flashcards
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.FilterList
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.material.icons.filled.MoreVert
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.CardElevation
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+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.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.san.englishbender.android.core.extensions.noRippleClickable
+import com.san.englishbender.android.core.extensions.toHex
+import com.san.englishbender.android.ui.common.BackgroundColorPicker
+import com.san.englishbender.android.ui.common.BaseDialogContent
+import com.san.englishbender.android.ui.common.EBIcon
+import com.san.englishbender.android.ui.common.EBOutlinedButton
+import com.san.englishbender.android.ui.common.EBOutlinedTextField
+import com.san.englishbender.android.ui.common.widgets.ErrorView
+import com.san.englishbender.android.ui.common.widgets.LoadingView
+import com.san.englishbender.android.ui.theme.backgroundColors
+import com.san.englishbender.core.extensions.isNotNull
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.ui.flashcards.BoardsUiState
+import com.san.englishbender.ui.flashcards.BoardsViewModel
+import io.github.aakira.napier.log
+import org.koin.androidx.compose.getViewModel
+
+@Composable
+fun BoardsScreen(
+ onBoardClick: (String?) -> Unit,
+ openDrawer: () -> Unit
+) {
+ val viewModel: BoardsViewModel = getViewModel()
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ when {
+ uiState.isLoading -> LoadingView()
+ uiState.userMessage.isNotNull -> ErrorView(userMessage = uiState.userMessage)
+ else -> BoardsContent(
+ uiState,
+ onBoardCreate = { board -> viewModel.saveBoard(board) },
+ onBoardClick = onBoardClick,
+ onJson = { viewModel.loadAndParseJsonFile() },
+ openDrawer = openDrawer
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun BoardsContent(
+ uiState: BoardsUiState,
+ onBoardCreate: (BoardEntity) -> Unit,
+ onBoardClick: (String?) -> Unit,
+ onJson: () -> Unit,
+ openDrawer: () -> Unit
+) {
+ val focusManager = LocalFocusManager.current
+ var boardCreationDialog by remember { mutableStateOf(false) }
+
+ Scaffold(
+ modifier = Modifier.fillMaxWidth(),
+ containerColor = MaterialTheme.colorScheme.surfaceVariant,
+ topBar = {
+ TopAppBar(
+ modifier = Modifier.fillMaxWidth(),
+ title = {},
+ navigationIcon = {
+ EBIcon(
+ imageVector = Icons.Filled.Menu,
+ modifier = Modifier.padding(8.dp),
+ onClick = { openDrawer() }
+ )
+ },
+ actions = {
+ EBIcon(
+ imageVector = Icons.Filled.MoreVert,
+ modifier = Modifier.padding(8.dp)
+ )
+ },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant
+ )
+ )
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ contentColor = Color.White,
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ shape = RoundedCornerShape(8.dp),
+ onClick = { boardCreationDialog = true }
+ ) {
+ Icon(
+ Icons.Filled.Add,
+ contentDescription = "",
+ tint = MaterialTheme.colorScheme.onPrimaryContainer
+ )
+ }
+ }
+ ) { paddingValues ->
+ LazyColumn(modifier = Modifier.padding(paddingValues).padding(16.dp)) {
+ item {
+ Button(onClick = { onJson() }) {
+ Text("Test")
+ }
+ }
+ items(items = uiState.boards, key = { it.id }) { board ->
+ BoardItem(board, onBoardClick)
+ }
+ }
+ }
+ if (boardCreationDialog) {
+ BoardCreationDialog(
+ onBoardCreate = onBoardCreate,
+ dismiss = {
+ focusManager.clearFocus()
+ boardCreationDialog = false
+ }
+ )
+ }
+}
+
+@Composable
+fun BoardCreationDialog(
+ onBoardCreate: (BoardEntity) -> Unit,
+ dismiss: () -> Unit
+) {
+ BaseDialogContent(
+ height = 250.dp,
+ dismiss = dismiss
+ ) {
+ val board by remember { mutableStateOf(BoardEntity()) }
+ var boardName by remember { mutableStateOf("") }
+
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .padding(vertical = 24.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ EBOutlinedTextField(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ value = boardName,
+ placeholder = "Board name",
+ singleLine = true,
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Text,
+ imeAction = ImeAction.Done
+ ),
+ onValueChange = {
+ boardName = it
+ board.name = it
+ }
+ )
+
+ BackgroundColorPicker(
+ modifier = Modifier.padding(
+ vertical = 12.dp,
+ horizontal = 8.dp
+ ),
+ label = "",
+ listState = rememberLazyListState(),
+ onClick = { color: Color -> board.backgroundColor = color.toHex() }
+ )
+
+ Spacer(Modifier.weight(1f))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = 16.dp),
+ horizontalArrangement = Arrangement.End
+ ) {
+ EBOutlinedButton(
+ text = "Save",
+ onClick = {
+ if (board.name.isEmpty()) return@EBOutlinedButton
+ board.backgroundColor.ifEmpty {
+ board.backgroundColor = backgroundColors.first().toHex()
+ }
+
+ onBoardCreate(board)
+ dismiss()
+ }
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun BoardItem(
+ board: BoardEntity,
+ onBoardClick: (String?) -> Unit,
+) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp)
+ .clickable { onBoardClick(board.id) },
+ elevation = CardDefaults.cardElevation(6.dp),
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiaryContainer)
+ ) {
+ Text(
+ modifier = Modifier.padding(12.dp),
+ text = board.name
+ )
+ }
+}
+
+//@PreviewLightDark
+//@Preview
+//@Composable
+//fun BoardsScreenPreview() {
+// EnglishBenderTheme {
+// BoardsScreen(
+// uiState = BoardsUiState(),
+// onBoardCreate = {},
+// onBoardClick = {},
+// openDrawer = {}
+// )
+// }
+//}
+
+//@Composable
+//fun rememberImeState(): State {
+// val imeState = remember {
+// mutableStateOf(false)
+// }
+//
+// val view = LocalView.current
+// DisposableEffect(view) {
+// val listener = ViewTreeObserver.OnGlobalLayoutListener {
+// val isKeyboardOpen = ViewCompat.getRootWindowInsets(view)
+// ?.isVisible(WindowInsetsCompat.Type.ime()) ?: true
+// imeState.value = isKeyboardOpen
+// }
+//
+// view.viewTreeObserver.addOnGlobalLayoutListener(listener)
+// onDispose {
+// view.viewTreeObserver.removeOnGlobalLayoutListener(listener)
+// }
+// }
+// return imeState
+//}
\ No newline at end of file
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/RecordDetailsScreen.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/RecordDetailsScreen.kt
index 5938b34..ccab96b 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/RecordDetailsScreen.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/RecordDetailsScreen.kt
@@ -24,8 +24,8 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField
@@ -66,7 +66,10 @@ import com.mohamedrejeb.richeditor.ui.BasicRichTextEditor
import com.san.englishbender.Strings
import com.san.englishbender.android.core.extensions.toColor
import com.san.englishbender.android.core.extensions.toHex
+import com.san.englishbender.android.ui.common.BottomNavBar
+import com.san.englishbender.android.ui.common.BottomNavItem
import com.san.englishbender.android.ui.common.EBOutlinedButton
+import com.san.englishbender.android.ui.common.RecordDetailsNavItem
import com.san.englishbender.android.ui.common.richText.RichTextStyleRow
import com.san.englishbender.android.ui.recordDetails.bottomSheets.BackgroundColorPickerBSContent
import com.san.englishbender.android.ui.recordDetails.bottomSheets.GrammarCheckBSContent
@@ -76,7 +79,6 @@ import com.san.englishbender.android.ui.theme.BottomSheetContainerColor
import com.san.englishbender.android.ui.theme.RedDark
import com.san.englishbender.core.AppConstants
import com.san.englishbender.core.extensions.ifNotEmpty
-import com.san.englishbender.core.extensions.isNotNull
import com.san.englishbender.ui.recordDetails.DetailUiState
import com.san.englishbender.ui.recordDetails.RecordDetailsViewModel
import org.koin.androidx.compose.getViewModel
@@ -93,7 +95,7 @@ fun RecordDetailsScreen(
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(recordId) {
- if (recordId.isNotNull) viewModel.getRecord(recordId)
+ recordId?.let { viewModel.getRecord(it) }
}
RecordDetailsContent(
@@ -117,6 +119,7 @@ fun RecordDetailsContent(
viewModel: RecordDetailsViewModel,
onBackClick: () -> Unit
) {
+
val context = LocalContext.current
val focusManager = LocalFocusManager.current
// val coroutineScope = rememberCoroutineScope()
@@ -162,7 +165,7 @@ fun RecordDetailsContent(
else record.backgroundColor.toColor
)
}
- var bottomNavItem by remember { mutableStateOf(BottomNavItem.Translate) }
+ var bottomNavItem by remember { mutableStateOf(RecordDetailsNavItem.Translate) }
var tagsDialog by remember { mutableStateOf(false) }
val selectedTags = remember(record) {
record.tags?.toMutableStateList() ?: mutableStateListOf()
@@ -212,8 +215,13 @@ fun RecordDetailsContent(
}
},
bottomBar = {
- NavigationBar(
+ BottomNavBar(
containerColor = containerColor,
+ navItems = listOf(
+ RecordDetailsNavItem.GrammarCheck,
+ RecordDetailsNavItem.Translate,
+ RecordDetailsNavItem.Settings
+ ),
navItemClicked = { navItem ->
bottomNavItem = navItem
openBottomSheet = true
@@ -269,7 +277,7 @@ fun RecordDetailsContent(
),
)
- Divider(
+ HorizontalDivider(
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp),
color = Color.LightGray
)
@@ -334,21 +342,20 @@ fun RecordDetailsContent(
) {
focusManager.clearFocus()
when (bottomNavItem) {
- BottomNavItem.GrammarCheck -> GrammarCheckBSContent(
+ RecordDetailsNavItem.GrammarCheck -> GrammarCheckBSContent(
viewModel,
richTextState.annotatedString.text
)
-
- BottomNavItem.Translate -> TranslatedTextBSContent(
+ RecordDetailsNavItem.Translate -> TranslatedTextBSContent(
text = "Some translated text"
)
-
- BottomNavItem.Settings -> BackgroundColorPickerBSContent(
+ RecordDetailsNavItem.Settings -> BackgroundColorPickerBSContent(
onClick = { color ->
containerColor = color
record.backgroundColor = color.toHex()
}
)
+ else -> {}
}
}
}
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/bottomSheets/BackgroundColorPickerBSContent.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/bottomSheets/BackgroundColorPickerBSContent.kt
index cc8e5f5..27c52fc 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/bottomSheets/BackgroundColorPickerBSContent.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/recordDetails/bottomSheets/BackgroundColorPickerBSContent.kt
@@ -72,7 +72,6 @@ fun BackgroundColorPickerBSContent(
.clickable(onClick = { onClick(color) }),
colors = CardDefaults.cardColors(containerColor = color),
border = BorderStroke(1.dp, Color.LightGray)
-// border = if (index == 0) BorderStroke(1.dp, Color.LightGray) else null
) {}
}
}
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/records/RecordsScreen.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/records/RecordsScreen.kt
index 860e097..5630c6e 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/records/RecordsScreen.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/records/RecordsScreen.kt
@@ -17,6 +17,7 @@ import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.FilterList
import androidx.compose.material.icons.filled.Menu
@@ -42,6 +43,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.san.englishbender.android.core.extensions.truncateText
+import com.san.englishbender.android.ui.common.EBIcon
import com.san.englishbender.android.ui.common.widgets.ErrorView
import com.san.englishbender.android.ui.common.widgets.LoadingView
import com.san.englishbender.core.AppConstants.RECORD_MAX_LENGTH_DESCRIPTION
@@ -93,31 +95,22 @@ fun RecordsContent(
modifier = Modifier.fillMaxWidth(),
title = {},
navigationIcon = {
- Icon(
- rememberVectorPainter(Icons.Filled.Menu),
- contentDescription = null,
- tint = MaterialTheme.colorScheme.onPrimaryContainer,
- modifier = Modifier
- .padding(8.dp)
- .clickable { openDrawer() }
+ EBIcon(
+ imageVector = Icons.Filled.Menu,
+ modifier = Modifier.padding(8.dp),
+ onClick = { openDrawer() }
)
},
actions = {
- Icon(
- rememberVectorPainter(Icons.Filled.FilterList),
- contentDescription = null,
- tint = MaterialTheme.colorScheme.onPrimaryContainer,
- modifier = Modifier
- .padding(8.dp)
- .clickable { }
+ EBIcon(
+ imageVector = Icons.Filled.FilterList,
+ modifier = Modifier.padding(8.dp),
+ onClick = {}
)
- Icon(
- rememberVectorPainter(Icons.Filled.Settings),
- contentDescription = null,
- tint = MaterialTheme.colorScheme.onPrimaryContainer,
- modifier = Modifier
- .padding(8.dp)
- .clickable { }
+ EBIcon(
+ imageVector = Icons.Filled.Settings,
+ modifier = Modifier.padding(8.dp),
+ onClick = {}
)
},
colors = TopAppBarDefaults.topAppBarColors(
@@ -129,14 +122,12 @@ fun RecordsContent(
FloatingActionButton(
contentColor = Color.White,
backgroundColor = MaterialTheme.colorScheme.tertiaryContainer,
- shape = RoundedCornerShape(10.dp),
- onClick = {
- onRecordClick(null)
- }
+ shape = RoundedCornerShape(8.dp),
+ onClick = { onRecordClick(null) }
) {
Icon(
Icons.Filled.Edit,
- contentDescription = "",
+ contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/tags/AddEditTagScreen.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/tags/AddEditTagScreen.kt
index 9fa0516..5a42be3 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/tags/AddEditTagScreen.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/tags/AddEditTagScreen.kt
@@ -39,7 +39,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.san.englishbender.android.core.extensions.noRippleClickable
import com.san.englishbender.android.core.extensions.toColor
import com.san.englishbender.android.core.extensions.toHex
-import com.san.englishbender.android.ui.common.DialogHeader
+import com.san.englishbender.android.ui.common.DialogNavHeader
import com.san.englishbender.android.ui.common.EBOutlinedButton
import com.san.englishbender.android.ui.common.EBOutlinedIconButton
import com.san.englishbender.android.ui.common.FontColorChangeButton
@@ -82,7 +82,7 @@ fun AddEditTagScreen(
.fillMaxWidth()
.padding(16.dp)
) {
- DialogHeader(
+ DialogNavHeader(
title = title,
onClick = onBack
)
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/tags/ColorPickerScreen.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/tags/ColorPickerScreen.kt
index 33b43ee..3f29f86 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/tags/ColorPickerScreen.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/tags/ColorPickerScreen.kt
@@ -28,7 +28,7 @@ import com.github.skydoves.colorpicker.compose.BrightnessSlider
import com.github.skydoves.colorpicker.compose.ColorEnvelope
import com.github.skydoves.colorpicker.compose.HsvColorPicker
import com.github.skydoves.colorpicker.compose.rememberColorPickerController
-import com.san.englishbender.android.ui.common.DialogHeader
+import com.san.englishbender.android.ui.common.DialogNavHeader
import com.san.englishbender.android.ui.common.EBOutlinedButton
@@ -47,7 +47,7 @@ fun ColorPickerScreen(
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
- DialogHeader(
+ DialogNavHeader(
title = "Color Picker",
onClick = onBack
)
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/tags/TagsScreen.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/tags/TagsScreen.kt
index 03c3edf..4df6262 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/tags/TagsScreen.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/tags/TagsScreen.kt
@@ -17,7 +17,6 @@ import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
@@ -30,12 +29,12 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.san.englishbender.android.core.extensions.toColor
+import com.san.englishbender.android.ui.common.DialogHeader
import com.san.englishbender.android.ui.common.EBOutlinedButton
import com.san.englishbender.android.ui.theme.ColorsPreset
import com.san.englishbender.android.ui.theme.selectedLabelColor
import com.san.englishbender.domain.entities.TagEntity
import com.san.englishbender.ui.TagsViewModel
-import io.github.aakira.napier.log
@Composable
@@ -59,14 +58,16 @@ fun TagsScreen(
Column(modifier = Modifier.padding(16.dp)) {
- Text(
- modifier = Modifier
- .align(Alignment.CenterHorizontally)
- .padding(bottom = 16.dp),
- text = "Tags",
- fontWeight = FontWeight.Bold,
- fontSize = 16.sp
- )
+// Text(
+// modifier = Modifier
+// .align(Alignment.CenterHorizontally)
+// .padding(bottom = 16.dp),
+// text = "Tags",
+// fontWeight = FontWeight.Bold,
+// fontSize = 16.sp
+// )
+
+ DialogHeader(title = "Tags")
Column(modifier = Modifier
.weight(1f)
diff --git a/androidApp/src/main/java/com/san/englishbender/android/ui/theme/Color.kt b/androidApp/src/main/java/com/san/englishbender/android/ui/theme/Color.kt
index 93948e1..4137831 100644
--- a/androidApp/src/main/java/com/san/englishbender/android/ui/theme/Color.kt
+++ b/androidApp/src/main/java/com/san/englishbender/android/ui/theme/Color.kt
@@ -59,6 +59,19 @@ val darkReplyBlueColors = darkColorScheme(
surface = Color.White
)
+val backgroundColors = listOf(
+ Color(0xFFFFFFFF),
+ Color(0xFFFFFFCC),
+ Color(0xFFFFCC99),
+ Color(0xFFFFCCCC),
+ Color(0xFFFFCCFF),
+ Color(0xFFCCCCFF),
+ Color(0xFF99CCFF),
+ Color(0xFFCCFFFF),
+ Color(0xFF99FFCC),
+ Color(0xFFCCFF99),
+)
+
object ColorsPreset {
val coral: Color = Color(0xFFF29131)
diff --git a/build.gradle.kts b/build.gradle.kts
index 265fe3c..9970dbc 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -10,6 +10,7 @@ plugins {
id("com.android.library").version("7.4.2").apply(false)
kotlin("android").version("1.8.0").apply(false)
kotlin("multiplatform").version("1.8.22").apply(false)
+ kotlin("plugin.serialization") version "1.9.22"
}
tasks.register("clean", Delete::class) {
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index e360f3c..f611a4c 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
id("kotlin-kapt")
id("io.realm.kotlin") version "1.11.0"
id("dev.icerock.mobile.multiplatform-resources")
+ kotlin("plugin.serialization")
}
kotlin {
@@ -29,16 +30,21 @@ kotlin {
}
sourceSets {
- val coroutineVersion = "1.7.2"
+// val coroutineVersion = "1.7.3"
+ val coroutineVersion = "1.8.0"
val retrofitCoroutineAdapterVersion = "0.9.2"
val retrofitVersion = "2.9.0"
val okHttpVersion = "4.11.0"
val moshiVersion = "1.13.0"
- val lifecycleViewModelVersion = "2.6.2"
+ val lifecycleViewModelVersion = "2.7.0"
val koinCoreVersion = "3.4.2"
val koinAndroidVersion = "3.4.2"
val koinComposeVersion = "3.4.5"
+ getByName("androidMain") {
+ kotlin.srcDir("build/generated/moko/androidMain/src")
+ }
+
val commonMain by getting {
dependencies {
implementation("io.insert-koin:koin-core:$koinCoreVersion")
@@ -74,10 +80,10 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
// Realm
- api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2") // Add to use coroutines with the SDK
- api("io.realm.kotlin:library-base:1.11.0") // Add to only use the local database
- api("io.realm.kotlin:library-sync:1.11.0") // Add to use Device Sync
- compileOnly("io.realm.kotlin:library-base:1.11.0")
+ api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") // Add to use coroutines with the SDK
+ api("io.realm.kotlin:library-base:1.12.0") // Add to only use the local database
+ api("io.realm.kotlin:library-sync:1.12.0") // Add to use Device Sync
+ compileOnly("io.realm.kotlin:library-base:1.12.0")
// implementation("com.github.vicpinm:krealmextensions:2.5.0")
// Moco
@@ -90,7 +96,7 @@ kotlin {
// Napier
api("io.github.aakira:napier:2.6.1")
-// implementation("org.gradle:gradle-tooling-api:7.4.2")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
}
val commonTest by getting {
@@ -209,6 +215,13 @@ android {
}
}
}
+
+ sourceSets["main"].resources.setSrcDirs(
+ listOf(
+ "src/androidMain/resources",
+ "src/commonMain/resources"
+ )
+ )
}
// Don't cache SNAPSHOT (changing) dependencies.
diff --git a/shared/src/androidMain/kotlin/com/san/englishbender/Platform.kt b/shared/src/androidMain/kotlin/com/san/englishbender/Platform.kt
index 483483c..dd658ac 100644
--- a/shared/src/androidMain/kotlin/com/san/englishbender/Platform.kt
+++ b/shared/src/androidMain/kotlin/com/san/englishbender/Platform.kt
@@ -16,6 +16,7 @@ import kotlinx.parcelize.Parceler
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.TypeParceler
import org.koin.dsl.module
+import java.io.InputStreamReader
import java.util.UUID
actual typealias CommonParcelize = Parcelize
@@ -67,4 +68,14 @@ actual class Strings(
id.format(*args.toTypedArray()).toString(context)
}
}
+}
+
+internal actual class SharedFileReader{
+ actual fun loadJsonFile(fileName: String): String? {
+ return javaClass.classLoader?.getResourceAsStream(fileName).use { stream ->
+ InputStreamReader(stream).use { reader ->
+ reader.readText()
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/Platform.kt b/shared/src/commonMain/kotlin/com/san/englishbender/Platform.kt
index 28a1156..7741db3 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/Platform.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/Platform.kt
@@ -54,4 +54,8 @@ expect class Platform() {
expect class Strings {
fun get(id: StringResource, args: List = emptyList()): String
+}
+
+internal expect class SharedFileReader() {
+ fun loadJsonFile(fileName: String): String?
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/core/di/DatabaseModule.kt b/shared/src/commonMain/kotlin/com/san/englishbender/core/di/DatabaseModule.kt
index e482fd5..11e2da0 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/core/di/DatabaseModule.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/core/di/DatabaseModule.kt
@@ -3,12 +3,18 @@ package com.san.englishbender.core.di
import com.san.englishbender.data.local.dataStore.DataStoreRealm
import com.san.englishbender.data.local.dataStore.IDataStore
import com.san.englishbender.data.local.models.AppSettings
+import com.san.englishbender.data.local.models.Board
+import com.san.englishbender.data.local.models.FlashCard
import com.san.englishbender.data.local.models.Record
import com.san.englishbender.data.local.models.Stats
import com.san.englishbender.data.local.models.Tag
+import com.san.englishbender.data.repositories.BoardsRepository
+import com.san.englishbender.data.repositories.FlashCardsRepository
import com.san.englishbender.data.repositories.RecordsRepository
import com.san.englishbender.data.repositories.StatsRepository
import com.san.englishbender.data.repositories.TagsRepository
+import com.san.englishbender.domain.repositories.IBoardsRepository
+import com.san.englishbender.domain.repositories.IFlashCardsRepository
import com.san.englishbender.domain.repositories.IRecordsRepository
import com.san.englishbender.domain.repositories.IStatsRepository
import com.san.englishbender.domain.repositories.ITagsRepository
@@ -22,6 +28,8 @@ private val dataStoreModels = setOf(
Record::class,
Tag::class,
Stats::class,
+ Board::class,
+ FlashCard::class,
)
val databaseModule = module {
@@ -34,6 +42,8 @@ val databaseModule = module {
}
single { RecordsRepository(get()) }
- single { TagsRepository(get(), get()) }
+ single { TagsRepository(get()) }
single { StatsRepository(get()) }
+ single { BoardsRepository(get()) }
+ single { FlashCardsRepository(get()) }
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/core/di/UseCaseModule.kt b/shared/src/commonMain/kotlin/com/san/englishbender/core/di/UseCaseModule.kt
index cde13ec..0b78e2b 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/core/di/UseCaseModule.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/core/di/UseCaseModule.kt
@@ -1,5 +1,14 @@
package com.san.englishbender.core.di
+import com.san.englishbender.domain.usecases.flashCards.AddFlashCardToBoardUseCase
+import com.san.englishbender.domain.usecases.flashCards.DeleteBoardUseCase
+import com.san.englishbender.domain.usecases.flashCards.DeleteFlashCardUseCase
+import com.san.englishbender.domain.usecases.flashCards.GetBoardAsFlowUseCase
+import com.san.englishbender.domain.usecases.flashCards.GetBoardsFlowUseCase
+import com.san.englishbender.domain.usecases.flashCards.GetBoardByIdUseCase
+import com.san.englishbender.domain.usecases.flashCards.GetFlashCardsAsFlowUseCase
+import com.san.englishbender.domain.usecases.flashCards.SaveBoardUseCase
+import com.san.englishbender.domain.usecases.flashCards.SaveFlashCardUseCase
import com.san.englishbender.domain.usecases.records.GetRecordFlowUseCase
import com.san.englishbender.domain.usecases.records.GetRecordsCountUseCase
import com.san.englishbender.domain.usecases.records.GetRecordsUseCase
@@ -34,4 +43,15 @@ val useCaseModule = module {
single { SaveTagUseCase(get()) }
single { SaveTagColorUseCase(get()) }
single { DeleteTagUseCase(get()) }
+
+ // --- FlashCards
+ single { GetBoardsFlowUseCase(get()) }
+ single { GetBoardByIdUseCase(get()) }
+ single { GetBoardAsFlowUseCase(get()) }
+ single { GetFlashCardsAsFlowUseCase(get()) }
+ single { SaveBoardUseCase(get()) }
+ single { AddFlashCardToBoardUseCase(get()) }
+ single { SaveFlashCardUseCase(get()) }
+ single { DeleteBoardUseCase(get()) }
+ single { DeleteFlashCardUseCase(get()) }
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/core/di/ViewModelModule.kt b/shared/src/commonMain/kotlin/com/san/englishbender/core/di/ViewModelModule.kt
index e9625e5..79c1e5e 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/core/di/ViewModelModule.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/core/di/ViewModelModule.kt
@@ -1,6 +1,8 @@
package com.san.englishbender.core.di
import com.san.englishbender.ui.TagsViewModel
+import com.san.englishbender.ui.flashcards.BoardsViewModel
+import com.san.englishbender.ui.flashcards.FlashCardsViewModel
import com.san.englishbender.ui.recordDetails.RecordDetailsViewModel
import com.san.englishbender.ui.records.RecordsViewModel
import com.san.englishbender.ui.stats.StatsViewModel
@@ -11,4 +13,6 @@ val viewModelModule = module {
single { RecordDetailsViewModel(get(), get(), get(), get(), get()) }
single { StatsViewModel(get(), get()) }
single { TagsViewModel(get(), get(), get(), get(), get()) }
+ single { BoardsViewModel(get(), get(), get()) }
+ single { FlashCardsViewModel(get(), get(), get(), get(), get()) }
}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/core/navigation/Destinations.kt b/shared/src/commonMain/kotlin/com/san/englishbender/core/navigation/Destinations.kt
index 23e6241..e796974 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/core/navigation/Destinations.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/core/navigation/Destinations.kt
@@ -1,6 +1,8 @@
package com.san.englishbender.core.navigation
+import com.san.englishbender.core.navigation.Screens.BOARDS_SCREEN
import com.san.englishbender.core.navigation.Screens.COLOR_PICKER_SCREEN
+import com.san.englishbender.core.navigation.Screens.FLASHCARDS_SCREEN
import com.san.englishbender.core.navigation.Screens.RECORDS_SCREEN
import com.san.englishbender.core.navigation.Screens.RECORD_DETAIL_SCREEN
import com.san.englishbender.core.navigation.Screens.STATS_SCREEN
@@ -11,6 +13,8 @@ object Screens {
const val STATS_SCREEN = "stats"
const val RECORDS_SCREEN = "records"
const val RECORD_DETAIL_SCREEN = "recordDetail"
+ const val BOARDS_SCREEN = "boards"
+ const val FLASHCARDS_SCREEN = "flashcards"
// ---
const val TAG_LIST_SCREEN = "tag_list"
@@ -20,6 +24,7 @@ object Screens {
object DestinationsArgs {
const val RECORD_ID_ARG = "recordId"
+ const val BOARD_ID_ARG = "boardId"
const val TAG_ID_ARG = "tagId"
}
@@ -27,6 +32,8 @@ object Destinations {
const val STATS_ROUTE = STATS_SCREEN
const val RECORD_ROUTE = RECORDS_SCREEN
const val RECORD_DETAIL_ROUTE = "$RECORD_DETAIL_SCREEN?recordId={recordId}"
+ const val BOARDS_ROUTE = "$BOARDS_SCREEN?boardId={boardId}"
+ const val FLASHCARDS_ROUTE = "$FLASHCARDS_SCREEN?boardId={boardId}"
const val TAG_LIST_ROUTE = TAG_LIST_SCREEN
const val TAG_CREATE_ROUTE = "$TAG_ADD_EDIT_SCREEN?tagId={tagId}"
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/Result.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/Result.kt
index 66263bf..2860b8f 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/Result.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/Result.kt
@@ -54,29 +54,36 @@ fun Flow.asResult(): Flow> {
.catch { emit(Result.Failure(it)) }
}
-suspend fun getResult(action: suspend () -> T) = try {
- Result.Success(action.invoke())
-} catch (e: Exception) {
- Result.Failure(e)
-}
+//suspend fun getResult(action: suspend () -> T) = try {
+// Result.Success(action.invoke())
+//} catch (e: Exception) {
+// Result.Failure(e)
+//}
+
+//suspend fun getResult(action: suspend () -> Flow) : T = try {
+// action.invoke().collect {
+// return@collect Result.Success(it)
+// }
+//// Result.Success(action.invoke())
+//} catch (e: Exception) {
+// Result.Failure(e)
+//}
suspend fun getResultFlow(action: suspend () -> T): Flow> = flow {
return@flow try {
- log(tag = "ExceptionHandling") { "getResultFlow s" }
emit(Result.Success(action.invoke()))
} catch (e: Exception) {
- log(tag = "ExceptionHandling") { "getResultFlow f" }
emit(Result.Failure(e))
}
}
-suspend fun Flow>.ifSuccess(block: suspend (T) -> Unit) {
+suspend fun Flow>.onSuccess(block: suspend (T) -> Unit) {
this.collect {
if (it is Result.Success) block(it.data)
}
}
-fun Flow>.ifFailure(block: (Throwable) -> Unit): Flow> {
+fun Flow>.onFailure(block: (Throwable) -> Unit): Flow> {
return this.map {
if (it is Result.Failure) {
log(tag = "ifFailureException") { "ifFailure exception: ${it.exception}" }
@@ -86,7 +93,7 @@ fun Flow>.ifFailure(block: (Throwable) -> Unit): Flow> {
}
}
-suspend fun ifFailure(action: suspend () -> Unit, block: (Throwable) -> Unit) {
+suspend fun onFailure(action: suspend () -> Unit, block: (Throwable) -> Unit) {
try {
action()
} catch (e: Exception) {
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/dataStore/DataStoreRealm.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/dataStore/DataStoreRealm.kt
index 88eb9dc..6b6be97 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/dataStore/DataStoreRealm.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/dataStore/DataStoreRealm.kt
@@ -1,18 +1,11 @@
package com.san.englishbender.data.local.dataStore
-import com.san.englishbender.data.local.mappers.toEntity
import com.san.englishbender.data.local.models.AppSettings
-import com.san.englishbender.data.local.models.Record
-import com.san.englishbender.data.local.models.Stats
-import com.san.englishbender.data.local.models.Tag
import com.san.englishbender.ioDispatcher
import io.realm.kotlin.Realm
import io.realm.kotlin.UpdatePolicy
-import io.realm.kotlin.ext.asFlow
import io.realm.kotlin.ext.query
-import io.realm.kotlin.notifications.InitialResults
import io.realm.kotlin.notifications.SingleQueryChange
-import io.realm.kotlin.notifications.UpdatedResults
import io.realm.kotlin.types.RealmObject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/RecordMappers.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/RecordMappers.kt
deleted file mode 100644
index ffd29c6..0000000
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/RecordMappers.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.san.englishbender.data.local.mappers
-
-import com.san.englishbender.data.local.models.Record
-import com.san.englishbender.data.local.models.Tag
-import com.san.englishbender.domain.entities.RecordEntity
-import com.san.englishbender.domain.entities.TagEntity
-import io.realm.kotlin.ext.realmListOf
-import io.realm.kotlin.ext.toRealmList
-
-fun Record.toEntity(): RecordEntity =
- RecordEntity(
- id = id,
- title = title,
- text = text,
- plainText = plainText,
- creationDate = creationDate,
- isDeleted = isDeleted,
- isDraft = isDraft,
- backgroundColor = backgroundColor,
- tags = tags.map {
- TagEntity(
- id = it.id,
- name = it.name,
- color = it.color
- )
- }
- )
-
-fun RecordEntity.toLocal(): Record =
- Record(
- id = id,
- title = title,
- text = text,
- plainText = plainText,
- creationDate = creationDate,
- isDeleted = isDeleted,
- isDraft = isDraft,
- backgroundColor = backgroundColor,
- tags = tags?.map { Tag(it.id, it.name, it.color, it.isWhite) }?.toRealmList() ?: realmListOf()
- )
-
-fun List.toEntity() = this.map { it.toEntity() }
-fun List.toLocal() = this.map { it.toLocal() }
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/StatsMappers.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/StatsMappers.kt
deleted file mode 100644
index eda57cd..0000000
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/StatsMappers.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.san.englishbender.data.local.mappers
-
-import com.san.englishbender.data.local.models.Stats
-import com.san.englishbender.domain.entities.StatsEntity
-
-
-fun Stats.toEntity() =
- StatsEntity(
- recordsCount = this.recordsCount,
- wordsCount = this.wordsCount,
- lettersCount = this.lettersCount
- )
-
-fun StatsEntity.toLocal() =
- Stats(this.recordsCount, this.wordsCount, this.lettersCount)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/TagMappers.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/TagMappers.kt
deleted file mode 100644
index 6a54ce7..0000000
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/mappers/TagMappers.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.san.englishbender.data.local.mappers
-
-import com.san.englishbender.data.local.models.Tag
-import com.san.englishbender.domain.entities.TagEntity
-
-fun Tag.toEntity() =
- TagEntity(
- id = id,
- name = name,
- color = color,
- isWhite = isWhite
- )
-
-fun TagEntity.toLocal() =
- Tag(
- id = id,
- name = name,
- color = color,
- isWhite = isWhite
- )
-
-fun List.toEntity() = this.map { it.toEntity() }
-fun List.toLocal() = this.map { it.toLocal() }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Board.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Board.kt
new file mode 100644
index 0000000..60d738e
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Board.kt
@@ -0,0 +1,50 @@
+package com.san.englishbender.data.local.models
+
+import com.san.englishbender.domain.entities.BoardEntity
+import io.realm.kotlin.ext.realmListOf
+import io.realm.kotlin.ext.toRealmList
+import io.realm.kotlin.types.RealmList
+import io.realm.kotlin.types.RealmObject
+import io.realm.kotlin.types.annotations.PrimaryKey
+
+class Board : RealmObject {
+ @PrimaryKey
+ var id: String = ""
+ var name: String = ""
+ var backgroundColor: String = ""
+ var isDisabled: Boolean = false
+ var flashCards: RealmList = realmListOf()
+
+ constructor(
+ id: String,
+ name: String,
+ backgroundColor: String = "",
+ flashCards: RealmList
+ ) {
+ this.id = id
+ this.name = name
+ this.backgroundColor = backgroundColor
+ this.flashCards = flashCards
+ }
+
+ constructor() {}
+}
+
+fun Board.toEntity() =
+ BoardEntity(
+ id = id,
+ name = name,
+ backgroundColor = backgroundColor,
+ flashCards = flashCards.toEntity()
+ )
+
+fun BoardEntity.toLocal() =
+ Board(
+ id = id,
+ name = name,
+ backgroundColor = backgroundColor,
+ flashCards = flashCards.toLocal().toRealmList()
+ )
+
+fun List.toEntity() = this.map { it.toEntity() }
+fun List.toLocal() = this.map { it.toLocal() }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/FlashCard.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/FlashCard.kt
new file mode 100644
index 0000000..5d4d46d
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/FlashCard.kt
@@ -0,0 +1,42 @@
+package com.san.englishbender.data.local.models
+
+import com.san.englishbender.domain.entities.FlashCardEntity
+import io.realm.kotlin.query.RealmResults
+import io.realm.kotlin.types.RealmObject
+import io.realm.kotlin.types.annotations.PrimaryKey
+
+open class FlashCard : RealmObject {
+ @PrimaryKey
+ var id: String = ""
+ var frontText: String = ""
+ var backText: String = ""
+ var isArchived: Boolean = false
+
+ constructor(id: String, frontText: String, backText: String, isArchived: Boolean = false) {
+ this.id = id
+ this.frontText = frontText
+ this.backText = backText
+ this.isArchived = isArchived
+ }
+
+ constructor() {}
+}
+
+fun FlashCard.toEntity() =
+ FlashCardEntity(
+ id = id,
+ frontText = frontText,
+ backText = backText,
+ isArchived = isArchived
+ )
+
+fun FlashCardEntity.toLocal() =
+ FlashCard(
+ id = id,
+ frontText = frontText,
+ backText = backText,
+ isArchived = isArchived
+ )
+
+fun List.toEntity() = this.map { it.toEntity() }
+fun List.toLocal() = this.map { it.toLocal() }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Record.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Record.kt
index daff1f6..2fe10a2 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Record.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Record.kt
@@ -1,6 +1,9 @@
package com.san.englishbender.data.local.models
+import com.san.englishbender.domain.entities.RecordEntity
+import com.san.englishbender.domain.entities.TagEntity
import io.realm.kotlin.ext.realmListOf
+import io.realm.kotlin.ext.toRealmList
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey
@@ -40,4 +43,39 @@ class Record : RealmObject {
}
constructor() {}
-}
\ No newline at end of file
+}
+
+fun Record.toEntity(): RecordEntity =
+ RecordEntity(
+ id = id,
+ title = title,
+ text = text,
+ plainText = plainText,
+ creationDate = creationDate,
+ isDeleted = isDeleted,
+ isDraft = isDraft,
+ backgroundColor = backgroundColor,
+ tags = tags.map {
+ TagEntity(
+ id = it.id,
+ name = it.name,
+ color = it.color
+ )
+ }
+ )
+
+fun RecordEntity.toLocal(): Record =
+ Record(
+ id = id,
+ title = title,
+ text = text,
+ plainText = plainText,
+ creationDate = creationDate,
+ isDeleted = isDeleted,
+ isDraft = isDraft,
+ backgroundColor = backgroundColor,
+ tags = tags?.map { Tag(it.id, it.name, it.color, it.isWhite) }?.toRealmList() ?: realmListOf()
+ )
+
+fun List.toEntity() = this.map { it.toEntity() }
+fun List.toLocal() = this.map { it.toLocal() }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Stats.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Stats.kt
index 5d0cd17..ba841f4 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Stats.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Stats.kt
@@ -1,5 +1,6 @@
package com.san.englishbender.data.local.models
+import com.san.englishbender.domain.entities.StatsEntity
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey
@@ -17,4 +18,14 @@ class Stats : RealmObject {
}
constructor() {}
-}
\ No newline at end of file
+}
+
+fun Stats.toEntity() =
+ StatsEntity(
+ recordsCount = this.recordsCount,
+ wordsCount = this.wordsCount,
+ lettersCount = this.lettersCount
+ )
+
+fun StatsEntity.toLocal() =
+ Stats(this.recordsCount, this.wordsCount, this.lettersCount)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Tag.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Tag.kt
index 3b8a95c..333a45d 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Tag.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/local/models/Tag.kt
@@ -1,5 +1,6 @@
package com.san.englishbender.data.local.models
+import com.san.englishbender.domain.entities.TagEntity
import io.realm.kotlin.ext.backlinks
import io.realm.kotlin.query.RealmResults
import io.realm.kotlin.types.RealmObject
@@ -21,4 +22,23 @@ open class Tag : RealmObject {
}
constructor() {}
-}
\ No newline at end of file
+}
+
+fun Tag.toEntity() =
+ TagEntity(
+ id = id,
+ name = name,
+ color = color,
+ isWhite = isWhite
+ )
+
+fun TagEntity.toLocal() =
+ Tag(
+ id = id,
+ name = name,
+ color = color,
+ isWhite = isWhite
+ )
+
+fun List.toEntity() = this.map { it.toEntity() }
+fun List.toLocal() = this.map { it.toLocal() }
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/BoardsRepository.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/BoardsRepository.kt
new file mode 100644
index 0000000..a2ad6dc
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/BoardsRepository.kt
@@ -0,0 +1,121 @@
+package com.san.englishbender.data.repositories
+
+import com.san.englishbender.core.extensions.doQuery
+import com.san.englishbender.data.local.models.Board
+import com.san.englishbender.data.local.models.FlashCard
+import com.san.englishbender.data.local.models.toEntity
+import com.san.englishbender.data.local.models.toLocal
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.entities.FlashCardEntity
+import com.san.englishbender.domain.repositories.IBoardsRepository
+import com.san.englishbender.ioDispatcher
+import io.realm.kotlin.Realm
+import io.realm.kotlin.UpdatePolicy
+import io.realm.kotlin.ext.query
+import io.realm.kotlin.notifications.DeletedList
+import io.realm.kotlin.notifications.InitialList
+import io.realm.kotlin.notifications.InitialObject
+import io.realm.kotlin.notifications.InitialResults
+import io.realm.kotlin.notifications.ListChange
+import io.realm.kotlin.notifications.SingleQueryChange
+import io.realm.kotlin.notifications.UpdatedList
+import io.realm.kotlin.notifications.UpdatedObject
+import io.realm.kotlin.notifications.UpdatedResults
+import io.realm.kotlin.query.find
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+
+class BoardsRepository(
+ private val realm: Realm
+) : IBoardsRepository {
+ override fun getBoardsFlow(): Flow> = flow {
+ realm.query(Board::class).asFlow().collect { changes ->
+ when (changes) {
+ is InitialResults,
+ is UpdatedResults -> emit(changes.list.toList().toEntity())
+
+ else -> {}
+ }
+ }
+ }.flowOn(ioDispatcher)
+
+ override suspend fun getBoards(): List = doQuery {
+ realm.query(Board::class).find().map { it.toEntity() }
+ }
+
+ override suspend fun getBoard(id: String): BoardEntity? = doQuery {
+ realm.query("id == $0", id).first().find()?.toEntity()
+ }
+
+ override fun getBoardAsFlow(id: String): Flow = flow {
+ realm.query("id == $0", id)
+ .first()
+ .asFlow()
+ .collect { changes: SingleQueryChange ->
+ when (changes) {
+ is InitialObject<*>,
+ is UpdatedObject<*> -> changes.obj?.toEntity()?.let { emit(it) }
+ else -> {}
+ }
+ }
+ }.flowOn(ioDispatcher)
+
+ override fun getFlashCardsAsFlow(id: String): Flow> = flow {
+ realm.query("id == $0", id)
+ .first()
+ .find()
+ ?.also { board ->
+ board.flashCards
+ .asFlow()
+ .collect { listChange: ListChange ->
+ when (listChange) {
+ is InitialList,
+ is UpdatedList,
+ is DeletedList -> emit(
+ listChange.list.filter { !it.isArchived }.toEntity()
+ )
+ }
+ }
+ }
+ }.flowOn(ioDispatcher)
+
+ override suspend fun saveBoard(board: BoardEntity): Unit = doQuery {
+ realm.write { copyToRealm(board.toLocal(), UpdatePolicy.ALL) }
+ }
+
+ override suspend fun addFlashCardToBoard(boardId: String, flashCard: FlashCardEntity): Unit =
+ doQuery {
+ realm.write {
+ val board = this.query("id == $0", boardId).first().find()
+ board?.flashCards?.add(flashCard.toLocal())
+ }
+ }
+
+ override suspend fun saveFlashCard(card: FlashCardEntity): Unit = doQuery {
+ realm.write { copyToRealm(card.toLocal(), UpdatePolicy.ALL) }
+ }
+
+// override suspend fun sendCardToArchive(boardId: String, cardId: String): Unit =
+// doQuery {
+// realm.write {
+// val board = this.query("id == $0", boardId).first().find()
+// board?.flashCards?.find { it.id == cardId }?.isArchived = true
+// }
+// }
+
+ override suspend fun deleteBoard(boardId: String): Unit = doQuery {
+ realm.write {
+ val board = query("id == $0", boardId).find()
+ delete(board)
+ }
+ }
+
+ override suspend fun deleteFlashCard(cardId: String): Unit = doQuery {
+ realm.write {
+ val card = query("id == $0", cardId).find()
+ delete(card)
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/FlashCardsRepository.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/FlashCardsRepository.kt
new file mode 100644
index 0000000..fe28feb
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/FlashCardsRepository.kt
@@ -0,0 +1,46 @@
+package com.san.englishbender.data.repositories
+
+import com.san.englishbender.core.extensions.doQuery
+import com.san.englishbender.data.local.models.FlashCard
+import com.san.englishbender.data.local.models.toEntity
+import com.san.englishbender.data.local.models.toLocal
+import com.san.englishbender.domain.entities.FlashCardEntity
+import com.san.englishbender.domain.repositories.IFlashCardsRepository
+import com.san.englishbender.ioDispatcher
+import io.realm.kotlin.Realm
+import io.realm.kotlin.UpdatePolicy
+import io.realm.kotlin.ext.query
+import io.realm.kotlin.notifications.InitialResults
+import io.realm.kotlin.notifications.UpdatedResults
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+
+class FlashCardsRepository(
+ private val realm: Realm
+): IFlashCardsRepository {
+ override fun getFlashCardsFlow(boardId: String): Flow> = flow {
+ realm.query("").asFlow().collect { changes ->
+ when (changes) {
+ is InitialResults,
+ is UpdatedResults -> emit(changes.list.toList().toEntity())
+ else -> {}
+ }
+ }
+ }.flowOn(ioDispatcher)
+
+ override suspend fun getFlashCards(): List = doQuery {
+ realm.query(FlashCard::class).find().map { it.toEntity() }
+ }
+
+// override suspend fun saveFlashCard(card: FlashCardEntity): Unit = doQuery {
+// realm.write { copyToRealm(card.toLocal(), UpdatePolicy.ALL) }
+// }
+
+ override suspend fun deleteFlashCard(cardId: String): Unit = doQuery {
+ realm.write {
+ val card = query("id == $0", cardId).find()
+ delete(card)
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/RecordsRepository.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/RecordsRepository.kt
index fdba55a..a2247bf 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/RecordsRepository.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/RecordsRepository.kt
@@ -1,10 +1,9 @@
package com.san.englishbender.data.repositories
import com.san.englishbender.core.extensions.doQuery
-import com.san.englishbender.data.local.mappers.toEntity
-import com.san.englishbender.data.local.mappers.toLocal
import com.san.englishbender.data.local.models.Record
-import com.san.englishbender.data.local.models.Tag
+import com.san.englishbender.data.local.models.toEntity
+import com.san.englishbender.data.local.models.toLocal
import com.san.englishbender.domain.entities.RecordEntity
import com.san.englishbender.domain.repositories.IRecordsRepository
import com.san.englishbender.ioDispatcher
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/StatsRepository.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/StatsRepository.kt
index 34b876f..447d008 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/StatsRepository.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/StatsRepository.kt
@@ -1,9 +1,9 @@
package com.san.englishbender.data.repositories
import com.san.englishbender.core.extensions.doQuery
-import com.san.englishbender.data.local.mappers.toEntity
-import com.san.englishbender.data.local.mappers.toLocal
import com.san.englishbender.data.local.models.Stats
+import com.san.englishbender.data.local.models.toEntity
+import com.san.englishbender.data.local.models.toLocal
import com.san.englishbender.domain.entities.StatsEntity
import com.san.englishbender.domain.repositories.IStatsRepository
import com.san.englishbender.ioDispatcher
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/TagsRepository.kt b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/TagsRepository.kt
index 9cb30a9..18e3b55 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/TagsRepository.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/data/repositories/TagsRepository.kt
@@ -2,10 +2,10 @@ package com.san.englishbender.data.repositories
import com.san.englishbender.core.extensions.doQuery
import com.san.englishbender.data.local.dataStore.IDataStore
-import com.san.englishbender.data.local.mappers.toEntity
-import com.san.englishbender.data.local.mappers.toLocal
import com.san.englishbender.data.local.models.AppSettings
import com.san.englishbender.data.local.models.Tag
+import com.san.englishbender.data.local.models.toEntity
+import com.san.englishbender.data.local.models.toLocal
import com.san.englishbender.domain.entities.TagEntity
import com.san.englishbender.domain.repositories.ITagsRepository
import com.san.englishbender.ioDispatcher
@@ -16,14 +16,24 @@ import io.realm.kotlin.ext.query
import io.realm.kotlin.notifications.InitialResults
import io.realm.kotlin.notifications.UpdatedResults
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
class TagsRepository(
private val realm: Realm,
- private val dataStore: IDataStore
+// private val dataStore: IDataStore
) : ITagsRepository {
+// val tags = realm
+// .query()
+// .asFlow()
+// .map { result -> result.list.toList().toEntity() }
+// .flowOn(ioDispatcher)
+
override fun getAllTagsFlow(): Flow> = flow {
realm.query(Tag::class).asFlow().collect { changes ->
when (changes) {
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/BoardEntity.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/BoardEntity.kt
new file mode 100644
index 0000000..15d2303
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/BoardEntity.kt
@@ -0,0 +1,13 @@
+package com.san.englishbender.domain.entities
+
+import com.san.englishbender.CommonParcelable
+import com.san.englishbender.CommonParcelize
+import com.san.englishbender.randomUUID
+
+@CommonParcelize
+data class BoardEntity(
+ val id: String = randomUUID(),
+ var name: String = "",
+ var backgroundColor: String = "",
+ var flashCards: List = emptyList()
+) : CommonParcelable
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/FlashCardEntity.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/FlashCardEntity.kt
new file mode 100644
index 0000000..da38ebd
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/FlashCardEntity.kt
@@ -0,0 +1,15 @@
+package com.san.englishbender.domain.entities
+
+import com.san.englishbender.CommonParcelable
+import com.san.englishbender.CommonParcelize
+import com.san.englishbender.randomUUID
+import kotlinx.serialization.Serializable
+
+@Serializable
+@CommonParcelize
+data class FlashCardEntity(
+ var id: String = randomUUID(),
+ var frontText: String = "",
+ var backText: String = "",
+ var isArchived: Boolean = false
+) : CommonParcelable
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/RecordEntity.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/RecordEntity.kt
index 9376845..b9e5375 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/RecordEntity.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/entities/RecordEntity.kt
@@ -1,8 +1,10 @@
package com.san.englishbender.domain.entities
+import androidx.compose.runtime.Immutable
import com.san.englishbender.CommonParcelable
import com.san.englishbender.CommonParcelize
+@Immutable
@CommonParcelize
data class RecordEntity(
var title: String = "",
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/repositories/IBoardsRepository.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/repositories/IBoardsRepository.kt
new file mode 100644
index 0000000..5a9072c
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/repositories/IBoardsRepository.kt
@@ -0,0 +1,19 @@
+package com.san.englishbender.domain.repositories
+
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.entities.FlashCardEntity
+import kotlinx.coroutines.flow.Flow
+
+interface IBoardsRepository {
+ fun getBoardsFlow() : Flow>
+ suspend fun getBoards() : List
+ suspend fun getBoard(id: String) : BoardEntity?
+ fun getBoardAsFlow(id: String) : Flow
+ fun getFlashCardsAsFlow(id: String): Flow>
+// suspend fun sendCardToArchive(boardId: String, cardId: String)
+ suspend fun saveBoard(board: BoardEntity)
+ suspend fun addFlashCardToBoard(boardId: String, flashCard: FlashCardEntity)
+ suspend fun saveFlashCard(card: FlashCardEntity)
+ suspend fun deleteBoard(boardId: String)
+ suspend fun deleteFlashCard(cardId: String)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/repositories/IFlashCardsRepository.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/repositories/IFlashCardsRepository.kt
new file mode 100644
index 0000000..808b529
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/repositories/IFlashCardsRepository.kt
@@ -0,0 +1,11 @@
+package com.san.englishbender.domain.repositories
+
+import com.san.englishbender.domain.entities.FlashCardEntity
+import kotlinx.coroutines.flow.Flow
+
+interface IFlashCardsRepository {
+ fun getFlashCardsFlow(boardId: String) : Flow>
+ suspend fun getFlashCards() : List
+// suspend fun saveFlashCard(card: FlashCardEntity)
+ suspend fun deleteFlashCard(cardId: String)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/AddFlashCardToBoardUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/AddFlashCardToBoardUseCase.kt
new file mode 100644
index 0000000..fb1ae0b
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/AddFlashCardToBoardUseCase.kt
@@ -0,0 +1,9 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.entities.FlashCardEntity
+import com.san.englishbender.domain.repositories.IBoardsRepository
+
+class AddFlashCardToBoardUseCase(private val boardRepository: IBoardsRepository) {
+ suspend operator fun invoke(boardId: String, flashCardEntity: FlashCardEntity) =
+ boardRepository.addFlashCardToBoard(boardId, flashCardEntity)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/DeleteBoardUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/DeleteBoardUseCase.kt
new file mode 100644
index 0000000..bd33131
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/DeleteBoardUseCase.kt
@@ -0,0 +1,10 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.repositories.IBoardsRepository
+
+class DeleteBoardUseCase(
+ private val boardRepository: IBoardsRepository
+) {
+ suspend operator fun invoke(boardId: String): Unit =
+ boardRepository.deleteBoard(boardId)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/DeleteFlashCardUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/DeleteFlashCardUseCase.kt
new file mode 100644
index 0000000..1da3055
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/DeleteFlashCardUseCase.kt
@@ -0,0 +1,10 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.repositories.IBoardsRepository
+
+class DeleteFlashCardUseCase(
+ private val boardRepository: IBoardsRepository
+) {
+ suspend operator fun invoke(cardId: String): Unit =
+ boardRepository.deleteFlashCard(cardId)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardAsFlowUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardAsFlowUseCase.kt
new file mode 100644
index 0000000..1ebb1dd
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardAsFlowUseCase.kt
@@ -0,0 +1,12 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.repositories.IBoardsRepository
+import kotlinx.coroutines.flow.Flow
+
+class GetBoardAsFlowUseCase(
+ private val boardRepository: IBoardsRepository
+) {
+ operator fun invoke(boardId: String): Flow =
+ boardRepository.getBoardAsFlow(boardId)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardByIdUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardByIdUseCase.kt
new file mode 100644
index 0000000..18bb8e7
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardByIdUseCase.kt
@@ -0,0 +1,15 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.repositories.IBoardsRepository
+import kotlinx.coroutines.flow.Flow
+
+class GetBoardByIdUseCase(
+ private val boardRepository: IBoardsRepository
+) {
+ suspend operator fun invoke(boardId: String): BoardEntity? =
+ boardRepository.getBoard(boardId)
+
+// operator fun invoke(boardId: String): Flow =
+// boardRepository.getBoardFlow(boardId)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardsFlowUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardsFlowUseCase.kt
new file mode 100644
index 0000000..a05d745
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetBoardsFlowUseCase.kt
@@ -0,0 +1,11 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.repositories.IBoardsRepository
+import kotlinx.coroutines.flow.Flow
+
+class GetBoardsFlowUseCase(
+ private val boardsRepository: IBoardsRepository
+) {
+ operator fun invoke(): Flow> = boardsRepository.getBoardsFlow()
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetFlashCardsAsFlowUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetFlashCardsAsFlowUseCase.kt
new file mode 100644
index 0000000..a2f0bc1
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/GetFlashCardsAsFlowUseCase.kt
@@ -0,0 +1,12 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.entities.FlashCardEntity
+import com.san.englishbender.domain.repositories.IBoardsRepository
+import kotlinx.coroutines.flow.Flow
+
+class GetFlashCardsAsFlowUseCase(
+ private val boardRepository: IBoardsRepository
+) {
+ operator fun invoke(boardId: String): Flow> =
+ boardRepository.getFlashCardsAsFlow(boardId)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/SaveBoardUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/SaveBoardUseCase.kt
new file mode 100644
index 0000000..083a240
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/SaveBoardUseCase.kt
@@ -0,0 +1,8 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.repositories.IBoardsRepository
+
+class SaveBoardUseCase(private val boardRepository: IBoardsRepository) {
+ suspend operator fun invoke(board: BoardEntity) = boardRepository.saveBoard(board)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/SaveFlashCardUseCase.kt b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/SaveFlashCardUseCase.kt
new file mode 100644
index 0000000..3bf4147
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/domain/usecases/flashCards/SaveFlashCardUseCase.kt
@@ -0,0 +1,8 @@
+package com.san.englishbender.domain.usecases.flashCards
+
+import com.san.englishbender.domain.entities.FlashCardEntity
+import com.san.englishbender.domain.repositories.IBoardsRepository
+
+class SaveFlashCardUseCase(private val boardRepository: IBoardsRepository) {
+ suspend operator fun invoke(card: FlashCardEntity) = boardRepository.saveFlashCard(card)
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/ui/flashcards/BoardsViewModel.kt b/shared/src/commonMain/kotlin/com/san/englishbender/ui/flashcards/BoardsViewModel.kt
new file mode 100644
index 0000000..60e0cd0
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/ui/flashcards/BoardsViewModel.kt
@@ -0,0 +1,114 @@
+package com.san.englishbender.ui.flashcards
+
+import androidx.compose.runtime.Immutable
+import com.san.englishbender.SharedFileReader
+import com.san.englishbender.SharedRes
+import com.san.englishbender.core.extensions.WhileUiSubscribed
+import com.san.englishbender.data.getResultFlow
+import com.san.englishbender.data.onFailure
+import com.san.englishbender.data.onSuccess
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.entities.FlashCardEntity
+import com.san.englishbender.domain.usecases.flashCards.DeleteBoardUseCase
+import com.san.englishbender.domain.usecases.flashCards.GetBoardsFlowUseCase
+import com.san.englishbender.domain.usecases.flashCards.SaveBoardUseCase
+import com.san.englishbender.ui.ViewModel
+import dev.icerock.moko.resources.StringResource
+import io.github.aakira.napier.log
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+@Immutable
+data class BoardsUiState(
+ val isLoading: Boolean = false,
+ val boards: List = emptyList(),
+ val userMessage: StringResource? = null
+)
+
+class BoardsViewModel(
+ private val getBoardsFlowUseCase: GetBoardsFlowUseCase,
+ private val saveBoardUseCase: SaveBoardUseCase,
+ private val deleteBoardUseCase: DeleteBoardUseCase
+) : ViewModel() {
+
+ val uiState: StateFlow =
+ getBoardsFlowUseCase()
+ .map { BoardsUiState(boards = it) }
+ .catch { BoardsUiState(userMessage = SharedRes.strings.loading_records_error) }
+ .stateIn(
+ scope = viewModelScope,
+ started = WhileUiSubscribed,
+ initialValue = BoardsUiState(isLoading = true)
+ )
+
+ fun saveBoard(board: BoardEntity) = safeLaunch {
+ getResultFlow { saveBoardUseCase(board) }
+ .onFailure { showError(SharedRes.strings.remove_record_error) }
+ .onSuccess {}
+ }
+
+ @Serializable
+ data class CardsContainer(
+ val cards: List = emptyList()
+ )
+
+ fun json() = safeLaunch {
+ val listOfCards = CardsContainer(
+ cards = listOf(
+ FlashCardEntity(
+ frontText = "Hustle",
+ backText = "Full of activity"
+ ),
+ FlashCardEntity(
+ frontText = "serene",
+ backText = "calm, peaceful, and untroubled; tranquil"
+ )
+ )
+ )
+ log(tag = "decodeFromString") { "encodeToString" }
+
+ val cardJson = "{\"frontText\":\"Hustle\",\"backText\":\"Full of activity\" }"
+
+ try {
+// val jsonString = Json.encodeToString(listOfCards)
+// log(tag = "decodeFromString") { "jsonString: $jsonString" }
+
+ val card = Json.decodeFromString(cardJson)
+// val cards = Json.decodeFromString(jsonString)
+ log(tag = "decodeFromString") { "card: $card" }
+// log(tag = "decodeFromString") { "cards: $cards" }
+ } catch (e: Exception) {
+ log(tag = "decodeFromString") { "e: $e" }
+ }
+
+ }
+
+ private val sharedFileReader: SharedFileReader = SharedFileReader()
+ fun loadAndParseJsonFile() {
+ val jsonString = sharedFileReader.loadJsonFile("commonWords.json")
+// val commonWords = sharedFileReader.loadJsonFile("commonWords.json")
+
+ log(tag = "decodeFromString") { "jsonString: $jsonString" }
+ }
+
+ fun deleteBoard(boardId: String) = safeLaunch {
+ getResultFlow { deleteBoardUseCase(boardId) }
+ .onFailure { showError(SharedRes.strings.remove_record_error) }
+ .onSuccess {}
+ }
+
+ private fun showError(message: StringResource) = safeLaunch {
+// uiState.update {
+// it.copy(
+// isLoading = false,
+// userMessage = message
+// )
+// }
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/ui/flashcards/FlashCardsViewModel.kt b/shared/src/commonMain/kotlin/com/san/englishbender/ui/flashcards/FlashCardsViewModel.kt
new file mode 100644
index 0000000..013aa3d
--- /dev/null
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/ui/flashcards/FlashCardsViewModel.kt
@@ -0,0 +1,118 @@
+package com.san.englishbender.ui.flashcards
+
+import androidx.compose.runtime.Immutable
+import com.san.englishbender.SharedRes
+import com.san.englishbender.data.getResultFlow
+import com.san.englishbender.data.onFailure
+import com.san.englishbender.data.onSuccess
+import com.san.englishbender.domain.entities.BoardEntity
+import com.san.englishbender.domain.entities.FlashCardEntity
+import com.san.englishbender.domain.usecases.flashCards.AddFlashCardToBoardUseCase
+import com.san.englishbender.domain.usecases.flashCards.DeleteFlashCardUseCase
+import com.san.englishbender.domain.usecases.flashCards.GetBoardAsFlowUseCase
+import com.san.englishbender.domain.usecases.flashCards.GetFlashCardsAsFlowUseCase
+import com.san.englishbender.domain.usecases.flashCards.SaveFlashCardUseCase
+import com.san.englishbender.ui.ViewModel
+import dev.icerock.moko.resources.StringResource
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.update
+
+@Immutable
+data class FlashCardsUiState(
+ val isLoading: Boolean = false,
+ val board: BoardEntity? = null,
+ val flashCards: List = emptyList(),
+ val userMessage: StringResource? = null
+)
+
+class FlashCardsViewModel(
+ private val getBoardAsFlowUseCase: GetBoardAsFlowUseCase,
+ private val getFlashCardsAsFlowUseCase: GetFlashCardsAsFlowUseCase,
+ private val addFlashCardToBoardUseCase: AddFlashCardToBoardUseCase,
+ private val saveFlashCardUseCase: SaveFlashCardUseCase,
+ private val deleteFlashCardUseCase: DeleteFlashCardUseCase
+) : ViewModel() {
+
+ private val _uiState = MutableStateFlow(FlashCardsUiState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ fun observeBoard(boardId: String) = safeLaunch {
+ combine(
+ getBoardAsFlowUseCase(boardId),
+ getFlashCardsAsFlowUseCase(boardId)
+ ) { boardEntity, flashCards ->
+ _uiState.update { state ->
+ state.copy(
+ isLoading = false,
+ board = boardEntity,
+ flashCards = flashCards
+ )
+ }
+ }.launchIn(viewModelScope)
+ }
+
+// fun getBoard(boardId: String) = safeLaunch {
+// getBoardAsFlowUseCase(boardId)
+// .catch { showError(SharedRes.strings.remove_record_error) }
+// .collect { boardEntity ->
+// if (boardEntity.isNull) {
+// showError(SharedRes.strings.remove_record_error)
+// return@collect
+// }
+// _uiState.update { state ->
+// state.copy(
+// isLoading = false,
+// board = boardEntity
+// )
+// }
+// }
+// }
+//
+// fun getFlashCards(boardId: String) = safeLaunch {
+// getFlashCardsAsFlowUseCase(boardId)
+// .catch { showError(SharedRes.strings.remove_record_error) }
+// .collect { flashCards ->
+// if (flashCards.isNull) {
+// showError(SharedRes.strings.remove_record_error)
+// return@collect
+// }
+// _uiState.update { state ->
+// state.copy(
+// isLoading = false,
+// flashCards = flashCards
+// )
+// }
+// }
+// }
+
+ fun addCardToBoard(board: BoardEntity, flashCard: FlashCardEntity) = safeLaunch {
+ getResultFlow { addFlashCardToBoardUseCase(board.id, flashCard) }
+ .onFailure { showError(SharedRes.strings.remove_record_error) }
+ .onSuccess {}
+ }
+
+ fun saveCard(card: FlashCardEntity) = safeLaunch {
+ getResultFlow { saveFlashCardUseCase(card) }
+ .onFailure {}
+ .onSuccess {}
+ }
+
+ fun deleteFlashCard(cardId: String) = safeLaunch {
+ getResultFlow { deleteFlashCardUseCase(cardId) }
+ .onFailure { showError(SharedRes.strings.remove_record_error) }
+ .onSuccess {}
+ }
+
+ private fun showError(message: StringResource) = safeLaunch {
+ _uiState.update {
+ it.copy(
+ isLoading = false,
+ userMessage = message
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/ui/recordDetails/RecordDetailsViewModel.kt b/shared/src/commonMain/kotlin/com/san/englishbender/ui/recordDetails/RecordDetailsViewModel.kt
index f76b737..2f0a5c5 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/ui/recordDetails/RecordDetailsViewModel.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/ui/recordDetails/RecordDetailsViewModel.kt
@@ -1,5 +1,6 @@
package com.san.englishbender.ui.recordDetails
+import androidx.compose.runtime.mutableStateOf
import com.aallam.openai.api.BetaOpenAI
import com.aallam.openai.api.chat.ChatCompletionRequest
import com.aallam.openai.api.chat.ChatMessage
@@ -12,8 +13,8 @@ import com.san.englishbender.SharedRes
import com.san.englishbender.core.Event
import com.san.englishbender.core.navigation.Navigator
import com.san.englishbender.data.getResultFlow
-import com.san.englishbender.data.ifFailure
-import com.san.englishbender.data.ifSuccess
+import com.san.englishbender.data.onFailure
+import com.san.englishbender.data.onSuccess
import com.san.englishbender.domain.entities.RecordEntity
import com.san.englishbender.domain.entities.TagEntity
import com.san.englishbender.domain.entities.isNotEqual
@@ -62,11 +63,9 @@ class RecordDetailsViewModel(
private var prevText: String = ""
private val results = mutableListOf()
- fun getRecord(recordId: String?) {
- val recId = recordId ?: return
-
+ fun getRecord(recordId: String) {
combine(
- getRecordFlowUseCase(recId),
+ getRecordFlowUseCase(recordId),
getTagsFlowUseCase()
) { recordEntity, tags ->
_uiState.update { state ->
@@ -96,11 +95,11 @@ class RecordDetailsViewModel(
currRecordState.tags = selectedTags
getResultFlow { saveRecordUseCase(currRecordState) }
- .ifFailure {
+ .onFailure {
saveInProgress = false
showUserMessage(SharedRes.strings.save_record_error)
}
- .ifSuccess {
+ .onSuccess {
updateStatsUseCase(
prevRecordState = prevRecordState,
currRecordState = currRecordState
diff --git a/shared/src/commonMain/kotlin/com/san/englishbender/ui/records/RecordsViewModel.kt b/shared/src/commonMain/kotlin/com/san/englishbender/ui/records/RecordsViewModel.kt
index 98cfd60..eb12f4c 100644
--- a/shared/src/commonMain/kotlin/com/san/englishbender/ui/records/RecordsViewModel.kt
+++ b/shared/src/commonMain/kotlin/com/san/englishbender/ui/records/RecordsViewModel.kt
@@ -3,8 +3,8 @@ package com.san.englishbender.ui.records
import com.san.englishbender.SharedRes
import com.san.englishbender.core.extensions.WhileUiSubscribed
import com.san.englishbender.data.getResultFlow
-import com.san.englishbender.data.ifFailure
-import com.san.englishbender.data.ifSuccess
+import com.san.englishbender.data.onFailure
+import com.san.englishbender.data.onSuccess
import com.san.englishbender.domain.entities.RecordEntity
import com.san.englishbender.domain.entities.TagEntity
import com.san.englishbender.domain.usecases.records.GetRecordsUseCase
@@ -42,8 +42,8 @@ class RecordsViewModel(
fun removeRecord(record: RecordEntity) = safeLaunch {
getResultFlow { removeRecordUseCase(record) }
- .ifFailure { RecordsUiState(userMessage = SharedRes.strings.remove_record_error) }
- .ifSuccess {
+ .onFailure { RecordsUiState(userMessage = SharedRes.strings.remove_record_error) }
+ .onSuccess {
log(tag = "ExceptionHandling") { "getResultFlow success" }
}
}
diff --git a/shared/src/commonMain/resources/commonWords.json b/shared/src/commonMain/resources/commonWords.json
new file mode 100644
index 0000000..3afd66f
--- /dev/null
+++ b/shared/src/commonMain/resources/commonWords.json
@@ -0,0 +1,16 @@
+{
+ "cards": [
+ {
+ "frontText":"Word1",
+ "backText":"Full of activity"
+ },
+ {
+ "frontText":"Word2",
+ "backText":"Full of activity"
+ },
+ {
+ "frontText":"Word3",
+ "backText":"Full of activity"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/shared/src/iosMain/kotlin/com/san/englishbender/Platform.kt b/shared/src/iosMain/kotlin/com/san/englishbender/Platform.kt
index dde8222..370507f 100644
--- a/shared/src/iosMain/kotlin/com/san/englishbender/Platform.kt
+++ b/shared/src/iosMain/kotlin/com/san/englishbender/Platform.kt
@@ -65,4 +65,35 @@ actual class Strings {
id.format(*args.toTypedArray()).localized()
}
}
+}
+
+internal actual class SharedFileReader{
+ private val bundle: NSBundle = NSBundle.bundleForClass(BundleMarker)
+
+ actual fun loadJsonFile(fileName: String): String? {
+ val (filename, type) = when (val lastPeriodIndex = fileName.lastIndexOf('.')) {
+ 0 -> {
+ null to fileName.drop(1)
+ }
+ in 1..Int.MAX_VALUE -> {
+ fileName.take(lastPeriodIndex) to fileName.drop(lastPeriodIndex + 1)
+ }
+ else -> {
+ fileName to null
+ }
+ }
+ val path = bundle.pathForResource(filename, type) ?: error("Couldn't get path of $fileName (parsed as: ${listOfNotNull(filename, type).joinToString(".")})")
+
+ return memScoped {
+ val errorPtr = alloc>()
+
+ NSString.stringWithContentsOfFile(path, encoding = NSUTF8StringEncoding, error = errorPtr.ptr) ?: run {
+ error("Couldn't load resource: $fileName. Error: ${errorPtr.value?.localizedDescription}")
+ }
+ }
+ }
+
+ private class BundleMarker : NSObject() {
+ companion object : NSObjectMeta()
+ }
}
\ No newline at end of file