Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
nanihadesuka committed Jul 16, 2022
2 parents dc47d5b + 9cdb3b2 commit 0c11546
Show file tree
Hide file tree
Showing 28 changed files with 1,136 additions and 263 deletions.
10 changes: 8 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ dependencies {
implementation(fileTree("libs") { include("*.jar") })

// Kotlin
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlin:kotlin-script-runtime:1.6.20")
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.21")

// Needed to have the Task -> await extension.
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.2")


// Room components
implementation("androidx.room:room-runtime:2.4.2")
implementation("androidx.room:room-ktx:2.4.2")
Expand Down Expand Up @@ -169,13 +173,15 @@ dependencies {
// Coil for jetpack compose
implementation("io.coil-kt:coil-compose:2.1.0")


// Compose collapsing toolbar
implementation("me.onebone:toolbar-compose:2.3.3")

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

// Android ML Translation Kit
implementation("com.google.mlkit:translate:17.0.0")

// Logging
implementation("com.jakewharton.timber:timber:5.0.1")

Expand Down
4 changes: 0 additions & 4 deletions app/src/main/java/my/noveldokusha/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ package my.noveldokusha

import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import java.io.File
import javax.inject.Inject

Expand Down
24 changes: 12 additions & 12 deletions app/src/main/java/my/noveldokusha/AppModule.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package my.noveldokusha

import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -14,39 +12,41 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import my.noveldokusha.data.Repository
import my.noveldokusha.data.database.AppDatabase
import my.noveldokusha.tools.TranslationManager
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
@Module
object AppModule
{
object AppModule {
const val mainDatabaseName = "bookEntry"

@Provides
@Singleton
fun provideRepository(database: AppDatabase, @ApplicationContext context: Context): Repository
{
fun provideRepository(database: AppDatabase, @ApplicationContext context: Context): Repository {
return Repository(database, context, mainDatabaseName)
}

@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase
{
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return AppDatabase.createRoom(context, mainDatabaseName)
}

@Provides
@Singleton
fun provideAppPreferencies(@ApplicationContext context: Context): AppPreferences
{
fun provideAppPreferences(@ApplicationContext context: Context): AppPreferences {
return AppPreferences(context)
}

@Provides
@Singleton
fun provideAppCoroutineScope(): CoroutineScope
{
fun provideAppCoroutineScope(): CoroutineScope {
return CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("App"))
}

@Provides
@Singleton
fun provideTranslationManager(coroutineScope: CoroutineScope): TranslationManager {
return TranslationManager(coroutineScope)
}
}
9 changes: 9 additions & 0 deletions app/src/main/java/my/noveldokusha/AppPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ class AppPreferences @Inject constructor(
val BOOKS_LIST_LAYOUT_MODE = object : Preference<LIST_LAYOUT_MODE>("BOOKS_LIST_LAYOUT_MODE"){
override var value by SharedPreference_Enum(name,preferences, LIST_LAYOUT_MODE.verticalGrid) { enumValueOf(it) }
}
val GLOBAL_TRANSLATIOR_ENABLED = object : Preference<Boolean>("GLOBAL_TRANSLATION_ENABLED"){
override var value by SharedPreference_Boolean(name,preferences, false)
}
val GLOBAL_TRANSLATIOR_PREFERRED_SOURCE = object : Preference<String>("GLOBAL_TRANSLATIOR_PREFERRED_SOURCE"){
override var value by SharedPreference_String(name,preferences, "en")
}
val GLOBAL_TRANSLATIOR_PREFERRED_TARGET = object : Preference<String>("GLOBAL_TRANSLATION_PREFERRED_TARGET"){
override var value by SharedPreference_String(name,preferences, "")
}

enum class TERNARY_STATE
{
Expand Down
158 changes: 158 additions & 0 deletions app/src/main/java/my/noveldokusha/tools/TranslationManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package my.noveldokusha.tools

import androidx.compose.runtime.mutableStateListOf
import com.google.mlkit.common.model.DownloadConditions
import com.google.mlkit.common.model.RemoteModel
import com.google.mlkit.common.model.RemoteModelManager
import com.google.mlkit.nl.translate.*
import kotlinx.coroutines.*
import kotlinx.coroutines.tasks.await
import java.util.Locale

data class TranslationModelState(
val language: String,
val model: TranslateRemoteModel?,
val downloading: Boolean,
val downloadingFailed: Boolean
) {
val locale = Locale(language)
}

data class TranslatorState(
val translator: Translator,
val source: String,
var target: String
) {
val sourceLocale = Locale(source)
val targetLocale = Locale(target)
fun translate(input: String) = translator.translate(input)
}

class TranslationManager(
private val coroutineScope: CoroutineScope
) {
val models = mutableStateListOf<TranslationModelState>().apply {
val list = TranslateLanguage.getAllLanguages().map {
TranslationModelState(
language = it,
model = null,
downloading = false,
downloadingFailed = false
)
}
addAll(list)
}

val modelsDownloaded = mutableStateListOf<TranslationModelState>()

init {
coroutineScope.launch {
val downloaded = loadDownloadedModelsList().associateBy { it.language }
models.replaceAll {
it.copy(model = downloaded[it.language])
}
updateModelsDownloaded()
}
}

private fun updateModelsDownloaded() {
modelsDownloaded.clear()
modelsDownloaded.addAll(models.filter { it.model != null })
}

private suspend fun loadDownloadedModelsList() = withContext(Dispatchers.IO) {
RemoteModelManager
.getInstance()
.getDownloadedModels(TranslateRemoteModel::class.java)
.await()
}

// TODO Should not use blocking code
fun hasModelDownloadedSync(language: String) = runBlocking {
val model = TranslateRemoteModel.Builder(language).build()
val isDownloaded = RemoteModelManager
.getInstance()
.isModelDownloaded(model)
.await()
if (isDownloaded) TranslationModelState(
model = model,
downloading = false,
language = language,
downloadingFailed = false
) else null
}

/**
* Doesn't check if the model has been downloaded. Must be externally guaranteed.
* @param source [TranslateLanguage]
* @param target [TranslateLanguage]
*/
fun getTranslator(
source: String,
target: String
): TranslatorState {
val option = TranslatorOptions.Builder()
.setSourceLanguage(source)
.setTargetLanguage(target)
.build()

return TranslatorState(
translator = Translation.getClient(option),
source = source,
target = target,
)
}

fun downloadModel(language: String) = coroutineScope.launch {
val index = models.indexOfFirst { it.language == language }
if (index == -1 || models[index].model != null) return@launch

models[index] = models[index].copy(
downloadingFailed = false,
downloading = true,
)

RemoteModelManager
.getInstance()
.download(
TranslateRemoteModel.Builder(language).build(),
DownloadConditions.Builder().build()
)
.addOnSuccessListener {
models[index] = models[index].copy(
downloadingFailed = false,
downloading = false,
model = TranslateRemoteModel.Builder(language).build()
)
updateModelsDownloaded()
}
.addOnFailureListener {
models[index] = models[index].copy(
downloadingFailed = true,
downloading = false
)
}
}

fun removeModel(language: String) = coroutineScope.launch {

// English can't be removed.
if (language == "en") return@launch

val index = models.indexOfFirst { it.language == language }
if (index == -1) return@launch
val model = models[index].model ?: return@launch

RemoteModelManager
.getInstance()
.deleteDownloadedModel(model)
.addOnSuccessListener {
models[index] = models[index].copy(
downloadingFailed = false,
downloading = false,
model = null
)
updateModelsDownloaded()
}
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/my/noveldokusha/ui/BaseActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import my.noveldokusha.utils.toast
@AndroidEntryPoint
open class BaseActivity : AppCompatActivity()
{
val appPreferences: AppPreferences by lazy { AppModule.provideAppPreferencies(applicationContext) }
val appPreferences: AppPreferences by lazy { AppModule.provideAppPreferences(applicationContext) }

private fun getAppTheme(): Int
{
Expand Down
48 changes: 48 additions & 0 deletions app/src/main/java/my/noveldokusha/ui/composeViews/Section.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package my.noveldokusha.ui.composeViews

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import my.noveldokusha.ui.theme.ColorAccent

@Composable
fun Section(
title: String,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Surface(
color = MaterialTheme.colors.primaryVariant,
modifier = modifier
.clip(RoundedCornerShape(16.dp))
) {
Column(
modifier = Modifier.padding(vertical = 16.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.subtitle1,
fontWeight = FontWeight.Bold,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.padding(bottom = 16.dp),
color = ColorAccent,
textAlign = TextAlign.Center,
)
Divider(color = MaterialTheme.colors.secondary)
content()
}
}
}
Loading

0 comments on commit 0c11546

Please sign in to comment.