Skip to content

Commit 4ffa59d

Browse files
committed
add glossary to android
1 parent 034c336 commit 4ffa59d

File tree

4 files changed

+214
-29
lines changed

4 files changed

+214
-29
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package ireader.presentation.ui.reader.components
2+
3+
import android.content.Context
4+
import android.net.Uri
5+
import androidx.activity.compose.rememberLauncherForActivityResult
6+
import androidx.activity.result.contract.ActivityResultContracts
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.getValue
9+
import androidx.compose.runtime.mutableStateOf
10+
import androidx.compose.runtime.remember
11+
import androidx.compose.runtime.setValue
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.platform.LocalContext
14+
import ireader.domain.models.entities.Glossary
15+
import ireader.domain.models.entities.GlossaryTermType
16+
import ireader.i18n.UiText
17+
18+
@Composable
19+
actual fun GlossaryDialogWithFilePickers(
20+
glossaryEntries: List<Glossary>,
21+
bookTitle: String?,
22+
onDismiss: () -> Unit,
23+
onAddEntry: (String, String, GlossaryTermType, String?) -> Unit,
24+
onEditEntry: (Glossary) -> Unit,
25+
onDeleteEntry: (Long) -> Unit,
26+
onExportGlossary: ((String) -> Unit) -> Unit,
27+
onImportGlossary: (String) -> Unit,
28+
onShowSnackBar: (UiText) -> Unit,
29+
modifier: Modifier
30+
) {
31+
val context = LocalContext.current
32+
var showExportPicker by remember { mutableStateOf(false) }
33+
var showImportPicker by remember { mutableStateOf(false) }
34+
var exportJson by remember { mutableStateOf<String?>(null) }
35+
36+
// Export file picker
37+
val exportLauncher = rememberLauncherForActivityResult(
38+
contract = ActivityResultContracts.CreateDocument("application/json")
39+
) { uri: Uri? ->
40+
if (uri != null && exportJson != null) {
41+
try {
42+
writeToUri(context, uri, exportJson!!)
43+
onShowSnackBar(UiText.DynamicString("Glossary exported successfully"))
44+
} catch (e: Exception) {
45+
onShowSnackBar(UiText.ExceptionString(e))
46+
}
47+
}
48+
exportJson = null
49+
showExportPicker = false
50+
}
51+
52+
// Import file picker
53+
val importLauncher = rememberLauncherForActivityResult(
54+
contract = ActivityResultContracts.OpenDocument()
55+
) { uri: Uri? ->
56+
if (uri != null) {
57+
try {
58+
val inputStream = context.contentResolver.openInputStream(uri)
59+
val content = inputStream?.readBytes() ?: ByteArray(0)
60+
inputStream?.close()
61+
val json = content.decodeToString()
62+
onImportGlossary(json)
63+
} catch (e: Exception) {
64+
onShowSnackBar(UiText.ExceptionString(e))
65+
}
66+
}
67+
showImportPicker = false
68+
}
69+
70+
// Trigger export
71+
if (showExportPicker && exportJson != null) {
72+
val fileName = "${bookTitle ?: "glossary"}_glossary.json"
73+
exportLauncher.launch(fileName)
74+
}
75+
76+
// Trigger import
77+
if (showImportPicker) {
78+
importLauncher.launch(arrayOf("application/json", "application/*", "*/*"))
79+
}
80+
81+
GlossaryDialog(
82+
glossaryEntries = glossaryEntries,
83+
onDismiss = onDismiss,
84+
onAddEntry = onAddEntry,
85+
onEditEntry = onEditEntry,
86+
onDeleteEntry = onDeleteEntry,
87+
onExport = {
88+
onExportGlossary { json ->
89+
exportJson = json
90+
showExportPicker = true
91+
}
92+
},
93+
onImport = {
94+
showImportPicker = true
95+
},
96+
modifier = modifier
97+
)
98+
}
99+
100+
/**
101+
* Helper function to write content to URI on Android
102+
*/
103+
private fun writeToUri(context: Context, uri: Uri, content: String): Boolean {
104+
return try {
105+
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
106+
outputStream.write(content.toByteArray())
107+
}
108+
true
109+
} catch (e: Exception) {
110+
e.printStackTrace()
111+
false
112+
}
113+
}

