Skip to content

Commit 0c11546

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents dc47d5b + 9cdb3b2 commit 0c11546

28 files changed

Lines changed: 1136 additions & 263 deletions

app/build.gradle.kts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,14 @@ dependencies {
8989
implementation(fileTree("libs") { include("*.jar") })
9090

9191
// Kotlin
92-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1")
92+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
9393
implementation("org.jetbrains.kotlin:kotlin-script-runtime:1.6.20")
9494
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.21")
9595

96+
// Needed to have the Task -> await extension.
97+
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.2")
98+
99+
96100
// Room components
97101
implementation("androidx.room:room-runtime:2.4.2")
98102
implementation("androidx.room:room-ktx:2.4.2")
@@ -169,13 +173,15 @@ dependencies {
169173
// Coil for jetpack compose
170174
implementation("io.coil-kt:coil-compose:2.1.0")
171175

172-
173176
// Compose collapsing toolbar
174177
implementation("me.onebone:toolbar-compose:2.3.3")
175178

176179
// Compose scroll bar
177180
implementation("com.github.nanihadesuka:LazyColumnScrollbar:1.3.1")
178181

182+
// Android ML Translation Kit
183+
implementation("com.google.mlkit:translate:17.0.0")
184+
179185
// Logging
180186
implementation("com.jakewharton.timber:timber:5.0.1")
181187

app/src/main/java/my/noveldokusha/App.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@ package my.noveldokusha
22

33
import android.app.Application
44
import dagger.hilt.android.HiltAndroidApp
5-
import kotlinx.coroutines.CoroutineName
6-
import kotlinx.coroutines.CoroutineScope
7-
import kotlinx.coroutines.Dispatchers
8-
import kotlinx.coroutines.SupervisorJob
95
import java.io.File
106
import javax.inject.Inject
117

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package my.noveldokusha
22

33
import android.content.Context
4-
import android.content.SharedPreferences
5-
import androidx.preference.PreferenceManager
64
import dagger.Module
75
import dagger.Provides
86
import dagger.hilt.InstallIn
@@ -14,39 +12,41 @@ import kotlinx.coroutines.Dispatchers
1412
import kotlinx.coroutines.SupervisorJob
1513
import my.noveldokusha.data.Repository
1614
import my.noveldokusha.data.database.AppDatabase
15+
import my.noveldokusha.tools.TranslationManager
1716
import javax.inject.Singleton
1817

1918
@InstallIn(SingletonComponent::class)
2019
@Module
21-
object AppModule
22-
{
20+
object AppModule {
2321
const val mainDatabaseName = "bookEntry"
2422

2523
@Provides
2624
@Singleton
27-
fun provideRepository(database: AppDatabase, @ApplicationContext context: Context): Repository
28-
{
25+
fun provideRepository(database: AppDatabase, @ApplicationContext context: Context): Repository {
2926
return Repository(database, context, mainDatabaseName)
3027
}
3128

3229
@Provides
3330
@Singleton
34-
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase
35-
{
31+
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
3632
return AppDatabase.createRoom(context, mainDatabaseName)
3733
}
3834

3935
@Provides
4036
@Singleton
41-
fun provideAppPreferencies(@ApplicationContext context: Context): AppPreferences
42-
{
37+
fun provideAppPreferences(@ApplicationContext context: Context): AppPreferences {
4338
return AppPreferences(context)
4439
}
4540

4641
@Provides
4742
@Singleton
48-
fun provideAppCoroutineScope(): CoroutineScope
49-
{
43+
fun provideAppCoroutineScope(): CoroutineScope {
5044
return CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("App"))
5145
}
46+
47+
@Provides
48+
@Singleton
49+
fun provideTranslationManager(coroutineScope: CoroutineScope): TranslationManager {
50+
return TranslationManager(coroutineScope)
51+
}
5252
}

app/src/main/java/my/noveldokusha/AppPreferences.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ class AppPreferences @Inject constructor(
6060
val BOOKS_LIST_LAYOUT_MODE = object : Preference<LIST_LAYOUT_MODE>("BOOKS_LIST_LAYOUT_MODE"){
6161
override var value by SharedPreference_Enum(name,preferences, LIST_LAYOUT_MODE.verticalGrid) { enumValueOf(it) }
6262
}
63+
val GLOBAL_TRANSLATIOR_ENABLED = object : Preference<Boolean>("GLOBAL_TRANSLATION_ENABLED"){
64+
override var value by SharedPreference_Boolean(name,preferences, false)
65+
}
66+
val GLOBAL_TRANSLATIOR_PREFERRED_SOURCE = object : Preference<String>("GLOBAL_TRANSLATIOR_PREFERRED_SOURCE"){
67+
override var value by SharedPreference_String(name,preferences, "en")
68+
}
69+
val GLOBAL_TRANSLATIOR_PREFERRED_TARGET = object : Preference<String>("GLOBAL_TRANSLATION_PREFERRED_TARGET"){
70+
override var value by SharedPreference_String(name,preferences, "")
71+
}
6372

6473
enum class TERNARY_STATE
6574
{
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package my.noveldokusha.tools
2+
3+
import androidx.compose.runtime.mutableStateListOf
4+
import com.google.mlkit.common.model.DownloadConditions
5+
import com.google.mlkit.common.model.RemoteModel
6+
import com.google.mlkit.common.model.RemoteModelManager
7+
import com.google.mlkit.nl.translate.*
8+
import kotlinx.coroutines.*
9+
import kotlinx.coroutines.tasks.await
10+
import java.util.Locale
11+
12+
data class TranslationModelState(
13+
val language: String,
14+
val model: TranslateRemoteModel?,
15+
val downloading: Boolean,
16+
val downloadingFailed: Boolean
17+
) {
18+
val locale = Locale(language)
19+
}
20+
21+
data class TranslatorState(
22+
val translator: Translator,
23+
val source: String,
24+
var target: String
25+
) {
26+
val sourceLocale = Locale(source)
27+
val targetLocale = Locale(target)
28+
fun translate(input: String) = translator.translate(input)
29+
}
30+
31+
class TranslationManager(
32+
private val coroutineScope: CoroutineScope
33+
) {
34+
val models = mutableStateListOf<TranslationModelState>().apply {
35+
val list = TranslateLanguage.getAllLanguages().map {
36+
TranslationModelState(
37+
language = it,
38+
model = null,
39+
downloading = false,
40+
downloadingFailed = false
41+
)
42+
}
43+
addAll(list)
44+
}
45+
46+
val modelsDownloaded = mutableStateListOf<TranslationModelState>()
47+
48+
init {
49+
coroutineScope.launch {
50+
val downloaded = loadDownloadedModelsList().associateBy { it.language }
51+
models.replaceAll {
52+
it.copy(model = downloaded[it.language])
53+
}
54+
updateModelsDownloaded()
55+
}
56+
}
57+
58+
private fun updateModelsDownloaded() {
59+
modelsDownloaded.clear()
60+
modelsDownloaded.addAll(models.filter { it.model != null })
61+
}
62+
63+
private suspend fun loadDownloadedModelsList() = withContext(Dispatchers.IO) {
64+
RemoteModelManager
65+
.getInstance()
66+
.getDownloadedModels(TranslateRemoteModel::class.java)
67+
.await()
68+
}
69+
70+
// TODO Should not use blocking code
71+
fun hasModelDownloadedSync(language: String) = runBlocking {
72+
val model = TranslateRemoteModel.Builder(language).build()
73+
val isDownloaded = RemoteModelManager
74+
.getInstance()
75+
.isModelDownloaded(model)
76+
.await()
77+
if (isDownloaded) TranslationModelState(
78+
model = model,
79+
downloading = false,
80+
language = language,
81+
downloadingFailed = false
82+
) else null
83+
}
84+
85+
/**
86+
* Doesn't check if the model has been downloaded. Must be externally guaranteed.
87+
* @param source [TranslateLanguage]
88+
* @param target [TranslateLanguage]
89+
*/
90+
fun getTranslator(
91+
source: String,
92+
target: String
93+
): TranslatorState {
94+
val option = TranslatorOptions.Builder()
95+
.setSourceLanguage(source)
96+
.setTargetLanguage(target)
97+
.build()
98+
99+
return TranslatorState(
100+
translator = Translation.getClient(option),
101+
source = source,
102+
target = target,
103+
)
104+
}
105+
106+
fun downloadModel(language: String) = coroutineScope.launch {
107+
val index = models.indexOfFirst { it.language == language }
108+
if (index == -1 || models[index].model != null) return@launch
109+
110+
models[index] = models[index].copy(
111+
downloadingFailed = false,
112+
downloading = true,
113+
)
114+
115+
RemoteModelManager
116+
.getInstance()
117+
.download(
118+
TranslateRemoteModel.Builder(language).build(),
119+
DownloadConditions.Builder().build()
120+
)
121+
.addOnSuccessListener {
122+
models[index] = models[index].copy(
123+
downloadingFailed = false,
124+
downloading = false,
125+
model = TranslateRemoteModel.Builder(language).build()
126+
)
127+
updateModelsDownloaded()
128+
}
129+
.addOnFailureListener {
130+
models[index] = models[index].copy(
131+
downloadingFailed = true,
132+
downloading = false
133+
)
134+
}
135+
}
136+
137+
fun removeModel(language: String) = coroutineScope.launch {
138+
139+
// English can't be removed.
140+
if (language == "en") return@launch
141+
142+
val index = models.indexOfFirst { it.language == language }
143+
if (index == -1) return@launch
144+
val model = models[index].model ?: return@launch
145+
146+
RemoteModelManager
147+
.getInstance()
148+
.deleteDownloadedModel(model)
149+
.addOnSuccessListener {
150+
models[index] = models[index].copy(
151+
downloadingFailed = false,
152+
downloading = false,
153+
model = null
154+
)
155+
updateModelsDownloaded()
156+
}
157+
}
158+
}

app/src/main/java/my/noveldokusha/ui/BaseActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import my.noveldokusha.utils.toast
1515
@AndroidEntryPoint
1616
open class BaseActivity : AppCompatActivity()
1717
{
18-
val appPreferences: AppPreferences by lazy { AppModule.provideAppPreferencies(applicationContext) }
18+
val appPreferences: AppPreferences by lazy { AppModule.provideAppPreferences(applicationContext) }
1919

2020
private fun getAppTheme(): Int
2121
{
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package my.noveldokusha.ui.composeViews
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.fillMaxWidth
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.foundation.shape.RoundedCornerShape
7+
import androidx.compose.material.Divider
8+
import androidx.compose.material.MaterialTheme
9+
import androidx.compose.material.Surface
10+
import androidx.compose.material.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.draw.clip
14+
import androidx.compose.ui.text.font.FontWeight
15+
import androidx.compose.ui.text.style.TextAlign
16+
import androidx.compose.ui.unit.dp
17+
import my.noveldokusha.ui.theme.ColorAccent
18+
19+
@Composable
20+
fun Section(
21+
title: String,
22+
modifier: Modifier = Modifier,
23+
content: @Composable () -> Unit
24+
) {
25+
Surface(
26+
color = MaterialTheme.colors.primaryVariant,
27+
modifier = modifier
28+
.clip(RoundedCornerShape(16.dp))
29+
) {
30+
Column(
31+
modifier = Modifier.padding(vertical = 16.dp)
32+
) {
33+
Text(
34+
text = title,
35+
style = MaterialTheme.typography.subtitle1,
36+
fontWeight = FontWeight.Bold,
37+
modifier = Modifier
38+
.fillMaxWidth()
39+
.padding(horizontal = 8.dp)
40+
.padding(bottom = 16.dp),
41+
color = ColorAccent,
42+
textAlign = TextAlign.Center,
43+
)
44+
Divider(color = MaterialTheme.colors.secondary)
45+
content()
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)