diff --git a/frontend/app/build.gradle.kts b/frontend/app/build.gradle.kts index c7eed2bf..d1ddce7c 100644 --- a/frontend/app/build.gradle.kts +++ b/frontend/app/build.gradle.kts @@ -13,7 +13,7 @@ android { defaultConfig { applicationId = "com.example.speechbuddy" - minSdk = 24 + minSdk = 26 targetSdk = 34 versionCode = 1 versionName = "1.0" diff --git a/frontend/app/src/main/java/com/example/speechbuddy/AuthActivity.kt b/frontend/app/src/main/java/com/example/speechbuddy/AuthActivity.kt index 81fbf9bd..26270b29 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/AuthActivity.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/AuthActivity.kt @@ -6,22 +6,25 @@ import android.os.Bundle import android.view.MotionEvent import android.view.inputmethod.InputMethodManager import androidx.activity.compose.setContent -import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.lifecycle.lifecycleScope +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager import com.example.speechbuddy.compose.SpeechBuddyAuth import com.example.speechbuddy.ui.SpeechBuddyTheme +import com.example.speechbuddy.utils.Constants.Companion.GUEST_ID import com.example.speechbuddy.utils.ResponseCode -import com.example.speechbuddy.viewmodel.LoginViewModel +import com.example.speechbuddy.worker.SeedDatabaseWorker import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import java.time.LocalDate @AndroidEntryPoint class AuthActivity : BaseActivity() { - private val loginViewModel: LoginViewModel by viewModels() - @RequiresApi(Build.VERSION_CODES.O) override fun onCreate(savedInstanceState: Bundle?) { @@ -40,26 +43,12 @@ class AuthActivity : BaseActivity() { } } - @RequiresApi(Build.VERSION_CODES.O) private fun subscribeObservers() { sessionManager.isAuthorized.observe(this) { isAuthorized -> - if (isAuthorized && - sessionManager.userId.value != GUEST && - sessionManager.isLogin.value != true && - getAutoBackup() && - getLastBackupDate() != LocalDate.now().toString() - ) { - autoBackup() - } else if (isAuthorized){ - navHomeActivity() - } + if (isAuthorized) navHomeActivity() } } - companion object { - const val GUEST = -1 - } - @RequiresApi(Build.VERSION_CODES.O) private fun autoBackup() { setContent { @@ -83,34 +72,40 @@ class AuthActivity : BaseActivity() { var darkMode = false lifecycleScope.launch { settingsRepository.getDarkMode().collect { - darkMode = it.data?: false + darkMode = it.data ?: false } } return darkMode } - private fun getAutoBackup(): Boolean { - var autoBackup = true - lifecycleScope.launch { - settingsRepository.getAutoBackup().collect { - autoBackup = it.data?: true - } - } - return autoBackup + @RequiresApi(Build.VERSION_CODES.O) + private suspend fun isBackupNecessary(): Boolean { + val autoBackup = settingsRepository.getAutoBackup().first().data + if (autoBackup == null || autoBackup == false) return false + val lastBackupDate = settingsRepository.getLastBackupDate().first().data + if (lastBackupDate == null || lastBackupDate == LocalDate.now().toString()) return false + return true } - private fun getLastBackupDate(): String { - var lastBackupDate = "" + @RequiresApi(Build.VERSION_CODES.O) + private fun checkPreviousAuthUser() { lifecycleScope.launch { - settingsRepository.getLastBackupDate().collect { - lastBackupDate = it.data?: "" + authRepository.checkPreviousUser().collect { + if (it.data != null) { + val userId = it.data.first + val authToken = it.data.second + val setAuthTokenJob = sessionManager.setAuthToken(authToken) + setAuthTokenJob.join() + + if (userId != GUEST_ID && sessionManager.isLogin.value != true && isBackupNecessary()) + autoBackup() + sessionManager.setUserId(userId) + } else { + val request = OneTimeWorkRequestBuilder().build() + WorkManager.getInstance(applicationContext).enqueue(request) + } } } - return lastBackupDate - } - - private fun checkPreviousAuthUser() { - loginViewModel.checkPreviousUser() } // hides keyboard @@ -120,7 +115,7 @@ class AuthActivity : BaseActivity() { if (v != null) { v.clearFocus() val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(v.getWindowToken(), 0) + imm.hideSoftInputFromWindow(v.windowToken, 0) } } return super.dispatchTouchEvent(event) @@ -128,10 +123,12 @@ class AuthActivity : BaseActivity() { @RequiresApi(Build.VERSION_CODES.O) private fun displayBackup() { - lifecycleScope.launch { + CoroutineScope(Dispatchers.IO).launch { settingsRepository.displayBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { symbolListBackup() } + ResponseCode.SUCCESS.value -> { + symbolListBackup() + } ResponseCode.NO_INTERNET_CONNECTION.value -> { sessionManager.setIsLogin(false) @@ -144,28 +141,30 @@ class AuthActivity : BaseActivity() { @RequiresApi(Build.VERSION_CODES.O) private fun symbolListBackup() { - lifecycleScope.launch { + CoroutineScope(Dispatchers.IO).launch { settingsRepository.symbolListBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { favoriteSymbolBackup() } + ResponseCode.SUCCESS.value -> { + favoriteSymbolBackup() + } ResponseCode.NO_INTERNET_CONNECTION.value -> { sessionManager.setIsLogin(false) navHomeActivity() } } - } } - } @RequiresApi(Build.VERSION_CODES.O) private fun favoriteSymbolBackup() { - lifecycleScope.launch { + CoroutineScope(Dispatchers.IO).launch { settingsRepository.favoriteSymbolBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { weightTableBackup() } + ResponseCode.SUCCESS.value -> { + weightTableBackup() + } ResponseCode.NO_INTERNET_CONNECTION.value -> { sessionManager.setIsLogin(false) @@ -178,7 +177,7 @@ class AuthActivity : BaseActivity() { @RequiresApi(Build.VERSION_CODES.O) private fun weightTableBackup() { - lifecycleScope.launch { + CoroutineScope(Dispatchers.IO).launch { settingsRepository.weightTableBackup().collect { result -> when (result.code()) { ResponseCode.SUCCESS.value -> { diff --git a/frontend/app/src/main/java/com/example/speechbuddy/BaseActivity.kt b/frontend/app/src/main/java/com/example/speechbuddy/BaseActivity.kt index 640780f9..f923f6c6 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/BaseActivity.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/BaseActivity.kt @@ -2,8 +2,8 @@ package com.example.speechbuddy import androidx.appcompat.app.AppCompatActivity import com.example.speechbuddy.domain.SessionManager +import com.example.speechbuddy.repository.AuthRepository import com.example.speechbuddy.repository.SettingsRepository -import com.example.speechbuddy.repository.UserRepository import javax.inject.Inject abstract class BaseActivity : AppCompatActivity() { @@ -12,9 +12,9 @@ abstract class BaseActivity : AppCompatActivity() { lateinit var sessionManager: SessionManager @Inject - lateinit var settingsRepository: SettingsRepository + lateinit var authRepository: AuthRepository @Inject - lateinit var userRepository: UserRepository + lateinit var settingsRepository: SettingsRepository } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/BaseApplication.kt b/frontend/app/src/main/java/com/example/speechbuddy/BaseApplication.kt index ca8ef724..6bcab81d 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/BaseApplication.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/BaseApplication.kt @@ -1,19 +1,7 @@ package com.example.speechbuddy import android.app.Application -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import com.example.speechbuddy.worker.SeedDatabaseWorker import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp -class BaseApplication : Application(){ - - override fun onCreate() { - super.onCreate() - - // create DB at the start of the app - val request = OneTimeWorkRequestBuilder().build() - WorkManager.getInstance(this).enqueue(request) - } -} \ No newline at end of file +class BaseApplication : Application() \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/HomeActivity.kt b/frontend/app/src/main/java/com/example/speechbuddy/HomeActivity.kt index 9a66fe5d..c7d761ea 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/HomeActivity.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/HomeActivity.kt @@ -1,38 +1,24 @@ package com.example.speechbuddy import android.content.Intent -import android.os.Build import android.os.Bundle -import android.util.Log import android.view.MotionEvent import android.view.inputmethod.InputMethodManager import androidx.activity.compose.setContent -import androidx.annotation.RequiresApi import androidx.lifecycle.lifecycleScope -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager import com.example.speechbuddy.compose.SpeechBuddyHome import com.example.speechbuddy.ui.SpeechBuddyTheme -import com.example.speechbuddy.worker.SeedDatabaseWorker import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -import com.example.speechbuddy.utils.Constants.Companion.GUEST_ID @AndroidEntryPoint class HomeActivity : BaseActivity() { - @RequiresApi(Build.VERSION_CODES.O) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if(sessionManager.userId.value==GUEST_ID) { - // only activates if it is in guest mode. - // does not activate when logged in since db is overwritten on login - // force the database worker to build a new db - // in order to check if the weight-db is empty or not and fill it - Log.d("test", "home acrivity starting initialization of db") - val request = OneTimeWorkRequestBuilder().build() - WorkManager.getInstance(this).enqueue(request) - } + + subscribeObservers() + setContent { SpeechBuddyTheme( settingsRepository = settingsRepository, @@ -41,13 +27,11 @@ class HomeActivity : BaseActivity() { SpeechBuddyHome(getInitialPage()) } } - - subscribeObservers() } private fun subscribeObservers() { sessionManager.isAuthorized.observe(this) { isAuthorized -> - if (!isAuthorized && !intent.getBooleanExtra("isTest", false)) navAuthActivity() + if (!isAuthorized) navAuthActivity() } } @@ -61,7 +45,7 @@ class HomeActivity : BaseActivity() { var initialPage = true lifecycleScope.launch { settingsRepository.getInitialPage().collect { - initialPage = it.data?: true + initialPage = it.data ?: true } } return initialPage @@ -71,7 +55,7 @@ class HomeActivity : BaseActivity() { var darkMode = false lifecycleScope.launch { settingsRepository.getDarkMode().collect { - darkMode = it.data?: false + darkMode = it.data ?: false } } return darkMode diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/SpeechBuddyHome.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/SpeechBuddyHome.kt index 732beb30..c856211c 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/SpeechBuddyHome.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/SpeechBuddyHome.kt @@ -1,7 +1,5 @@ package com.example.speechbuddy.compose -import android.os.Build -import androidx.annotation.RequiresApi import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically @@ -54,7 +52,6 @@ data class BottomNavItem( val iconResId: Int ) -@RequiresApi(Build.VERSION_CODES.O) @OptIn(ExperimentalMaterial3Api::class) @Composable fun SpeechBuddyHome( @@ -224,7 +221,6 @@ private fun BottomNavigationBar( } } -@RequiresApi(Build.VERSION_CODES.O) @Composable private fun SpeechBuddyHomeNavHost( navController: NavHostController, diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/AccountSettings.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/AccountSettings.kt index f0bd7c34..4fbcd408 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/AccountSettings.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/AccountSettings.kt @@ -1,8 +1,6 @@ package com.example.speechbuddy.compose.settings -import android.os.Build import android.widget.Toast -import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -29,7 +27,6 @@ import com.example.speechbuddy.compose.utils.TitleUi import com.example.speechbuddy.ui.models.AccountSettingsAlert import com.example.speechbuddy.viewmodel.AccountSettingsViewModel -@RequiresApi(Build.VERSION_CODES.O) @Composable fun AccountSettings( modifier: Modifier = Modifier, diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/BackupSettings.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/BackupSettings.kt index 1dabdcc7..44b4f530 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/BackupSettings.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/BackupSettings.kt @@ -1,8 +1,6 @@ package com.example.speechbuddy.compose.settings -import android.os.Build import android.widget.Toast -import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -30,7 +28,6 @@ import com.example.speechbuddy.compose.utils.TitleUi import com.example.speechbuddy.ui.models.BackupSettingsAlert import com.example.speechbuddy.viewmodel.BackupSettingsViewModel -@RequiresApi(Build.VERSION_CODES.O) @Composable fun BackupSettings( modifier: Modifier = Modifier, @@ -71,7 +68,9 @@ fun BackupSettings( Switch( checked = uiState.isAutoBackupEnabled, onCheckedChange = { viewModel.setAutoBackup(it) }, - modifier = Modifier.heightIn(max = 32.dp).testTag("auto_backup"), + modifier = Modifier + .heightIn(max = 32.dp) + .testTag("auto_backup"), enabled = uiState.buttonEnabled ) } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/CopyrightScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/CopyrightScreen.kt index d75bb08c..49978d2f 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/CopyrightScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/CopyrightScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.unit.dp import com.example.speechbuddy.R import com.example.speechbuddy.compose.utils.TitleUi - @Composable fun Copyright( modifier: Modifier = Modifier, diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsRow.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsRow.kt index 1dc1d9e9..f5ca7a4d 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsRow.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsRow.kt @@ -10,9 +10,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.example.speechbuddy.ui.SpeechBuddyTheme @Composable fun SettingsRow( diff --git a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsScreen.kt b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsScreen.kt index 21e0f77f..13e38655 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsScreen.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/compose/settings/SettingsScreen.kt @@ -1,7 +1,5 @@ package com.example.speechbuddy.compose.settings -import android.os.Build -import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.navigation.NavHostController @@ -9,7 +7,6 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -@RequiresApi(Build.VERSION_CODES.O) @Composable fun SettingsScreen( paddingValues: PaddingValues @@ -21,7 +18,6 @@ fun SettingsScreen( ) } -@RequiresApi(Build.VERSION_CODES.O) @Composable private fun SettingsScreenNavHost( navController: NavHostController, diff --git a/frontend/app/src/main/java/com/example/speechbuddy/data/local/UserDao.kt b/frontend/app/src/main/java/com/example/speechbuddy/data/local/UserDao.kt index 36c664e5..e8ed1a33 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/data/local/UserDao.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/data/local/UserDao.kt @@ -19,9 +19,6 @@ interface UserDao { @Query("SELECT * FROM users WHERE id = :id") fun getUserById(id: Int): Flow - @Update - suspend fun updateUser(user: UserEntity) - @Query("DELETE FROM users WHERE id = :id") suspend fun deleteUserById(id: Int) } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/data/remote/models/AccessTokenDtoMapper.kt b/frontend/app/src/main/java/com/example/speechbuddy/data/remote/models/AccessTokenDtoMapper.kt index 9df8ac9f..05effd12 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/data/remote/models/AccessTokenDtoMapper.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/data/remote/models/AccessTokenDtoMapper.kt @@ -1,7 +1,6 @@ package com.example.speechbuddy.data.remote.models import com.example.speechbuddy.domain.models.AccessToken -import com.example.speechbuddy.domain.models.AuthToken import com.example.speechbuddy.domain.utils.DomainMapper class AccessTokenDtoMapper : DomainMapper { diff --git a/frontend/app/src/main/java/com/example/speechbuddy/data/remote/models/SettingsBackupDto.kt b/frontend/app/src/main/java/com/example/speechbuddy/data/remote/models/SettingsBackupDto.kt index 3cfd75c5..e06fbe90 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/data/remote/models/SettingsBackupDto.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/data/remote/models/SettingsBackupDto.kt @@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class SettingsBackupDto( + @Json(name = "auto_backup") val autoBackup: Int? = null, @Json(name = "display_mode") val displayMode: Int? = null, @Json(name = "default_menu") val defaultMenu: Int? = null, @Json(name = "updated_at") val updatedAt: String? = null diff --git a/frontend/app/src/main/java/com/example/speechbuddy/domain/SessionManager.kt b/frontend/app/src/main/java/com/example/speechbuddy/domain/SessionManager.kt index b9f62097..9e0815f6 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/domain/SessionManager.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/domain/SessionManager.kt @@ -7,6 +7,7 @@ import com.example.speechbuddy.domain.models.AuthToken import com.example.speechbuddy.utils.Constants.Companion.GUEST_ID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch class SessionManager { @@ -43,8 +44,8 @@ class SessionManager { return _cachedToken.value?.refreshToken != null || _userId.value == GUEST_ID } - fun setAuthToken(value: AuthToken) { - CoroutineScope(Dispatchers.Main).launch { + fun setAuthToken(value: AuthToken): Job { + return CoroutineScope(Dispatchers.Main).launch { if (_cachedToken.value != value) { _cachedToken.value = value } @@ -59,7 +60,7 @@ class SessionManager { } } - fun deleteToken() { + fun nullify() { CoroutineScope(Dispatchers.Main).launch { _cachedToken.value = null _userId.value = null @@ -72,10 +73,4 @@ class SessionManager { } } - fun exitGuestMode() { - CoroutineScope(Dispatchers.Main).launch { - _userId.value = null - } - } - } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/repository/AuthRepository.kt b/frontend/app/src/main/java/com/example/speechbuddy/repository/AuthRepository.kt index d5145a18..6d5b668a 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/repository/AuthRepository.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/repository/AuthRepository.kt @@ -15,6 +15,7 @@ import com.example.speechbuddy.domain.SessionManager import com.example.speechbuddy.domain.models.AccessToken import com.example.speechbuddy.domain.models.AuthToken import com.example.speechbuddy.service.AuthService +import com.example.speechbuddy.utils.Constants.Companion.GUEST_ID import com.example.speechbuddy.utils.Resource import com.example.speechbuddy.utils.ResponseCode import com.example.speechbuddy.utils.ResponseHandler @@ -179,7 +180,7 @@ class AuthRepository @Inject constructor( }.map { pair -> val userId = pair.first val authToken = pair.second - if (userId != null && authToken.accessToken!!.isNotEmpty() && authToken.refreshToken!!.isNotEmpty()) + if ((userId == GUEST_ID) || (userId != null && authToken.accessToken!!.isNotEmpty() && authToken.refreshToken!!.isNotEmpty())) Resource.success(Pair(userId, authToken)) else Resource.error("Couldn't find previous user", null) diff --git a/frontend/app/src/main/java/com/example/speechbuddy/repository/SettingsRepository.kt b/frontend/app/src/main/java/com/example/speechbuddy/repository/SettingsRepository.kt index 30d2abc9..d514b085 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/repository/SettingsRepository.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/repository/SettingsRepository.kt @@ -35,11 +35,7 @@ class SettingsRepository @Inject constructor( private val converters: Converters, ) { suspend fun setDarkMode(value: Boolean) { - if (value) { - settingsPrefsManager.saveDarkMode(true) - } else { - settingsPrefsManager.saveDarkMode(false) - } + settingsPrefsManager.saveDarkMode(value) } suspend fun setInitialPage(page: InitialPage) { @@ -97,8 +93,12 @@ class SettingsRepository @Inject constructor( suspend fun displayBackup(): Flow> = flow { try { + var autoBackup: Int? = 1 var darkMode: Int? = 0 var initialPage: Int? = 1 + getAutoBackup().first().data?.let { + autoBackup = if (it) 1 else 0 + } getDarkMode().first().data?.let { darkMode = if (it) 1 else 0 } @@ -107,7 +107,7 @@ class SettingsRepository @Inject constructor( } val result = backupService.displayBackup( getAuthHeader(), - SettingsBackupDto(darkMode, initialPage) + SettingsBackupDto(autoBackup, darkMode, initialPage) ) emit(result) } catch (e: Exception) { @@ -177,19 +177,25 @@ class SettingsRepository @Inject constructor( return settingsRemoteSource.getDisplaySettings("Bearer $accessToken").map { response -> if (response.isSuccessful && response.code() == ResponseCode.SUCCESS.value) { response.body()?.let { settingsDto -> + val autoBackup = settingsDto.autoBackup val displayMode = settingsDto.displayMode val defaultMenu = settingsDto.defaultMenu val updatedAt = settingsDto.updatedAt!! setLastBackupDate(updatedAt) - if (displayMode == 0) { - setDarkMode(false) + if (autoBackup == 0) { + setAutoBackup(false) } else { - setDarkMode(true) + setAutoBackup(true) } - if (defaultMenu == 1) { - setInitialPage(InitialPage.SYMBOL_SELECTION) + if (displayMode == 1) { + setDarkMode(true) } else { + setDarkMode(false) + } + if (defaultMenu == 0) { setInitialPage(InitialPage.TEXT_TO_SPEECH) + } else { + setInitialPage(InitialPage.SYMBOL_SELECTION) } Resource.success(null) diff --git a/frontend/app/src/main/java/com/example/speechbuddy/repository/UserRepository.kt b/frontend/app/src/main/java/com/example/speechbuddy/repository/UserRepository.kt index 70960643..300f7213 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/repository/UserRepository.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/repository/UserRepository.kt @@ -2,7 +2,6 @@ package com.example.speechbuddy.repository import com.example.speechbuddy.data.local.UserDao import com.example.speechbuddy.data.local.UserIdPrefsManager -import com.example.speechbuddy.data.local.models.UserEntity import com.example.speechbuddy.data.local.models.UserMapper import com.example.speechbuddy.data.remote.UserRemoteSource import com.example.speechbuddy.data.remote.models.UserDtoMapper @@ -35,8 +34,7 @@ class UserRepository @Inject constructor( return userDao.getUserById(sessionManager.userId.value!!).map { userEntity -> if (userEntity != null) { Resource.success(userMapper.mapToDomainModel(userEntity)) - } - else { + } else { Resource.error("Unable to find user", null) } } @@ -71,8 +69,10 @@ class UserRepository @Inject constructor( suspend fun deleteUserInfo() { CoroutineScope(Dispatchers.IO).launch { - userDao.deleteUserById(sessionManager.userId.value!!) + val userId = sessionManager.userId.value!! + if (userId != GUEST_ID) userDao.deleteUserById(userId) userIdPrefsManager.clearUserId() + sessionManager.nullify() } } @@ -82,11 +82,4 @@ class UserRepository @Inject constructor( ) } - fun setMyInfo(id: Int, email: String, nickname: String) { - CoroutineScope(Dispatchers.IO).launch { - userIdPrefsManager.saveUserId(id) - userDao.insertUser(UserEntity(id, email, nickname)) - } - } - } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/repository/WeightTableRepository.kt b/frontend/app/src/main/java/com/example/speechbuddy/repository/WeightTableRepository.kt index 64bd422a..9cd4166f 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/repository/WeightTableRepository.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/repository/WeightTableRepository.kt @@ -146,7 +146,7 @@ class WeightTableRepository @Inject constructor( val listOfSymCntPairs = mutableListOf>() val weights = oneWeightRow[0].weights - for (i in 0 until allSymbolList.size) { + for (i in allSymbolList.indices) { listOfSymCntPairs.add(Pair(allSymbolList[i], weights[i])) } @@ -191,9 +191,7 @@ class WeightTableRepository @Inject constructor( preSymbolWeights[dbIndex2] += 10 - val aftSymbolWeights = preSymbolWeights - - updateWeightRow(targetRow, aftSymbolWeights.toList()) + updateWeightRow(targetRow, preSymbolWeights.toList()) } } } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/service/BackupService.kt b/frontend/app/src/main/java/com/example/speechbuddy/service/BackupService.kt index f5aa0659..b14b757d 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/service/BackupService.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/service/BackupService.kt @@ -5,7 +5,6 @@ import com.example.speechbuddy.data.remote.models.SettingsBackupDto import com.example.speechbuddy.data.remote.models.SymbolListDto import com.example.speechbuddy.data.remote.requests.BackupWeightTableRequest import okhttp3.ResponseBody -import retrofit2.Call import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModel.kt index 63dd23d3..68386f17 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModel.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/AccountSettingsViewModel.kt @@ -1,10 +1,7 @@ package com.example.speechbuddy.viewmodel -import android.os.Build -import androidx.annotation.RequiresApi import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.speechbuddy.domain.SessionManager import com.example.speechbuddy.repository.AuthRepository import com.example.speechbuddy.repository.SettingsRepository import com.example.speechbuddy.repository.SymbolRepository @@ -29,8 +26,7 @@ class AccountSettingsViewModel @Inject internal constructor( private val settingsRepository: SettingsRepository, private val weightTableRepository: WeightTableRepository, private val symbolRepository: SymbolRepository, - private val userRepository: UserRepository, - private val sessionManager: SessionManager + private val userRepository: UserRepository ) : ViewModel() { private val _uiState = MutableStateFlow(AccountSettingsUiState()) @@ -78,8 +74,6 @@ class AccountSettingsViewModel @Inject internal constructor( weightTableRepository.resetAllWeightRows() symbolRepository.resetSymbolsAndFavorites() userRepository.deleteUserInfo() - sessionManager.deleteToken() - // authToken is already cleared by the repository hideAlert() } @@ -101,8 +95,6 @@ class AccountSettingsViewModel @Inject internal constructor( weightTableRepository.resetAllWeightRows() symbolRepository.resetSymbolsAndFavorites() userRepository.deleteUserInfo() - sessionManager.deleteToken() - // authToken is already cleared by the repository hideAlert() } @@ -120,28 +112,32 @@ class AccountSettingsViewModel @Inject internal constructor( weightTableRepository.resetAllWeightRows() symbolRepository.resetSymbolsAndFavorites() userRepository.deleteUserInfo() - sessionManager.exitGuestMode() } } - @RequiresApi(Build.VERSION_CODES.O) private fun displayBackup() { viewModelScope.launch { settingsRepository.displayBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { symbolListBackup() } - ResponseCode.NO_INTERNET_CONNECTION.value -> { handleNoInternetConnection() } + ResponseCode.SUCCESS.value -> { + symbolListBackup() + } + + ResponseCode.NO_INTERNET_CONNECTION.value -> { + handleNoInternetConnection() + } } } } } - @RequiresApi(Build.VERSION_CODES.O) private fun symbolListBackup() { viewModelScope.launch { settingsRepository.symbolListBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { favoriteSymbolBackup() } + ResponseCode.SUCCESS.value -> { + favoriteSymbolBackup() + } ResponseCode.NO_INTERNET_CONNECTION.value -> { handleNoInternetConnection() @@ -153,12 +149,13 @@ class AccountSettingsViewModel @Inject internal constructor( } - @RequiresApi(Build.VERSION_CODES.O) private fun favoriteSymbolBackup() { viewModelScope.launch { settingsRepository.favoriteSymbolBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { weightTableBackup() } + ResponseCode.SUCCESS.value -> { + weightTableBackup() + } ResponseCode.NO_INTERNET_CONNECTION.value -> { handleNoInternetConnection() @@ -168,7 +165,6 @@ class AccountSettingsViewModel @Inject internal constructor( } } - @RequiresApi(Build.VERSION_CODES.O) private fun weightTableBackup() { viewModelScope.launch { settingsRepository.weightTableBackup().collect { result -> @@ -185,7 +181,6 @@ class AccountSettingsViewModel @Inject internal constructor( } } - @RequiresApi(Build.VERSION_CODES.O) fun backup() { _uiState.update { currentState -> currentState.copy( @@ -205,7 +200,6 @@ class AccountSettingsViewModel @Inject internal constructor( } } - @RequiresApi(Build.VERSION_CODES.O) private fun handleSuccess() { viewModelScope.launch { settingsRepository.setLastBackupDate(LocalDate.now().toString()) diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/BackupSettingsViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/BackupSettingsViewModel.kt index c51b37c6..a17d45e0 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/BackupSettingsViewModel.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/BackupSettingsViewModel.kt @@ -1,9 +1,5 @@ package com.example.speechbuddy.viewmodel -import android.os.Build -import androidx.annotation.RequiresApi -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.speechbuddy.repository.SettingsRepository @@ -11,6 +7,8 @@ import com.example.speechbuddy.ui.models.BackupSettingsAlert import com.example.speechbuddy.ui.models.BackupSettingsUiState import com.example.speechbuddy.utils.ResponseCode import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -58,7 +56,7 @@ class BackupSettingsViewModel @Inject internal constructor( var autoBackup = false viewModelScope.launch { repository.getAutoBackup().collect { - autoBackup = it.data?: false + autoBackup = it.data ?: false } } return autoBackup @@ -68,7 +66,7 @@ class BackupSettingsViewModel @Inject internal constructor( var lastBackupDate = "" viewModelScope.launch { repository.getLastBackupDate().collect { - lastBackupDate = it.data?: "" + lastBackupDate = it.data ?: "" } } return lastBackupDate @@ -93,27 +91,33 @@ class BackupSettingsViewModel @Inject internal constructor( } } - @RequiresApi(Build.VERSION_CODES.O) private fun displayBackup() { - viewModelScope.launch { + CoroutineScope(Dispatchers.IO).launch { repository.displayBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { symbolListBackup() } + ResponseCode.SUCCESS.value -> { + symbolListBackup() + } - ResponseCode.NO_INTERNET_CONNECTION.value -> { handleNoInternetConnection() } + ResponseCode.NO_INTERNET_CONNECTION.value -> { + handleNoInternetConnection() + } } } } } - @RequiresApi(Build.VERSION_CODES.O) private fun symbolListBackup() { - viewModelScope.launch { + CoroutineScope(Dispatchers.IO).launch { repository.symbolListBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { favoriteSymbolBackup() } + ResponseCode.SUCCESS.value -> { + favoriteSymbolBackup() + } - ResponseCode.NO_INTERNET_CONNECTION.value -> { handleNoInternetConnection() } + ResponseCode.NO_INTERNET_CONNECTION.value -> { + handleNoInternetConnection() + } } } @@ -121,33 +125,38 @@ class BackupSettingsViewModel @Inject internal constructor( } - @RequiresApi(Build.VERSION_CODES.O) private fun favoriteSymbolBackup() { - viewModelScope.launch { + CoroutineScope(Dispatchers.IO).launch { repository.favoriteSymbolBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { weightTableBackup() } + ResponseCode.SUCCESS.value -> { + weightTableBackup() + } - ResponseCode.NO_INTERNET_CONNECTION.value -> { handleNoInternetConnection() } + ResponseCode.NO_INTERNET_CONNECTION.value -> { + handleNoInternetConnection() + } } } } } - @RequiresApi(Build.VERSION_CODES.O) private fun weightTableBackup() { - viewModelScope.launch { + CoroutineScope(Dispatchers.IO).launch { repository.weightTableBackup().collect { result -> when (result.code()) { - ResponseCode.SUCCESS.value -> { handleSuccess() } + ResponseCode.SUCCESS.value -> { + handleSuccess() + } - ResponseCode.NO_INTERNET_CONNECTION.value -> { handleNoInternetConnection() } + ResponseCode.NO_INTERNET_CONNECTION.value -> { + handleNoInternetConnection() + } } } } } - @RequiresApi(Build.VERSION_CODES.O) fun backup() { changeLoadingState() displayBackup() @@ -156,22 +165,20 @@ class BackupSettingsViewModel @Inject internal constructor( private fun handleNoInternetConnection() { changeLoadingState() _uiState.update { currentState -> - currentState.copy ( + currentState.copy( alert = BackupSettingsAlert.CONNECTION ) } } - @RequiresApi(Build.VERSION_CODES.O) private fun handleSuccess() { changeLoadingState() setLastBackupDate(LocalDate.now().toString()) _uiState.update { currentState -> - currentState.copy ( + currentState.copy( alert = BackupSettingsAlert.SUCCESS, ) } } - } \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/EmailVerificationViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/EmailVerificationViewModel.kt index ab0db07d..5703e00a 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/EmailVerificationViewModel.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/EmailVerificationViewModel.kt @@ -3,12 +3,9 @@ package com.example.speechbuddy.viewmodel import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.speechbuddy.R -import com.example.speechbuddy.data.remote.requests.AuthSendCodeRequest import com.example.speechbuddy.data.remote.requests.AuthVerifyEmailRequest import com.example.speechbuddy.domain.SessionManager import com.example.speechbuddy.domain.models.AuthToken @@ -21,6 +18,7 @@ import com.example.speechbuddy.utils.ResponseHandler import com.example.speechbuddy.utils.Status import com.example.speechbuddy.utils.isValidCode import com.example.speechbuddy.utils.isValidEmail +import com.example.speechbuddy.viewmodel.strategy.NavigationSendCode import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -61,8 +59,8 @@ class EmailVerificationViewModel @Inject internal constructor( if (_uiState.value.error?.type == EmailVerificationErrorType.CODE) validateCode() } - private fun changeLoadingState(){ - _uiState.update {currentState -> + fun changeLoadingState() { + _uiState.update { currentState -> currentState.copy( loading = !currentState.loading, buttonEnabled = !currentState.buttonEnabled @@ -114,147 +112,54 @@ class EmailVerificationViewModel @Inject internal constructor( ) } } else { - if (source.value == "signup") sendCodeForSignup() - if (source.value == "reset_password") sendCodeForResetPassword() - else _uiState.update { currentState -> - currentState.copy( - isValidEmail = false, - error = EmailVerificationError( - type = EmailVerificationErrorType.UNKNOWN, - messageId = R.string.unknown_error - ) - ) - } + val navigationSendCode = NavigationSendCode() + navigationSendCode.setSource(source.value!!) + navigationSendCode.sendCode(this, repository, responseHandler) } } - private fun sendCodeForSignup() { - changeLoadingState() - viewModelScope.launch { - repository.sendCodeForSignup( - AuthSendCodeRequest( - email = emailInput - ) - ).collect { result -> - changeLoadingState() - when (result.code()) { - ResponseCode.SUCCESS.value -> { - _uiState.update { currentState -> - currentState.copy( - isCodeSuccessfullySent = true, - error = null - ) - } - } - - ResponseCode.BAD_REQUEST.value -> { - val errorMessageId = - when (responseHandler.parseErrorResponse(result.errorBody()!!).key) { - "email" -> R.string.wrong_email - "already_taken" -> R.string.email_already_taken - else -> R.string.unknown_error - } - _uiState.update { currentState -> - currentState.copy( - isValidEmail = false, - error = EmailVerificationError( - type = EmailVerificationErrorType.EMAIL, - messageId = errorMessageId - ) - ) - } - } - - ResponseCode.NO_INTERNET_CONNECTION.value -> { - _uiState.update { currentState -> - currentState.copy( - isValidEmail = false, - error = EmailVerificationError( - type = EmailVerificationErrorType.CONNECTION, - messageId = R.string.connection_error - ) - ) - } - } - - else -> { - _uiState.update { currentState -> - currentState.copy( - isValidEmail = false, - error = EmailVerificationError( - type = EmailVerificationErrorType.UNKNOWN, - messageId = R.string.unknown_error - ) - ) - } - } - } - } + fun handleSendCodeSuccess() { + _uiState.update { currentState -> + currentState.copy( + isCodeSuccessfullySent = true, + error = null + ) } } - private fun sendCodeForResetPassword() { - changeLoadingState() - viewModelScope.launch { - repository.sendCodeForResetPassword( - AuthSendCodeRequest( - email = emailInput + fun handleSendCodeBadRequest(errorMessageId: Int) { + _uiState.update { currentState -> + currentState.copy( + isValidEmail = false, + error = EmailVerificationError( + type = EmailVerificationErrorType.EMAIL, + messageId = errorMessageId ) - ).collect { result -> - changeLoadingState() - when (result.code()) { - ResponseCode.SUCCESS.value -> { - _uiState.update { currentState -> - currentState.copy( - isCodeSuccessfullySent = true, - error = null - ) - } - } - - ResponseCode.BAD_REQUEST.value -> { - val errorMessageId = - when (responseHandler.parseErrorResponse(result.errorBody()!!).key) { - "email" -> R.string.wrong_email - "no_user" -> R.string.unregistered_email - else -> R.string.unknown_error - } - _uiState.update { currentState -> - currentState.copy( - isValidEmail = false, - error = EmailVerificationError( - type = EmailVerificationErrorType.EMAIL, - messageId = errorMessageId - ) - ) - } - } + ) + } + } - ResponseCode.NO_INTERNET_CONNECTION.value -> { - _uiState.update { currentState -> - currentState.copy( - isValidEmail = false, - error = EmailVerificationError( - type = EmailVerificationErrorType.CONNECTION, - messageId = R.string.connection_error - ) - ) - } - } + fun handleSendCodeNoInternetConnection() { + _uiState.update { currentState -> + currentState.copy( + isValidEmail = false, + error = EmailVerificationError( + type = EmailVerificationErrorType.CONNECTION, + messageId = R.string.connection_error + ) + ) + } + } - else -> { - _uiState.update { currentState -> - currentState.copy( - isValidEmail = false, - error = EmailVerificationError( - type = EmailVerificationErrorType.UNKNOWN, - messageId = R.string.unknown_error - ) - ) - } - } - } - } + fun handleSendCodeUnknownError() { + _uiState.update { currentState -> + currentState.copy( + isValidEmail = false, + error = EmailVerificationError( + type = EmailVerificationErrorType.UNKNOWN, + messageId = R.string.unknown_error + ) + ) } } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/LoginViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/LoginViewModel.kt index cd4b72a8..5215d87c 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/LoginViewModel.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/LoginViewModel.kt @@ -18,6 +18,8 @@ import com.example.speechbuddy.utils.Status import com.example.speechbuddy.utils.isValidEmail import com.example.speechbuddy.utils.isValidPassword import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -139,13 +141,14 @@ class LoginViewModel @Inject internal constructor( if (resource.status == Status.SUCCESS) { // AccessToken is already saved in AuthTokenPrefsManager by the authRepository sessionManager.setAuthToken(resource.data!!) + runBlocking { val jobs = mutableListOf() - jobs.add(viewModelScope.launch { getMyInfoFromRemote(resource.data.accessToken) }) - jobs.add(viewModelScope.launch { getMyDisplaySettingsFromRemote(resource.data.accessToken) }) - jobs.add(viewModelScope.launch { getSymbolListFromRemote(resource.data.accessToken) }) - jobs.add(viewModelScope.launch { getFavoritesListFromRemote(resource.data.accessToken) }) - jobs.add(viewModelScope.launch { getWeightTableFromRemote(resource.data.accessToken) }) + jobs.add(getMyInfoFromRemote(resource.data.accessToken)) + jobs.add(getMyDisplaySettingsFromRemote(resource.data.accessToken)) + jobs.add(getSymbolListFromRemote(resource.data.accessToken)) + jobs.add(getFavoritesListFromRemote(resource.data.accessToken)) + jobs.add(getWeightTableFromRemote(resource.data.accessToken)) jobs.joinAll() @@ -190,8 +193,8 @@ class LoginViewModel @Inject internal constructor( } } - private fun getMyInfoFromRemote(accessToken: String?) { - viewModelScope.launch { + private fun getMyInfoFromRemote(accessToken: String?): Job { + return CoroutineScope(Dispatchers.IO).launch { userRepository.getMyInfoFromRemote(accessToken).collect { resource -> if (resource.status == Status.SUCCESS) { sessionManager.setUserId(resource.data!!.id) @@ -222,8 +225,8 @@ class LoginViewModel @Inject internal constructor( } } - private fun getMyDisplaySettingsFromRemote(accessToken: String?) { - viewModelScope.launch { + private fun getMyDisplaySettingsFromRemote(accessToken: String?): Job { + return CoroutineScope(Dispatchers.IO).launch { settingsRepository.getDisplaySettingsFromRemote(accessToken).collect { resource -> if (resource.message?.contains("unknown", ignoreCase = true) == true) { changeLoadingState() @@ -252,8 +255,8 @@ class LoginViewModel @Inject internal constructor( } } - private fun getSymbolListFromRemote(accessToken: String?) { - viewModelScope.launch { + private fun getSymbolListFromRemote(accessToken: String?): Job { + return CoroutineScope(Dispatchers.IO).launch { settingsRepository.getSymbolListFromRemote(accessToken).collect { resource -> if (resource.message?.contains("unknown", ignoreCase = true) == true) { changeLoadingState() @@ -282,8 +285,8 @@ class LoginViewModel @Inject internal constructor( } } - private fun getFavoritesListFromRemote(accessToken: String?) { - viewModelScope.launch { + private fun getFavoritesListFromRemote(accessToken: String?): Job { + return CoroutineScope(Dispatchers.IO).launch { settingsRepository.getFavoritesListFromRemote(accessToken).collect { resource -> if (resource.message?.contains("unknown", ignoreCase = true) == true) { changeLoadingState() @@ -312,8 +315,8 @@ class LoginViewModel @Inject internal constructor( } } - private fun getWeightTableFromRemote(accessToken: String?) { - viewModelScope.launch { + private fun getWeightTableFromRemote(accessToken: String?): Job { + return CoroutineScope(Dispatchers.IO).launch { settingsRepository.getWeightTableFromRemote(accessToken).collect { resource -> if (resource.message?.contains("unknown", ignoreCase = true) == true) { changeLoadingState() @@ -342,17 +345,6 @@ class LoginViewModel @Inject internal constructor( } } - fun checkPreviousUser() { - viewModelScope.launch { - authRepository.checkPreviousUser().collect { resource -> - if (resource.data != null) { - sessionManager.setUserId(resource.data.first) - sessionManager.setAuthToken(resource.data.second) - } - } - } - } - fun enterGuestMode() { viewModelScope.launch { userRepository.setGuestMode() diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/ResetPasswordViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/ResetPasswordViewModel.kt index 6ea749a9..33cd8cd1 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/ResetPasswordViewModel.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/ResetPasswordViewModel.kt @@ -118,7 +118,7 @@ class ResetPasswordViewModel @Inject internal constructor( changeLoadingState() when (result.code()) { ResponseCode.SUCCESS.value -> { - sessionManager.deleteToken() + sessionManager.nullify() onSuccess() } diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/SymbolSelectionViewModel.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/SymbolSelectionViewModel.kt index 0cf4afc3..ac2502ed 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/SymbolSelectionViewModel.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/SymbolSelectionViewModel.kt @@ -80,11 +80,7 @@ class SymbolSelectionViewModel @Inject internal constructor( fun setQuery(input: String) { queryInput = input - /** - * Passes a new queryInput to getEntries() to ensure that - * getEntries() is called precisely because of a change in query - */ - getEntries(input) + getEntries() } fun clear(symbolItem: SymbolItem) { @@ -106,6 +102,12 @@ class SymbolSelectionViewModel @Inject internal constructor( val newSymbolItem = SymbolItem(id = selectedSymbols.size, symbol = symbol) selectedSymbols = selectedSymbols.plus(newSymbolItem) + _uiState.update { currentState -> + currentState.copy( + displayMode = DisplayMode.SYMBOL + ) + } + provideSuggestion(symbol) return newSymbolItem.id @@ -157,7 +159,7 @@ class SymbolSelectionViewModel @Inject internal constructor( } } - private fun getEntries(query: String? = null) { + private fun getEntries() { getEntriesJob?.cancel() needsToBeRecalled = false getEntriesJob = viewModelScope.launch { @@ -168,31 +170,16 @@ class SymbolSelectionViewModel @Inject internal constructor( } } - /** - * In case of DisplayMode.SYMBOL and DisplayMode.CATEGORY, - * if getEntries() is called by setQuery(), - * retrieve both symbols and categories from the repository - */ DisplayMode.SYMBOL -> { - if (!query.isNullOrEmpty()) // called from setQuery() - repository.getEntries(query).collect { entries -> - _entries.postValue(entries) - } - else // called from somewhere else - repository.getSymbols(queryInput).collect { symbols -> - _entries.postValue(symbols) - } + repository.getSymbols(queryInput).collect { symbols -> + _entries.postValue(symbols) + } } DisplayMode.CATEGORY -> { - if (!query.isNullOrEmpty()) - repository.getEntries(query).collect { entries -> - _entries.postValue(entries) - } - else - repository.getCategories(queryInput).collect { categories -> - _entries.postValue(categories) - } + repository.getCategories(queryInput).collect { categories -> + _entries.postValue(categories) + } } DisplayMode.FAVORITE -> { diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/NavigationSendCode.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/NavigationSendCode.kt new file mode 100644 index 00000000..1c185643 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/NavigationSendCode.kt @@ -0,0 +1,23 @@ +package com.example.speechbuddy.viewmodel.strategy + +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.utils.ResponseHandler +import com.example.speechbuddy.viewmodel.EmailVerificationViewModel + +class NavigationSendCode { + private lateinit var sendCodeStrategy: SendCodeStrategy + private lateinit var source : String + + fun setSource(source: String) { + this.source = source + if(source=="signup") { + sendCodeStrategy = SendCodeForSignupStrategy() + } else if(source=="reset_password") { + sendCodeStrategy = SendCodeForResetPasswordStrategy() + } + } + + fun sendCode(viewModel: EmailVerificationViewModel, repository: AuthRepository, responseHandler: ResponseHandler) { + sendCodeStrategy.sendCode(viewModel, repository, responseHandler) + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeForResetPasswordStrategy.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeForResetPasswordStrategy.kt new file mode 100644 index 00000000..04fa0be2 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeForResetPasswordStrategy.kt @@ -0,0 +1,49 @@ +package com.example.speechbuddy.viewmodel.strategy + +import androidx.lifecycle.viewModelScope +import com.example.speechbuddy.R +import com.example.speechbuddy.data.remote.requests.AuthSendCodeRequest +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.utils.ResponseCode +import com.example.speechbuddy.utils.ResponseHandler +import com.example.speechbuddy.viewmodel.EmailVerificationViewModel +import kotlinx.coroutines.launch + +class SendCodeForResetPasswordStrategy: SendCodeStrategy { + override fun sendCode(viewModel: EmailVerificationViewModel, repository: AuthRepository, responseHandler: ResponseHandler) { + viewModel.changeLoadingState() + viewModel.viewModelScope.launch { + repository.sendCodeForResetPassword( + AuthSendCodeRequest( + email = viewModel.emailInput + ) + ).collect { result -> + viewModel.changeLoadingState() + when (result.code()) { + ResponseCode.SUCCESS.value -> { + viewModel.handleSendCodeSuccess() + } + + ResponseCode.BAD_REQUEST.value -> { + val errorMessageId = + when (responseHandler.parseErrorResponse(result.errorBody()!!).key) { + "email" -> R.string.wrong_email + "no_user" -> R.string.unregistered_email + else -> R.string.unknown_error + } + viewModel.handleSendCodeBadRequest(errorMessageId) + } + + ResponseCode.NO_INTERNET_CONNECTION.value -> { + viewModel.handleSendCodeNoInternetConnection() + } + + else -> { + viewModel.handleSendCodeUnknownError() + } + } + } + } + } +} + diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeForSignupStrategy.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeForSignupStrategy.kt new file mode 100644 index 00000000..e1c315c1 --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeForSignupStrategy.kt @@ -0,0 +1,48 @@ +package com.example.speechbuddy.viewmodel.strategy + +import androidx.lifecycle.viewModelScope +import com.example.speechbuddy.R +import com.example.speechbuddy.data.remote.requests.AuthSendCodeRequest +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.utils.ResponseCode +import com.example.speechbuddy.utils.ResponseHandler +import com.example.speechbuddy.viewmodel.EmailVerificationViewModel +import kotlinx.coroutines.launch + +class SendCodeForSignupStrategy: SendCodeStrategy { + override fun sendCode(viewModel: EmailVerificationViewModel, repository: AuthRepository, responseHandler: ResponseHandler) { + viewModel.changeLoadingState() + viewModel.viewModelScope.launch { + repository.sendCodeForSignup( + AuthSendCodeRequest( + email = viewModel.emailInput + ) + ).collect { result -> + viewModel.changeLoadingState() + when (result.code()) { + ResponseCode.SUCCESS.value -> { + viewModel.handleSendCodeSuccess() + } + + ResponseCode.BAD_REQUEST.value -> { + val errorMessageId = + when (responseHandler.parseErrorResponse(result.errorBody()!!).key) { + "email" -> R.string.wrong_email + "already_taken" -> R.string.email_already_taken + else -> R.string.unknown_error + } + viewModel.handleSendCodeBadRequest(errorMessageId) + } + + ResponseCode.NO_INTERNET_CONNECTION.value -> { + viewModel.handleSendCodeNoInternetConnection() + } + + else -> { + viewModel.handleSendCodeUnknownError() + } + } + } + } + } +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeStrategy.kt b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeStrategy.kt new file mode 100644 index 00000000..6618561d --- /dev/null +++ b/frontend/app/src/main/java/com/example/speechbuddy/viewmodel/strategy/SendCodeStrategy.kt @@ -0,0 +1,9 @@ +package com.example.speechbuddy.viewmodel.strategy + +import com.example.speechbuddy.repository.AuthRepository +import com.example.speechbuddy.utils.ResponseHandler +import com.example.speechbuddy.viewmodel.EmailVerificationViewModel + +interface SendCodeStrategy { + fun sendCode(viewModel: EmailVerificationViewModel, repository: AuthRepository, responseHandler: ResponseHandler) +} \ No newline at end of file diff --git a/frontend/app/src/main/java/com/example/speechbuddy/worker/SeedDatabaseWorker.kt b/frontend/app/src/main/java/com/example/speechbuddy/worker/SeedDatabaseWorker.kt index d324e565..d8a23093 100644 --- a/frontend/app/src/main/java/com/example/speechbuddy/worker/SeedDatabaseWorker.kt +++ b/frontend/app/src/main/java/com/example/speechbuddy/worker/SeedDatabaseWorker.kt @@ -26,28 +26,41 @@ class SeedDatabaseWorker( try { val database = AppDatabase.getInstance(applicationContext) - - if (!applicationContext.getDatabasePath("speechbuddy-db").exists()) { - Log.d("create-db", "no db->initializing db") - createWeightTable(database) + val currentDb = database.weightRowDao().getAllWeightRows().first() + var cnt = 0 + for (currentWeightRowEntity in currentDb) { + if (currentWeightRowEntity.weights.all{it == 0}){ + cnt+=1 // count the rows with 0 weights + } } - else{ // check if the weight table is filled with 0 - val currentDb = database.weightRowDao().getAllWeightRows().first() - var cnt = 0 - for (currentWeightRowEntity in currentDb) { - if (currentWeightRowEntity.weights.all{it == 0}){ - cnt+=1 // count the rows with 0 weights + if (cnt == currentDb.size){ // if weight table is filled with 0 + Log.d("create-db", "empty db->initializing db") + applicationContext.assets.open("weight_table.txt").use { inputStream -> + val weightRows = mutableListOf() + + val inputList: MutableList> = ArrayList() + inputStream.bufferedReader().useLines { lines -> + lines.forEach { line -> + inputList.add( + line.split(",").mapNotNull { it.trim().toIntOrNull() }) + } } - } - if (cnt == currentDb.size){ // if weight table is filled with 0 - Log.d("create-db", "empty db->initializing db") - createWeightTable(database) - } - else{ - Log.d("create-db", "db exists") + var id = 1 + for (weight in inputList) { + val weightRowEntity = WeightRowEntity(id++, weight) + weightRows.add(weightRowEntity) + } + + database.weightRowDao().upsertAll(weightRows) + + Result.success() } } - + else{ + Log.d("create-db", "db exists") + } + + applicationContext.assets.open(SYMBOL_DATA_FILENAME).use { inputStream -> JsonReader(inputStream.reader()).use { jsonReader -> @@ -78,27 +91,4 @@ class SeedDatabaseWorker( } } - private suspend fun createWeightTable(database: AppDatabase){ - val weightRows = mutableListOf() - - applicationContext.assets.open("weight_table.txt").use { inputStream -> - val inputList: MutableList> = ArrayList() - inputStream.bufferedReader().useLines { lines -> - lines.forEach { line -> - inputList.add( - line.split(",").mapNotNull { it.trim().toIntOrNull() }) - } - } - var id = 1 - for (weight in inputList) { - val weightRowEntity = WeightRowEntity(id++, weight) - weightRows.add(weightRowEntity) - } - } - - database.weightRowDao().upsertAll(weightRows) - - Result.success() - } - } \ No newline at end of file diff --git a/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/EmailVerificationViewModelTest.kt b/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/EmailVerificationViewModelTest.kt index e74a93d0..ab98cc0b 100644 --- a/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/EmailVerificationViewModelTest.kt +++ b/frontend/app/src/test/java/com/example/speechbuddy/viewmodel/EmailVerificationViewModelTest.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import okhttp3.ResponseBody.Companion.toResponseBody @@ -312,18 +313,6 @@ class EmailVerificationViewModelTest { coVerify { navigateCallback("reset_password") } } - - @Test - fun `should fail code send when source is invalid`() { - viewModel.setSource("invalid source") - viewModel.setEmail(validEmail) - viewModel.sendCode() - - assertEquals(viewModel.uiState.value.error?.type, EmailVerificationErrorType.UNKNOWN) - assertEquals(viewModel.uiState.value.error?.messageId, R.string.unknown_error) - assertEquals(viewModel.uiState.value.isCodeSuccessfullySent, false) - } - @Test fun `should fail code send when bad request occurs`() { val sendCodeRequest = AuthSendCodeRequest(wrongEmail)