diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index dd892c4c..4c1b78b1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -20,7 +20,7 @@ android {
defaultConfig {
applicationId = "com.joeloewi.croissant"
- versionCode = 44
+ versionCode = 45
versionName = "1.2.1"
targetSdk = 34
@@ -151,8 +151,6 @@ dependencies {
implementation(libs.androidx.profileinstaller)
- implementation(libs.tts)
-
//open source license activity
implementation(libs.gms.play.services.oss.licenses)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e9523993..2d7fa733 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,6 +23,9 @@
+
+
+
Unit,
onShowDefaultScreen: () -> Unit
) {
+ val context = LocalContext.current
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
snapshotFlow { isFirstLaunch() }.catch { }.filterNotNull()
.collect { showFirstLaunchScreen ->
withContext(Dispatchers.Main) {
- if (showFirstLaunchScreen) {
+ val anyOfPermissionsIsDenied = listOf(
+ CroissantPermission.AccessHoYoLABSession.permission,
+ CroissantPermission.PostNotifications.permission
+ ).any {
+ ContextCompat.checkSelfPermission(
+ context,
+ it
+ ) == PackageManager.PERMISSION_DENIED
+ } || context.getSystemService()
+ ?.canScheduleExactAlarmsCompat() == false
+
+ if (showFirstLaunchScreen || anyOfPermissionsIsDenied) {
onShowFirstLaunchScreen()
} else {
onShowDefaultScreen()
diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/global/screen/FirstLaunchScreen.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/global/screen/FirstLaunchScreen.kt
index 1ea84cb8..d08fe3eb 100644
--- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/global/screen/FirstLaunchScreen.kt
+++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/global/screen/FirstLaunchScreen.kt
@@ -62,6 +62,7 @@ import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus
+import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.joeloewi.croissant.R
import com.joeloewi.croissant.ui.theme.DefaultDp
@@ -101,7 +102,7 @@ private fun FirstLaunchContent(
CroissantPermission.PostNotifications.permission
),
onPermissionsResult = {
- if (scheduleExactAlarmPermissionState.status != PermissionStatus.Granted) {
+ if (!scheduleExactAlarmPermissionState.status.isGranted) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/screen/DeveloperInfoScreen.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/screen/DeveloperInfoScreen.kt
index 829ad0b9..53e386c5 100644
--- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/screen/DeveloperInfoScreen.kt
+++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/screen/DeveloperInfoScreen.kt
@@ -2,6 +2,7 @@ package com.joeloewi.croissant.ui.navigation.main.settings.screen
import android.content.Intent
import android.net.Uri
+import android.speech.tts.TextToSpeech
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -37,6 +38,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.core.os.bundleOf
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
@@ -49,7 +51,6 @@ import com.joeloewi.croissant.util.navigationIconButton
import com.joeloewi.croissant.viewmodel.DeveloperInfoViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import nl.marc_apps.tts.TextToSpeechInstance
@Composable
fun DeveloperInfoScreen(
@@ -67,7 +68,7 @@ fun DeveloperInfoScreen(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DeveloperInfoContent(
- textToSpeech: () -> LCE,
+ textToSpeech: () -> LCE,
onNavigateUp: () -> Unit
) {
val activity = LocalActivity.current
@@ -111,7 +112,12 @@ private fun DeveloperInfoContent(
) {
coroutineScope.launch(Dispatchers.IO) {
textToSpeech().content?.runCatching {
- say("안아줘요", true)
+ speak(
+ "안아줘요",
+ TextToSpeech.QUEUE_FLUSH,
+ bundleOf(),
+ "hug_me"
+ )
}
}
},
diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/TextToSpeechFactory.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/TextToSpeechFactory.kt
new file mode 100644
index 00000000..5062ede4
--- /dev/null
+++ b/app/src/main/kotlin/com/joeloewi/croissant/util/TextToSpeechFactory.kt
@@ -0,0 +1,42 @@
+package com.joeloewi.croissant.util
+
+import android.content.Context
+import android.speech.tts.TextToSpeech
+import com.joeloewi.croissant.state.foldAsLce
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
+import java.util.Locale
+import javax.inject.Inject
+
+class TextToSpeechFactory @Inject constructor(
+ @ApplicationContext private val context: Context
+) {
+ val flow = callbackFlow {
+ var textToSpeech: TextToSpeech? = null
+
+ textToSpeech = TextToSpeech(context) { status ->
+ trySend(runCatching {
+ if (status != TextToSpeech.SUCCESS) {
+ throw IllegalStateException()
+ }
+ textToSpeech!!.apply {
+ val currentLocale = Locale.getDefault()
+ val targetLocale = if (availableLanguages.contains(currentLocale)) {
+ currentLocale
+ } else {
+ Locale.ENGLISH
+ }
+ language = targetLocale
+ }
+ }.foldAsLce())
+ }
+
+ awaitClose {
+ with(textToSpeech) {
+ stop()
+ shutdown()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/DeveloperInfoViewModel.kt b/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/DeveloperInfoViewModel.kt
index 6908a93c..3c15ef66 100644
--- a/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/DeveloperInfoViewModel.kt
+++ b/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/DeveloperInfoViewModel.kt
@@ -3,37 +3,20 @@ package com.joeloewi.croissant.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.joeloewi.croissant.state.LCE
+import com.joeloewi.croissant.util.TextToSpeechFactory
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
-import nl.marc_apps.tts.TextToSpeechFactory
import javax.inject.Inject
@HiltViewModel
class DeveloperInfoViewModel @Inject constructor(
- private val textToSpeechFactory: TextToSpeechFactory
+ textToSpeechFactory: TextToSpeechFactory
) : ViewModel() {
- val textToSpeech = callbackFlow {
- val textToSpeech = textToSpeechFactory.runCatching {
- createOrThrow()
- }.fold(
- onSuccess = {
- LCE.Content(it)
- },
- onFailure = {
- LCE.Error(it)
- }
- )
-
- trySend(textToSpeech)
-
- awaitClose { textToSpeech.content?.close() }
- }.catch { }.flowOn(Dispatchers.IO).stateIn(
+ val textToSpeech = textToSpeechFactory.flow.catch { }.flowOn(Dispatchers.IO).stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = LCE.Loading