From 793ca945ab7b9480718988da1a47c52c248130a5 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Mon, 31 Mar 2025 23:46:14 +0900 Subject: [PATCH 1/6] [TEST] Testing new feature. - Test new feature for next release (Live translation using ML Kit) --- app/build.gradle.kts | 2 +- .../domain/FetchTopThreeNoticeByCategory.kt | 12 ++- .../domain/translate/TextTranslator.kt | 73 +++++++++++++++++++ .../knutice/domain/translate/ToJapanese.kt | 36 +++++++++ .../knutice/domain/translate/TranslateText.kt | 8 ++ .../presentation/CategorizedNoficiation.kt | 11 +++ .../knutice/presentation/MainActivity.kt | 1 + .../knutice/presentation/MainServiceScreen.kt | 37 ++++++++++ .../CategorizedNotificationViewModel.kt | 60 ++++++++++++++- .../knutice/viewModel/MainServiceViewModel.kt | 34 ++++++++- gradle/libs.versions.toml | 2 + 11 files changed, 267 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/doyoonkim/knutice/domain/translate/TextTranslator.kt create mode 100644 app/src/main/java/com/doyoonkim/knutice/domain/translate/ToJapanese.kt create mode 100644 app/src/main/java/com/doyoonkim/knutice/domain/translate/TranslateText.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 74a7fc08..c601813f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -131,7 +131,7 @@ dependencies { implementation(libs.androidx.room.ktx) // Translation -// implementation(libs.translate) + implementation(libs.translate) } diff --git a/app/src/main/java/com/doyoonkim/knutice/domain/FetchTopThreeNoticeByCategory.kt b/app/src/main/java/com/doyoonkim/knutice/domain/FetchTopThreeNoticeByCategory.kt index c55e4689..8b144982 100644 --- a/app/src/main/java/com/doyoonkim/knutice/domain/FetchTopThreeNoticeByCategory.kt +++ b/app/src/main/java/com/doyoonkim/knutice/domain/FetchTopThreeNoticeByCategory.kt @@ -1,17 +1,23 @@ package com.doyoonkim.knutice.domain +import android.util.Log import com.doyoonkim.knutice.data.NoticeLocalRepository +import com.doyoonkim.knutice.domain.translate.TextTranslator import com.doyoonkim.knutice.model.Notice import com.doyoonkim.knutice.model.NoticeCategory import com.doyoonkim.knutice.model.RawNoticeData import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.fold import kotlinx.coroutines.flow.map +import java.util.Locale import javax.inject.Inject class FetchTopThreeNoticeByCategory @Inject constructor ( - private val repository: NoticeLocalRepository + private val repository: NoticeLocalRepository, + private val translator: TextTranslator ): FetchTopThreeNotice { override fun fetchTopThreeGeneralNotice(): Flow { @@ -35,15 +41,17 @@ class FetchTopThreeNoticeByCategory @Inject constructor ( } fun getTopThreeNotices(category: NoticeCategory): Flow { + val translateNeeded: Boolean = Locale.getDefault() != Locale.KOREAN return repository.getTopThreeNotice(category).map { if (it.body.isNotEmpty()) { val result = it.body.toNotice() + TopThreeInCategory( isSuccessful = true, notice1 = result[0], notice2 = result[1], notice3 = result[2] - ) + ).also { Log.d("TEST", it.toString()) } } else { TopThreeInCategory( isSuccessful = false diff --git a/app/src/main/java/com/doyoonkim/knutice/domain/translate/TextTranslator.kt b/app/src/main/java/com/doyoonkim/knutice/domain/translate/TextTranslator.kt new file mode 100644 index 00000000..463c96ec --- /dev/null +++ b/app/src/main/java/com/doyoonkim/knutice/domain/translate/TextTranslator.kt @@ -0,0 +1,73 @@ +package com.doyoonkim.knutice.domain.translate + +import com.doyoonkim.knutice.model.Notice +import com.google.android.gms.tasks.Task +import com.google.mlkit.common.model.DownloadConditions +import com.google.mlkit.nl.translate.TranslateLanguage +import com.google.mlkit.nl.translate.Translation +import com.google.mlkit.nl.translate.TranslatorOptions +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import java.util.Locale +import javax.inject.Inject + +class TextTranslator @Inject constructor() : TranslateText { + private val translator by lazy { + Translation.getClient(initializeTranslator()) + } + + var translationNeeded: Boolean = false + + private fun initializeTranslator(): TranslatorOptions { + return TranslatorOptions.Builder().apply { + setSourceLanguage(TranslateLanguage.KOREAN) + when (Locale.getDefault()) { + Locale.JAPANESE, Locale.JAPAN -> { + setTargetLanguage(TranslateLanguage.JAPANESE) + } + Locale.ENGLISH -> { + setTargetLanguage(TranslateLanguage.ENGLISH) + } + } + }.build().also { + translationNeeded = true + } + } + + // Need to be revised later. + fun downloadLanguageModel(): Task { + val condition = DownloadConditions.Builder() + .requireWifi() + .build() + return translator.downloadModelIfNeeded(condition) + } + + + override fun translateTo(t: String): Task { + return translator.translate(t) + } + + fun translate(t: String) = callbackFlow> { + translator.translate(t).addOnSuccessListener { + trySend(Result.success(it)) + }.addOnFailureListener { + trySend(Result.failure(it)) + } + awaitClose { } + } + + fun translate(source: List): List { + val translated = mutableListOf() + source.forEach { notice -> + translator.run { + translate(notice.title).addOnSuccessListener { } + } + } + return source + } + + suspend fun translate_suspended(source: String) : String { + return translator.translate(source).result + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/domain/translate/ToJapanese.kt b/app/src/main/java/com/doyoonkim/knutice/domain/translate/ToJapanese.kt new file mode 100644 index 00000000..d0667020 --- /dev/null +++ b/app/src/main/java/com/doyoonkim/knutice/domain/translate/ToJapanese.kt @@ -0,0 +1,36 @@ +package com.doyoonkim.knutice.domain.translate + +import com.google.android.gms.tasks.Task +import com.google.mlkit.common.model.DownloadConditions +import com.google.mlkit.nl.translate.TranslateLanguage +import com.google.mlkit.nl.translate.Translation +import com.google.mlkit.nl.translate.TranslatorOptions +import kotlinx.coroutines.flow.callbackFlow +import javax.inject.Inject + +class ToJapanese @Inject constructor() : TranslateText { + private val koreanJapaneseTranslator by lazy { + val options = TranslatorOptions.Builder() + .setSourceLanguage(TranslateLanguage.KOREAN) + .setTargetLanguage(TranslateLanguage.JAPANESE) + .build() + Translation.getClient(options) + } + + fun downloadLanguageModel() = callbackFlow { + val conditions = DownloadConditions.Builder() + .requireWifi() + .build() + koreanJapaneseTranslator.downloadModelIfNeeded(conditions) + .addOnSuccessListener { + trySend(true) + } + .addOnFailureListener { + trySend(false) + } + } + + override fun translateTo(t: String): Task { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/domain/translate/TranslateText.kt b/app/src/main/java/com/doyoonkim/knutice/domain/translate/TranslateText.kt new file mode 100644 index 00000000..3a22cf4b --- /dev/null +++ b/app/src/main/java/com/doyoonkim/knutice/domain/translate/TranslateText.kt @@ -0,0 +1,8 @@ +package com.doyoonkim.knutice.domain.translate + +import com.google.android.gms.tasks.Task +import com.google.mlkit.nl.translate.TranslateLanguage + +interface TranslateText { + fun translateTo(t: String): Task +} \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/presentation/CategorizedNoficiation.kt b/app/src/main/java/com/doyoonkim/knutice/presentation/CategorizedNoficiation.kt index 255f8167..261d0ea1 100644 --- a/app/src/main/java/com/doyoonkim/knutice/presentation/CategorizedNoficiation.kt +++ b/app/src/main/java/com/doyoonkim/knutice/presentation/CategorizedNoficiation.kt @@ -1,6 +1,7 @@ package com.doyoonkim.knutice.presentation import android.content.res.Configuration +import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -17,6 +18,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -39,6 +41,8 @@ import com.doyoonkim.knutice.ui.theme.subTitle import com.doyoonkim.knutice.viewModel.CategorizedNotificationViewModel import com.doyoonkim.knutice.R import com.doyoonkim.knutice.model.FullContent +import com.doyoonkim.knutice.model.NoticeCategory +import java.util.Locale @Composable fun CategorizedNotification( @@ -55,6 +59,13 @@ fun CategorizedNotification( onGoBackAction() } + LaunchedEffect(uiState.notificationGeneral, uiState.notificationAcademic, uiState.notificationScholarship, uiState.notificationEvent) { + Log.d("Test", "${Locale.getDefault()} ${Locale.KOREAN} ${Locale.getDefault() != Locale.KOREAN}") + if (Locale.getDefault() != Locale.KOREAN) { + viewModel.translate(NoticeCategory.GENERAL_NEWS) + } + } + Column( modifier = modifier.verticalScroll( rememberScrollState(0) diff --git a/app/src/main/java/com/doyoonkim/knutice/presentation/MainActivity.kt b/app/src/main/java/com/doyoonkim/knutice/presentation/MainActivity.kt index e65de7e4..8b10b250 100644 --- a/app/src/main/java/com/doyoonkim/knutice/presentation/MainActivity.kt +++ b/app/src/main/java/com/doyoonkim/knutice/presentation/MainActivity.kt @@ -32,6 +32,7 @@ import com.doyoonkim.knutice.alarm.NotificationAlarmScheduler import com.doyoonkim.knutice.presentation.component.PermissionRationaleComposable import com.doyoonkim.knutice.ui.theme.KNUTICETheme import dagger.hilt.android.AndroidEntryPoint +import java.util.Locale @AndroidEntryPoint class MainActivity : ComponentActivity() { diff --git a/app/src/main/java/com/doyoonkim/knutice/presentation/MainServiceScreen.kt b/app/src/main/java/com/doyoonkim/knutice/presentation/MainServiceScreen.kt index 63157929..0a69e9f3 100644 --- a/app/src/main/java/com/doyoonkim/knutice/presentation/MainServiceScreen.kt +++ b/app/src/main/java/com/doyoonkim/knutice/presentation/MainServiceScreen.kt @@ -1,12 +1,17 @@ package com.doyoonkim.knutice.presentation +import android.Manifest import android.util.Log import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets @@ -32,6 +37,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState 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 @@ -50,12 +58,14 @@ import com.doyoonkim.knutice.model.Bookmark import com.doyoonkim.knutice.model.Destination import com.doyoonkim.knutice.model.NavDestination import com.doyoonkim.knutice.navigation.MainNavigator +import com.doyoonkim.knutice.presentation.component.PermissionRationaleComposable import com.doyoonkim.knutice.ui.theme.containerBackground import com.doyoonkim.knutice.ui.theme.displayBackground import com.doyoonkim.knutice.ui.theme.subTitle import com.doyoonkim.knutice.ui.theme.title import com.doyoonkim.knutice.viewModel.MainServiceViewModel import kotlinx.coroutines.async +import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -66,6 +76,12 @@ fun MainServiceScreen( val mainAppState by viewModel.uiState.collectAsState() val navController = rememberNavController() + var showLanguageDownloadRationale by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + if (Locale.getDefault() != Locale.KOREAN) { + showLanguageDownloadRationale = true + } + } LaunchedEffect(mainAppState.scheduleTriggered) { Log.d("MainServiceScreen", "Triggered") @@ -277,5 +293,26 @@ fun MainServiceScreen( ) .background(MaterialTheme.colorScheme.displayBackground) ) + + AnimatedVisibility( + visible = showLanguageDownloadRationale, + enter = scaleIn(), + exit = scaleOut() + ) { + Box( + modifier = Modifier.fillMaxSize() + .clickable { showLanguageDownloadRationale = false } + ) { + PermissionRationaleComposable( + modifier = Modifier.align(Alignment.Center).padding(start = 20.dp, end = 20.dp), + permissionName = stringResource(R.string.text_language), + rationaleTitle = stringResource(R.string.title_langauge_model_download), + description = stringResource(R.string.description_language_model_download) + ) { + viewModel.requestModelDownload() + showLanguageDownloadRationale = true + } + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/viewModel/CategorizedNotificationViewModel.kt b/app/src/main/java/com/doyoonkim/knutice/viewModel/CategorizedNotificationViewModel.kt index 9e0c0aac..0524747b 100644 --- a/app/src/main/java/com/doyoonkim/knutice/viewModel/CategorizedNotificationViewModel.kt +++ b/app/src/main/java/com/doyoonkim/knutice/viewModel/CategorizedNotificationViewModel.kt @@ -4,10 +4,12 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.doyoonkim.knutice.domain.FetchTopThreeNoticeByCategory +import com.doyoonkim.knutice.domain.translate.TextTranslator import com.doyoonkim.knutice.model.Notice import com.doyoonkim.knutice.model.NoticeCategory import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -20,7 +22,8 @@ import javax.inject.Inject @HiltViewModel class CategorizedNotificationViewModel @Inject constructor( - private val fetchTopThreeNoticeUseCase: FetchTopThreeNoticeByCategory + private val fetchTopThreeNoticeUseCase: FetchTopThreeNoticeByCategory, + private val translator: TextTranslator ) : ViewModel() { init { viewModelScope.launch(Dispatchers.Default) { @@ -62,9 +65,12 @@ class CategorizedNotificationViewModel @Inject constructor( onSuccess = { val notices = listOf(it.notice1!!, it.notice2!!, it.notice3!!) when(category) { - NoticeCategory.GENERAL_NEWS -> updateState( - updatedNotificationGeneral = notices - ) + NoticeCategory.GENERAL_NEWS -> { + + updateState( + updatedNotificationGeneral = notices + ) + } NoticeCategory.ACADEMIC_NEWS -> updateState( updatedNotificationAcademic = notices ) @@ -83,6 +89,52 @@ class CategorizedNotificationViewModel @Inject constructor( ) } } + + fun translate(category: NoticeCategory) { + Log.d("Test", "Translation Queried") + viewModelScope.launch { + val notices = uiState.value.notificationGeneral + + val translated = mutableListOf() + + Log.d("Test", "Translation Started") + notices.forEach { notice -> + var translatedTitle = notice.title + var translatedDept = notice.departName + + var updated = notice + + val title = async { translator.translateTo(notice.title) + .addOnSuccessListener { + Log.d("Test", "$it") + translatedTitle = it + updated = updated.copy(title = it) + } + .addOnFailureListener { Log.d("Test", "Unable: ${it.message}") } + } + + translator.translateTo(notice.departName) + .addOnSuccessListener { + translatedDept = it + updated = updated.copy(departName = it) + } + .addOnFailureListener { Log.d("Test", "Unable: ${it.message}") } +// Log.d("Test", updated.toString()) + translated.add(notice.copy( + title = translatedTitle, + departName = translatedDept + ).also { Log.d("Test", it.toString()) }) + } + + when(category) { + NoticeCategory.GENERAL_NEWS -> { _uiState.update { it.copy(notificationGeneral = translated).also { Log.d("Test", it.notificationGeneral.toString()) } } } + NoticeCategory.ACADEMIC_NEWS -> { _uiState.update { it.copy(notificationAcademic = translated) } } + NoticeCategory.SCHOLARSHIP_NEWS -> { _uiState.update { it.copy(notificationScholarship = translated) } } + NoticeCategory.EVENT_NEWS -> { _uiState.update { it.copy(notificationEvent = translated) } } + else -> { } + } + } + } } data class CategorizedNotificationState( diff --git a/app/src/main/java/com/doyoonkim/knutice/viewModel/MainServiceViewModel.kt b/app/src/main/java/com/doyoonkim/knutice/viewModel/MainServiceViewModel.kt index 7a3a01f8..77817364 100644 --- a/app/src/main/java/com/doyoonkim/knutice/viewModel/MainServiceViewModel.kt +++ b/app/src/main/java/com/doyoonkim/knutice/viewModel/MainServiceViewModel.kt @@ -1,17 +1,25 @@ package com.doyoonkim.knutice.viewModel import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.doyoonkim.knutice.domain.translate.TextTranslator import com.doyoonkim.knutice.model.Bookmark import com.doyoonkim.knutice.model.Destination import com.doyoonkim.knutice.model.Notice import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class MainServiceViewModel @Inject constructor() : ViewModel() { +class MainServiceViewModel @Inject constructor( + private val translator: TextTranslator +) : ViewModel() { private var _uiState = MutableStateFlow(MainServiceState()) val uiState = _uiState.asStateFlow() @@ -34,6 +42,27 @@ class MainServiceViewModel @Inject constructor() : ViewModel() { ) } } + + fun updateLanguageModelDownloadStatus(newStatus: String) { + _uiState.update { + it.copy( + languageModelDownloadResult = newStatus + ) + } + } + + fun requestModelDownload() { + updateLanguageModelDownloadStatus("REQUESTED") + viewModelScope.launch { + translator.downloadLanguageModel() + .addOnSuccessListener { + updateLanguageModelDownloadStatus("COMPLETED") + } + .addOnFailureListener { + updateLanguageModelDownloadStatus("FAILED") + } + } + } } data class MainServiceState( @@ -42,5 +71,6 @@ data class MainServiceState( val isBottomNavBarVisible: Boolean = false, val tempReserveNoticeForBookmark: Notice = Notice(), // ? val currentTargetBookmark: Bookmark = Bookmark(-1), - val scheduleTriggered: Boolean = false + val scheduleTriggered: Boolean = false, + val languageModelDownloadResult: String = "YET_STARTED" ) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 52eeb39c..15f2e82e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,7 @@ firebaseMessaging = "24.0.2" kotlinSerialization = "1.6.0" junitKtx = "1.2.1" protoliteWellKnownTypes = "18.0.0" +translate = "17.0.3" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -72,6 +73,7 @@ firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref="kotlinSerialization" } androidx-junit-ktx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "junitKtx" } protolite-well-known-types = { group = "com.google.firebase", name = "protolite-well-known-types", version.ref = "protoliteWellKnownTypes" } +translate = { module = "com.google.mlkit:translate", version.ref = "translate" } [plugins] From bb5f504ef2719ae2eb61db0b4e90615ac7ffe677 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 16 Apr 2025 22:46:22 +0900 Subject: [PATCH 2/6] [REFACT] Server Migration - Apply new environment for server migration. - Change version information. --- app/build.gradle.kts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c601813f..74eedd49 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,21 +23,21 @@ android { val properties = Properties().apply { load(FileInputStream("${rootDir}/local.properties")) } - val apiRoot = properties["api_root"] ?: "" + val apiMigrated = properties["api_migrated"] ?: "" defaultConfig { applicationId = "com.doyoonkim.knutice" minSdk = 31 targetSdk = 34 - versionCode = 11 - versionName = "1.3.2" + versionCode = 13 + versionName = "1.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } - buildConfigField("String", "API_ROOT", "\"$apiRoot\"") + buildConfigField("String", "API_MIGRATED", "\"$apiMigrated\"") javaCompileOptions { annotationProcessorOptions { From 334893e5e7c6663c5294403da514ee2e26a9927e Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 16 Apr 2025 22:48:36 +0900 Subject: [PATCH 3/6] [REFACT] Revoke Live Translation Feature - Revoke live translation feature for later version. --- .../domain/FetchTopThreeNoticeByCategory.kt | 6 +- .../domain/translate/TextTranslator.kt | 73 ------------------- .../knutice/domain/translate/ToJapanese.kt | 36 --------- .../knutice/domain/translate/TranslateText.kt | 8 -- .../presentation/CategorizedNoficiation.kt | 13 ---- .../knutice/presentation/MainServiceScreen.kt | 64 +++++++--------- .../CategorizedNotificationViewModel.kt | 49 +------------ .../knutice/viewModel/MainServiceViewModel.kt | 23 +----- 8 files changed, 31 insertions(+), 241 deletions(-) delete mode 100644 app/src/main/java/com/doyoonkim/knutice/domain/translate/TextTranslator.kt delete mode 100644 app/src/main/java/com/doyoonkim/knutice/domain/translate/ToJapanese.kt delete mode 100644 app/src/main/java/com/doyoonkim/knutice/domain/translate/TranslateText.kt diff --git a/app/src/main/java/com/doyoonkim/knutice/domain/FetchTopThreeNoticeByCategory.kt b/app/src/main/java/com/doyoonkim/knutice/domain/FetchTopThreeNoticeByCategory.kt index 8b144982..3ebb3c2f 100644 --- a/app/src/main/java/com/doyoonkim/knutice/domain/FetchTopThreeNoticeByCategory.kt +++ b/app/src/main/java/com/doyoonkim/knutice/domain/FetchTopThreeNoticeByCategory.kt @@ -2,22 +2,18 @@ package com.doyoonkim.knutice.domain import android.util.Log import com.doyoonkim.knutice.data.NoticeLocalRepository -import com.doyoonkim.knutice.domain.translate.TextTranslator import com.doyoonkim.knutice.model.Notice import com.doyoonkim.knutice.model.NoticeCategory import com.doyoonkim.knutice.model.RawNoticeData import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.fold import kotlinx.coroutines.flow.map import java.util.Locale import javax.inject.Inject class FetchTopThreeNoticeByCategory @Inject constructor ( - private val repository: NoticeLocalRepository, - private val translator: TextTranslator + private val repository: NoticeLocalRepository ): FetchTopThreeNotice { override fun fetchTopThreeGeneralNotice(): Flow { diff --git a/app/src/main/java/com/doyoonkim/knutice/domain/translate/TextTranslator.kt b/app/src/main/java/com/doyoonkim/knutice/domain/translate/TextTranslator.kt deleted file mode 100644 index 463c96ec..00000000 --- a/app/src/main/java/com/doyoonkim/knutice/domain/translate/TextTranslator.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.doyoonkim.knutice.domain.translate - -import com.doyoonkim.knutice.model.Notice -import com.google.android.gms.tasks.Task -import com.google.mlkit.common.model.DownloadConditions -import com.google.mlkit.nl.translate.TranslateLanguage -import com.google.mlkit.nl.translate.Translation -import com.google.mlkit.nl.translate.TranslatorOptions -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.callbackFlow -import java.util.Locale -import javax.inject.Inject - -class TextTranslator @Inject constructor() : TranslateText { - private val translator by lazy { - Translation.getClient(initializeTranslator()) - } - - var translationNeeded: Boolean = false - - private fun initializeTranslator(): TranslatorOptions { - return TranslatorOptions.Builder().apply { - setSourceLanguage(TranslateLanguage.KOREAN) - when (Locale.getDefault()) { - Locale.JAPANESE, Locale.JAPAN -> { - setTargetLanguage(TranslateLanguage.JAPANESE) - } - Locale.ENGLISH -> { - setTargetLanguage(TranslateLanguage.ENGLISH) - } - } - }.build().also { - translationNeeded = true - } - } - - // Need to be revised later. - fun downloadLanguageModel(): Task { - val condition = DownloadConditions.Builder() - .requireWifi() - .build() - return translator.downloadModelIfNeeded(condition) - } - - - override fun translateTo(t: String): Task { - return translator.translate(t) - } - - fun translate(t: String) = callbackFlow> { - translator.translate(t).addOnSuccessListener { - trySend(Result.success(it)) - }.addOnFailureListener { - trySend(Result.failure(it)) - } - awaitClose { } - } - - fun translate(source: List): List { - val translated = mutableListOf() - source.forEach { notice -> - translator.run { - translate(notice.title).addOnSuccessListener { } - } - } - return source - } - - suspend fun translate_suspended(source: String) : String { - return translator.translate(source).result - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/domain/translate/ToJapanese.kt b/app/src/main/java/com/doyoonkim/knutice/domain/translate/ToJapanese.kt deleted file mode 100644 index d0667020..00000000 --- a/app/src/main/java/com/doyoonkim/knutice/domain/translate/ToJapanese.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.doyoonkim.knutice.domain.translate - -import com.google.android.gms.tasks.Task -import com.google.mlkit.common.model.DownloadConditions -import com.google.mlkit.nl.translate.TranslateLanguage -import com.google.mlkit.nl.translate.Translation -import com.google.mlkit.nl.translate.TranslatorOptions -import kotlinx.coroutines.flow.callbackFlow -import javax.inject.Inject - -class ToJapanese @Inject constructor() : TranslateText { - private val koreanJapaneseTranslator by lazy { - val options = TranslatorOptions.Builder() - .setSourceLanguage(TranslateLanguage.KOREAN) - .setTargetLanguage(TranslateLanguage.JAPANESE) - .build() - Translation.getClient(options) - } - - fun downloadLanguageModel() = callbackFlow { - val conditions = DownloadConditions.Builder() - .requireWifi() - .build() - koreanJapaneseTranslator.downloadModelIfNeeded(conditions) - .addOnSuccessListener { - trySend(true) - } - .addOnFailureListener { - trySend(false) - } - } - - override fun translateTo(t: String): Task { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/domain/translate/TranslateText.kt b/app/src/main/java/com/doyoonkim/knutice/domain/translate/TranslateText.kt deleted file mode 100644 index 3a22cf4b..00000000 --- a/app/src/main/java/com/doyoonkim/knutice/domain/translate/TranslateText.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.doyoonkim.knutice.domain.translate - -import com.google.android.gms.tasks.Task -import com.google.mlkit.nl.translate.TranslateLanguage - -interface TranslateText { - fun translateTo(t: String): Task -} \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/presentation/CategorizedNoficiation.kt b/app/src/main/java/com/doyoonkim/knutice/presentation/CategorizedNoficiation.kt index 261d0ea1..0908acd1 100644 --- a/app/src/main/java/com/doyoonkim/knutice/presentation/CategorizedNoficiation.kt +++ b/app/src/main/java/com/doyoonkim/knutice/presentation/CategorizedNoficiation.kt @@ -10,15 +10,12 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -40,9 +37,6 @@ import com.doyoonkim.knutice.ui.theme.notificationType4 import com.doyoonkim.knutice.ui.theme.subTitle import com.doyoonkim.knutice.viewModel.CategorizedNotificationViewModel import com.doyoonkim.knutice.R -import com.doyoonkim.knutice.model.FullContent -import com.doyoonkim.knutice.model.NoticeCategory -import java.util.Locale @Composable fun CategorizedNotification( @@ -59,13 +53,6 @@ fun CategorizedNotification( onGoBackAction() } - LaunchedEffect(uiState.notificationGeneral, uiState.notificationAcademic, uiState.notificationScholarship, uiState.notificationEvent) { - Log.d("Test", "${Locale.getDefault()} ${Locale.KOREAN} ${Locale.getDefault() != Locale.KOREAN}") - if (Locale.getDefault() != Locale.KOREAN) { - viewModel.translate(NoticeCategory.GENERAL_NEWS) - } - } - Column( modifier = modifier.verticalScroll( rememberScrollState(0) diff --git a/app/src/main/java/com/doyoonkim/knutice/presentation/MainServiceScreen.kt b/app/src/main/java/com/doyoonkim/knutice/presentation/MainServiceScreen.kt index 0a69e9f3..42ce2862 100644 --- a/app/src/main/java/com/doyoonkim/knutice/presentation/MainServiceScreen.kt +++ b/app/src/main/java/com/doyoonkim/knutice/presentation/MainServiceScreen.kt @@ -1,17 +1,12 @@ package com.doyoonkim.knutice.presentation -import android.Manifest import android.util.Log import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets @@ -37,9 +32,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState 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 @@ -58,14 +50,12 @@ import com.doyoonkim.knutice.model.Bookmark import com.doyoonkim.knutice.model.Destination import com.doyoonkim.knutice.model.NavDestination import com.doyoonkim.knutice.navigation.MainNavigator -import com.doyoonkim.knutice.presentation.component.PermissionRationaleComposable import com.doyoonkim.knutice.ui.theme.containerBackground import com.doyoonkim.knutice.ui.theme.displayBackground import com.doyoonkim.knutice.ui.theme.subTitle import com.doyoonkim.knutice.ui.theme.title import com.doyoonkim.knutice.viewModel.MainServiceViewModel import kotlinx.coroutines.async -import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -76,12 +66,13 @@ fun MainServiceScreen( val mainAppState by viewModel.uiState.collectAsState() val navController = rememberNavController() - var showLanguageDownloadRationale by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - if (Locale.getDefault() != Locale.KOREAN) { - showLanguageDownloadRationale = true - } - } +// //TODO Live translation feature (TBD) +// var showLanguageDownloadRationale by remember { mutableStateOf(false) } +// LaunchedEffect(Unit) { +// if (Locale.getDefault() != Locale.KOREAN) { +// showLanguageDownloadRationale = true +// } +// } LaunchedEffect(mainAppState.scheduleTriggered) { Log.d("MainServiceScreen", "Triggered") @@ -294,25 +285,26 @@ fun MainServiceScreen( .background(MaterialTheme.colorScheme.displayBackground) ) - AnimatedVisibility( - visible = showLanguageDownloadRationale, - enter = scaleIn(), - exit = scaleOut() - ) { - Box( - modifier = Modifier.fillMaxSize() - .clickable { showLanguageDownloadRationale = false } - ) { - PermissionRationaleComposable( - modifier = Modifier.align(Alignment.Center).padding(start = 20.dp, end = 20.dp), - permissionName = stringResource(R.string.text_language), - rationaleTitle = stringResource(R.string.title_langauge_model_download), - description = stringResource(R.string.description_language_model_download) - ) { - viewModel.requestModelDownload() - showLanguageDownloadRationale = true - } - } - } + //TODO Live Translation Feature (TBD) +// AnimatedVisibility( +// visible = showLanguageDownloadRationale, +// enter = scaleIn(), +// exit = scaleOut() +// ) { +// Box( +// modifier = Modifier.fillMaxSize() +// .clickable { showLanguageDownloadRationale = false } +// ) { +// PermissionRationaleComposable( +// modifier = Modifier.align(Alignment.Center).padding(start = 20.dp, end = 20.dp), +// permissionName = stringResource(R.string.text_language), +// rationaleTitle = stringResource(R.string.title_langauge_model_download), +// description = stringResource(R.string.description_language_model_download) +// ) { +// viewModel.requestModelDownload() +// showLanguageDownloadRationale = true +// } +// } +// } } } \ No newline at end of file diff --git a/app/src/main/java/com/doyoonkim/knutice/viewModel/CategorizedNotificationViewModel.kt b/app/src/main/java/com/doyoonkim/knutice/viewModel/CategorizedNotificationViewModel.kt index 0524747b..e8c7367f 100644 --- a/app/src/main/java/com/doyoonkim/knutice/viewModel/CategorizedNotificationViewModel.kt +++ b/app/src/main/java/com/doyoonkim/knutice/viewModel/CategorizedNotificationViewModel.kt @@ -4,7 +4,6 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.doyoonkim.knutice.domain.FetchTopThreeNoticeByCategory -import com.doyoonkim.knutice.domain.translate.TextTranslator import com.doyoonkim.knutice.model.Notice import com.doyoonkim.knutice.model.NoticeCategory import dagger.hilt.android.lifecycle.HiltViewModel @@ -22,8 +21,7 @@ import javax.inject.Inject @HiltViewModel class CategorizedNotificationViewModel @Inject constructor( - private val fetchTopThreeNoticeUseCase: FetchTopThreeNoticeByCategory, - private val translator: TextTranslator + private val fetchTopThreeNoticeUseCase: FetchTopThreeNoticeByCategory ) : ViewModel() { init { viewModelScope.launch(Dispatchers.Default) { @@ -90,51 +88,6 @@ class CategorizedNotificationViewModel @Inject constructor( } } - fun translate(category: NoticeCategory) { - Log.d("Test", "Translation Queried") - viewModelScope.launch { - val notices = uiState.value.notificationGeneral - - val translated = mutableListOf() - - Log.d("Test", "Translation Started") - notices.forEach { notice -> - var translatedTitle = notice.title - var translatedDept = notice.departName - - var updated = notice - - val title = async { translator.translateTo(notice.title) - .addOnSuccessListener { - Log.d("Test", "$it") - translatedTitle = it - updated = updated.copy(title = it) - } - .addOnFailureListener { Log.d("Test", "Unable: ${it.message}") } - } - - translator.translateTo(notice.departName) - .addOnSuccessListener { - translatedDept = it - updated = updated.copy(departName = it) - } - .addOnFailureListener { Log.d("Test", "Unable: ${it.message}") } -// Log.d("Test", updated.toString()) - translated.add(notice.copy( - title = translatedTitle, - departName = translatedDept - ).also { Log.d("Test", it.toString()) }) - } - - when(category) { - NoticeCategory.GENERAL_NEWS -> { _uiState.update { it.copy(notificationGeneral = translated).also { Log.d("Test", it.notificationGeneral.toString()) } } } - NoticeCategory.ACADEMIC_NEWS -> { _uiState.update { it.copy(notificationAcademic = translated) } } - NoticeCategory.SCHOLARSHIP_NEWS -> { _uiState.update { it.copy(notificationScholarship = translated) } } - NoticeCategory.EVENT_NEWS -> { _uiState.update { it.copy(notificationEvent = translated) } } - else -> { } - } - } - } } data class CategorizedNotificationState( diff --git a/app/src/main/java/com/doyoonkim/knutice/viewModel/MainServiceViewModel.kt b/app/src/main/java/com/doyoonkim/knutice/viewModel/MainServiceViewModel.kt index 77817364..4a7fca64 100644 --- a/app/src/main/java/com/doyoonkim/knutice/viewModel/MainServiceViewModel.kt +++ b/app/src/main/java/com/doyoonkim/knutice/viewModel/MainServiceViewModel.kt @@ -1,25 +1,17 @@ package com.doyoonkim.knutice.viewModel import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.doyoonkim.knutice.domain.translate.TextTranslator import com.doyoonkim.knutice.model.Bookmark import com.doyoonkim.knutice.model.Destination import com.doyoonkim.knutice.model.Notice import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class MainServiceViewModel @Inject constructor( - private val translator: TextTranslator -) : ViewModel() { +class MainServiceViewModel @Inject constructor() : ViewModel() { private var _uiState = MutableStateFlow(MainServiceState()) val uiState = _uiState.asStateFlow() @@ -50,19 +42,6 @@ class MainServiceViewModel @Inject constructor( ) } } - - fun requestModelDownload() { - updateLanguageModelDownloadStatus("REQUESTED") - viewModelScope.launch { - translator.downloadLanguageModel() - .addOnSuccessListener { - updateLanguageModelDownloadStatus("COMPLETED") - } - .addOnFailureListener { - updateLanguageModelDownloadStatus("FAILED") - } - } - } } data class MainServiceState( From 39ccc1540c0ead07294efb2e8290b8a9bde48969 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 16 Apr 2025 22:52:55 +0900 Subject: [PATCH 4/6] [REFACT] Server Migration - Update data wrappers for migrated server based on its specification. * New field added: noticeName * Name of the field has changed --- .../doyoonkim/knutice/model/DataWrappers.kt | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/model/DataWrappers.kt b/app/src/main/java/com/doyoonkim/knutice/model/DataWrappers.kt index 6361a2d2..475839ab 100644 --- a/app/src/main/java/com/doyoonkim/knutice/model/DataWrappers.kt +++ b/app/src/main/java/com/doyoonkim/knutice/model/DataWrappers.kt @@ -1,6 +1,5 @@ package com.doyoonkim.knutice.model -import androidx.room.Entity import com.google.gson.annotations.SerializedName import kotlinx.serialization.Serializable @@ -16,8 +15,9 @@ data class RawNoticeData( @SerializedName("title") var title: String? = null, @SerializedName("contentUrl") var contentUrl: String? = null, @SerializedName("contentImage") var contentImage: String? = null, - @SerializedName("departName") var departName: String? = null, - @SerializedName("registeredAt") var registeredAt: String? = null + @SerializedName("departmentName") var departName: String? = null, + @SerializedName("registeredAt") var registeredAt: String? = null, + @SerializedName("noticeName") var noticeCategory: String? = null ) // POJO for receiving raw data from the server. @@ -40,12 +40,8 @@ data class NoticesPerPage( data class ApiPostResult( @SerializedName("result") var result: Result? = Result(), - @SerializedName("body") var body: Body? = Body() -) { - data class Body( - val message: String = "" - ) -} + @SerializedName("body") var body: Boolean? = null +) data class ApiDeviceTokenRequest( val result: Result = Result(), @@ -53,7 +49,7 @@ data class ApiDeviceTokenRequest( ) data class DeviceTokenRequest( - val deviceToken: String + val fcmToken: String ) data class ApiReportRequest( @@ -62,7 +58,7 @@ data class ApiReportRequest( ) data class ReportRequest( - val token: String = "", + val fcmToken: String = "", val content: String = "", val clientType: String = "APP", val deviceName: String = "", @@ -75,7 +71,7 @@ data class ApiTopicSubscriptionRequest( ) data class ManageTopicRequest( - val deviceToken: String = "", + val fcmToken: String = "", val noticeName: String = "", val isSubscribed: Boolean = false ) From c702ef1e4d34512cfad716150e77dc1a7e69a9d6 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 16 Apr 2025 22:54:07 +0900 Subject: [PATCH 5/6] [REFACT] Server Migration - Update root url to migratead server. - Update url specification. - Modify return value handling. --- .../knutice/data/KnuticeRemoteSource.kt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/doyoonkim/knutice/data/KnuticeRemoteSource.kt b/app/src/main/java/com/doyoonkim/knutice/data/KnuticeRemoteSource.kt index a921f68f..f7cfc2b0 100644 --- a/app/src/main/java/com/doyoonkim/knutice/data/KnuticeRemoteSource.kt +++ b/app/src/main/java/com/doyoonkim/knutice/data/KnuticeRemoteSource.kt @@ -31,7 +31,7 @@ import javax.inject.Singleton class KnuticeRemoteSource @Inject constructor() { private val knuticeService = Retrofit.Builder() - .baseUrl(BuildConfig.API_ROOT) + .baseUrl(BuildConfig.API_MIGRATED) .addConverterFactory(GsonConverterFactory.create()) .build() @@ -93,13 +93,13 @@ class KnuticeRemoteSource @Inject constructor() { Log.d("KnuticeRemoteSource", "ValidatedToken: $validatedToken") try { knuticeService.create(KnuticeService::class.java).submitUserReport( - ApiReportRequest(body = report.copy(token = validatedToken)) + ApiReportRequest(body = report.copy(fcmToken = validatedToken)) ).run { if (this.result?.resultCode == 200) { - Log.d("KnuticeServer", "User report has been submitted successfully.\n${this.body?.message}") + Log.d("KnuticeServer", "User report has been submitted successfully.\n${this.body}") return Result.success(true) } else { - Log.d("KnuticeServer", "Failed to submit user report\n${this.body?.message}") + Log.d("KnuticeServer", "Failed to submit user report\n${this.body ?: false}") return Result.success(false) } } @@ -121,7 +121,7 @@ class KnuticeRemoteSource @Inject constructor() { Log.d("KnuticeServer", "Topic preference has been updated.\n${this.body}") return Result.success(true) } else { - Log.d("KnuticeServer", "Failed to update topic preference.\n${this.body?.message}") + Log.d("KnuticeServer", "Failed to update topic preference.\n${this.body ?: false}") return Result.success(false) } } @@ -135,9 +135,6 @@ class KnuticeRemoteSource @Inject constructor() { interface KnuticeService { - @GET("/open-api/notice") - suspend fun getTopThreeNotice(): TopThreeNotices - @GET("/open-api/notice/list") suspend fun getTopThreeNotice( @Query("noticeName") category: NoticeCategory, @@ -161,7 +158,7 @@ interface KnuticeService { ): NoticesPerPage @Headers("Content-Type: application/json") - @POST("/open-api/token") + @POST("/open-api/fcm") suspend fun validateToken( @Body requestBody: ApiDeviceTokenRequest ): ApiPostResult @@ -173,7 +170,7 @@ interface KnuticeService { ): ApiPostResult @Headers("Content-Type: application/json") - @POST("/open-api/token/topic") + @POST("/open-api/topic") suspend fun submitTopicSubscriptionPreference( @Body requestBody: ApiTopicSubscriptionRequest ): ApiPostResult From 257d34867704a621ca09a02aa01f1f2bd3a93271 Mon Sep 17 00:00:00 2001 From: Doyoon Kim Date: Wed, 16 Apr 2025 23:07:57 +0900 Subject: [PATCH 6/6] [CHORE] Update version info - Update version string (1.3.3) --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 23b5f713..9fda8c4c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,7 +11,7 @@ About Version Open Source License - 1.3.2 + 1.3.3 Notification Preference New Notice has been delivered!