presentation/src/commonMain/kotlin/ireader/presentation/ui/reader/ReaderScreen.kt

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp
4040
import ireader.domain.models.entities.Chapter
4141
import ireader.domain.preferences.prefs.ReadingMode
4242
import ireader.presentation.ui.core.ui.Colour.Transparent
43-
import ireader.presentation.ui.reader.components.GlossaryDialog
43+
import ireader.presentation.ui.reader.components.GlossaryDialogWithFilePickers
4444
import ireader.presentation.ui.reader.components.MainBottomSettingComposable
4545
import ireader.presentation.ui.reader.components.PreloadIndicator
4646
import ireader.presentation.ui.reader.components.TranslationBadge
@@ -279,8 +279,9 @@ fun ReadingScreen(
279279

280280
// Glossary dialog
281281
if (vm.translationState.showGlossaryDialog) {
282-
GlossaryDialog(
282+
GlossaryDialogWithFilePickers(
283283
glossaryEntries = vm.translationState.glossaryEntries,
284+
bookTitle = vm.book?.title,
284285
onDismiss = { vm.translationState.showGlossaryDialog = false },
285286
onAddEntry = { source, target, type, notes ->
286287
vm.addGlossaryEntry(source, target, type, notes)
@@ -291,34 +292,14 @@ fun ReadingScreen(
291292
onDeleteEntry = { id ->
292293
vm.deleteGlossaryEntry(id)
293294
},
294-
onExport = {
295-
vm.exportGlossary { json ->
296-
ireader.presentation.ui.util.FilePicker.pickFileForSave(
297-
title = "Export Glossary",
298-
defaultFileName = "${vm.book?.title ?: "glossary"}_glossary.json",
299-
onFileSelected = { path, _ ->
300-
try {
301-
java.io.File(path).writeText(json)
302-
vm.showSnackBar(ireader.i18n.UiText.DynamicString("Glossary exported successfully"))
303-
} catch (e: Exception) {
304-
vm.showSnackBar(ireader.i18n.UiText.ExceptionString(e))
305-
}
306-
}
307-
)
308-
}
295+
onExportGlossary = { onSuccess ->
296+
vm.exportGlossary(onSuccess)
297+
},
298+
onImportGlossary = { json ->
299+
vm.importGlossary(json)
309300
},
310-
onImport = {
311-
ireader.presentation.ui.util.FilePicker.pickFileForLoad(
312-
title = "Import Glossary",
313-
onFileSelected = { _, content ->
314-
try {
315-
val json = content.decodeToString()
316-
vm.importGlossary(json)
317-
} catch (e: Exception) {
318-
vm.showSnackBar(ireader.i18n.UiText.ExceptionString(e))
319-
}
320-
}
321-
)
301+
onShowSnackBar = { message ->
302+
vm.showSnackBar(message)
322303
}
323304
)
324305
}

presentation/src/commonMain/kotlin/ireader/presentation/ui/reader/components/GlossaryDialog.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@ import androidx.compose.ui.Modifier
4545
import androidx.compose.ui.unit.dp
4646
import ireader.domain.models.entities.Glossary
4747
import ireader.domain.models.entities.GlossaryTermType
48+
import ireader.i18n.UiText
49+
50+
/**
51+
* Wrapper composable that handles file picking for glossary import/export
52+
*/
53+
@Composable
54+
expect fun GlossaryDialogWithFilePickers(
55+
glossaryEntries: List<Glossary>,
56+
bookTitle: String?,
57+
onDismiss: () -> Unit,
58+
onAddEntry: (String, String, GlossaryTermType, String?) -> Unit,
59+
onEditEntry: (Glossary) -> Unit,
60+
onDeleteEntry: (Long) -> Unit,
61+
onExportGlossary: ((String) -> Unit) -> Unit,
62+
onImportGlossary: (String) -> Unit,
63+
onShowSnackBar: (UiText) -> Unit,
64+
modifier: Modifier = Modifier
65+
)
4866

4967
@OptIn(ExperimentalMaterial3Api::class)
5068
@Composable
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package ireader.presentation.ui.reader.components
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.ui.Modifier
5+
import ireader.domain.models.entities.Glossary
6+
import ireader.domain.models.entities.GlossaryTermType
7+
import ireader.i18n.UiText
8+
import java.awt.FileDialog
9+
import java.awt.Frame
10+
import java.io.File
11+
12+
@Composable
13+
actual fun GlossaryDialogWithFilePickers(
14+
glossaryEntries: List<Glossary>,
15+
bookTitle: String?,
16+
onDismiss: () -> Unit,
17+
onAddEntry: (String, String, GlossaryTermType, String?) -> Unit,
18+
onEditEntry: (Glossary) -> Unit,
19+
onDeleteEntry: (Long) -> Unit,
20+
onExportGlossary: ((String) -> Unit) -> Unit,
21+
onImportGlossary: (String) -> Unit,
22+
onShowSnackBar: (UiText) -> Unit,
23+
modifier: Modifier
24+
) {
25+
GlossaryDialog(
26+
glossaryEntries = glossaryEntries,
27+
onDismiss = onDismiss,
28+
onAddEntry = onAddEntry,
29+
onEditEntry = onEditEntry,
30+
onDeleteEntry = onDeleteEntry,
31+
onExport = {
32+
onExportGlossary { json ->
33+
val fileDialog = FileDialog(null as Frame?, "Export Glossary", FileDialog.SAVE)
34+
fileDialog.file = "${bookTitle ?: "glossary"}_glossary.json"
35+
fileDialog.isVisible = true
36+
37+
val directory = fileDialog.directory
38+
val filename = fileDialog.file
39+
40+
if (directory != null && filename != null) {
41+
try {
42+
val file = File(directory, filename)
43+
file.writeText(json)
44+
onShowSnackBar(UiText.DynamicString("Glossary exported successfully"))
45+
} catch (e: Exception) {
46+
onShowSnackBar(UiText.ExceptionString(e))
47+
}
48+
}
49+
}
50+
},
51+
onImport = {
52+
val fileDialog = FileDialog(null as Frame?, "Import Glossary", FileDialog.LOAD)
53+
fileDialog.file = "*.json"
54+
fileDialog.isVisible = true
55+
56+
val directory = fileDialog.directory
57+
val filename = fileDialog.file
58+
59+
if (directory != null && filename != null) {
60+
try {
61+
val file = File(directory, filename)
62+
if (file.exists()) {
63+
val json = file.readText()
64+
onImportGlossary(json)
65+
}
66+
} catch (e: Exception) {
67+
onShowSnackBar(UiText.ExceptionString(e))
68+
}
69+
}
70+
},
71+
modifier = modifier
72+
)
73+
}

0 commit comments

Comments
 (0)