diff --git a/LOCALIZATION_README.md b/LOCALIZATION_README.md
deleted file mode 100644
index f330f5f..0000000
--- a/LOCALIZATION_README.md
+++ /dev/null
@@ -1,175 +0,0 @@
-# Multi-Language Localization Setup
-
-This project now supports multiple languages: English, Portuguese, and Spanish.
-
-## 📁 File Structure
-
-```
-app/src/main/res/
-├── values/
-│ └── strings.xml # English strings (default)
-├── values-pt/
-│ └── strings.xml # Portuguese strings
-└── values-es/
- └── strings.xml # Spanish strings
-```
-
-## 🌐 How It Works
-
-### Automatic Language Detection
-- The app automatically detects the device's language setting
-- If the device is set to Portuguese, it will use `values-pt/strings.xml`
-- If the device is set to Spanish, it will use `values-es/strings.xml`
-- If the device is set to any other language, it will use `values/strings.xml` (English)
-
-### String Resources
-All user-facing text in the app is now localized:
-- **UI Labels**: Buttons, titles, descriptions
-- **Dialog Messages**: Confirmations, errors, information
-- **Content Descriptions**: Accessibility labels for screen readers
-- **Settings**: Configuration options and descriptions
-
-## 🔧 Implementation Details
-
-### Compose UI
-The app uses `stringResource()` calls throughout the Compose UI:
-```kotlin
-Text(stringResource(R.string.app_name))
-Text(stringResource(R.string.settings))
-```
-
-### Android Manifest
-The app name is localized:
-```xml
-android:label="@string/app_name"
-```
-
-## 📝 Adding New Strings
-
-### 1. Add to English (values/strings.xml)
-```xml
-New Feature
-```
-
-### 2. Add to Portuguese (values-pt/strings.xml)
-```xml
-Nova Funcionalidade
-```
-
-### 3. Add to Spanish (values-es/strings.xml)
-```xml
-Nueva Funcionalidad
-```
-
-### 4. Use in Code
-```kotlin
-Text(stringResource(R.string.new_feature_title))
-```
-
-## 🌍 Supported Languages
-
-| Language | Code | File | Status |
-|----------|------|------|---------|
-| English | `en` | `values/strings.xml` | ✅ Complete |
-| Portuguese | `pt` | `values-pt/strings.xml` | ✅ Complete |
-| Spanish | `es` | `values-es/strings.xml` | ✅ Complete |
-
-## 🚀 Adding More Languages
-
-To add another language (e.g., French):
-
-1. Create `app/src/main/res/values-fr/strings.xml`
-2. Copy all strings from `values/strings.xml`
-3. Translate each string to French
-4. The app will automatically support French when the device language is set to French
-
-## 📱 Testing Localization
-
-### Method 1: Device Settings
-1. Go to Device Settings → System → Languages & input → Languages
-2. Add your desired language and move it to the top
-3. Restart the app
-4. All text should now appear in the selected language
-
-### Method 2: Android Studio
-1. Open Android Studio
-2. Go to Run → Edit Configurations
-3. In "Launch Options", set "Language" to your desired language code (e.g., "es" for Spanish)
-4. Run the app - it will launch in the selected language
-
-### Method 3: ADB Command
-```bash
-# For Spanish
-adb shell setprop persist.sys.language es
-adb shell setprop persist.sys.country ES
-
-# For Portuguese
-adb shell setprop persist.sys.language pt
-adb shell setprop persist.sys.country BR
-
-# For English
-adb shell setprop persist.sys.language en
-adb shell setprop persist.sys.country US
-```
-
-## 🔍 Common Issues
-
-### Missing Translations
-If a string is missing from a language file:
-- The app will fall back to English
-- Check the logcat for warnings about missing resources
-
-### String Format Issues
-Some strings might need special handling for different languages:
-- Date formats
-- Number formats
-- Pluralization rules
-- Gender-specific language variations
-
-### RTL Support
-All supported languages (English, Portuguese, Spanish) are left-to-right (LTR), so no special RTL handling is needed.
-
-## 📚 Best Practices
-
-1. **Never hardcode strings** - Always use `stringResource()`
-2. **Keep translations up to date** - Update all language files when adding new features
-3. **Test on real devices** - Emulator language switching can be unreliable
-4. **Use descriptive names** - String keys should be self-explanatory
-5. **Group related strings** - Use comments to organize strings by feature
-6. **Consider cultural differences** - Some phrases may need adaptation beyond direct translation
-
-## 🛠️ Maintenance
-
-### Regular Tasks
-- Review new strings added to English
-- Translate new strings to Portuguese and Spanish
-- Test all languages regularly
-- Update this documentation when adding new languages
-
-### Quality Assurance
-- Have native speakers review translations
-- Test on different Android versions
-- Verify accessibility with screen readers
-- Check for text overflow in different languages
-- Consider regional variations (e.g., European vs Latin American Spanish)
-
-## 🌎 Regional Considerations
-
-### Spanish
-- **Spain (es-ES)**: Uses "vosotros" form and European Spanish vocabulary
-- **Latin America (es-419)**: Uses "ustedes" form and Latin American vocabulary
-- Current implementation uses neutral Latin American Spanish
-
-### Portuguese
-- **Portugal (pt-PT)**: European Portuguese vocabulary and pronunciation
-- **Brazil (pt-BR)**: Brazilian Portuguese vocabulary and pronunciation
-- Current implementation uses Brazilian Portuguese
-
-## 📖 Resources
-
-- [Android Localization Guide](https://developer.android.com/guide/topics/resources/localization)
-- [Android String Resources](https://developer.android.com/guide/topics/resources/string-resource)
-- [Compose Localization](https://developer.android.com/jetpack/compose/localization)
-- [Material Design Localization](https://material.io/design/usability/bidirectionality.html)
-- [Spanish Language Resources](https://www.rae.es/)
-- [Portuguese Language Resources](https://www.priberam.pt/)
diff --git a/app/build.gradle b/app/build.gradle
index a9cab75..9244ed6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -62,6 +62,7 @@ dependencies {
implementation libs.androidx.core.ktx
implementation libs.bundles.lifecycle
implementation libs.androidx.activity.compose
+ implementation libs.androidx.startup
// Compose BOM and UI
implementation platform(libs.androidx.compose.bom)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1fcf015..d38a120 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -38,6 +38,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/mobyle/abbay/infra/startup/PlayerInitializer.kt b/app/src/main/java/com/mobyle/abbay/infra/startup/PlayerInitializer.kt
new file mode 100644
index 0000000..dc48c23
--- /dev/null
+++ b/app/src/main/java/com/mobyle/abbay/infra/startup/PlayerInitializer.kt
@@ -0,0 +1,31 @@
+package com.mobyle.abbay.infra.startup
+
+import android.content.ComponentName
+import android.content.Context
+import androidx.media3.session.MediaController
+import androidx.media3.session.SessionToken
+import androidx.startup.Initializer
+import com.mobyle.abbay.presentation.common.service.PlayerService
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+
+class PlayerInitializer : Initializer> {
+ override fun create(context: Context): Flow {
+ return flow {
+ val controller = MediaController.Builder(
+ context,
+ SessionToken(context, ComponentName(context, PlayerService::class.java))
+ ).buildAsync()
+
+ val player = controller.get()
+
+ emit(player)
+ }.flowOn(Dispatchers.IO)
+ }
+
+ override fun dependencies(): List>> {
+ return emptyList()
+ }
+}
diff --git a/app/src/main/java/com/mobyle/abbay/presentation/MainActivity.kt b/app/src/main/java/com/mobyle/abbay/presentation/MainActivity.kt
index d00fb93..a836f96 100644
--- a/app/src/main/java/com/mobyle/abbay/presentation/MainActivity.kt
+++ b/app/src/main/java/com/mobyle/abbay/presentation/MainActivity.kt
@@ -1,6 +1,5 @@
package com.mobyle.abbay.presentation
-import android.content.ComponentName
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -9,18 +8,15 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.media3.session.MediaController
-import androidx.media3.session.SessionToken
import androidx.navigation.compose.rememberNavController
+import androidx.startup.AppInitializer
import com.google.accompanist.permissions.ExperimentalPermissionsApi
-import com.google.common.util.concurrent.ListenableFuture
-import com.google.common.util.concurrent.MoreExecutors
import com.mobyle.abbay.infra.navigation.AbbayNavHost
-import com.mobyle.abbay.presentation.common.service.PlayerService
+import com.mobyle.abbay.infra.startup.PlayerInitializer
import com.mobyle.abbay.presentation.common.theme.MyApplicationTheme
import com.usecase.IsPlayWhenAppIsClosedEnabled
import com.usecase.UpdateAppLifeStatus
@@ -37,7 +33,7 @@ class MainActivity : ComponentActivity() {
@Inject
lateinit var updateAppLifeStatus: UpdateAppLifeStatus
- private lateinit var controller: ListenableFuture
+ private var player: MediaController? = null
private var isPlayerReady = false
override fun onCreate(savedInstanceState: Bundle?) {
@@ -52,36 +48,22 @@ class MainActivity : ComponentActivity() {
updateAppLifeStatus(true)
- controller = MediaController.Builder(
- this,
- SessionToken(this, ComponentName(this, PlayerService::class.java))
- ).buildAsync()
-
setContent {
val navController = rememberNavController()
- val currentPlayer = remember {
- mutableStateOf(null)
- }
+ val playerState = AppInitializer.getInstance(this@MainActivity)
+ .initializeComponent(PlayerInitializer::class.java).collectAsState(null)
- LaunchedEffect(Unit) {
- controller.addListener({
- try {
- currentPlayer.value = controller.get()
- isPlayerReady = true // This will dismiss the splash screen
- } catch (e: Exception) {
- // Handle error but still dismiss splash screen
- isPlayerReady = true
- }
- }, MoreExecutors.directExecutor())
+ LaunchedEffect(playerState.value) {
+ player = playerState.value
+ isPlayerReady = playerState.value != null
}
MyApplicationTheme {
- // A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.primary
) {
- currentPlayer.value?.let { player ->
+ playerState.value?.let { player ->
AbbayNavHost(
player = player,
navController = navController
@@ -98,9 +80,9 @@ class MainActivity : ComponentActivity() {
updateAppLifeStatus(false)
if (!isPlayWhenAppIsClosedEnabled()) {
- if (::controller.isInitialized) {
- controller.get().stop()
- controller.get().release()
+ if (player != null) {
+ player?.stop()
+ player?.release()
}
}
}
diff --git a/app/src/main/java/com/mobyle/abbay/presentation/booklist/BooksListScreen.kt b/app/src/main/java/com/mobyle/abbay/presentation/booklist/BooksListScreen.kt
index 8b02220..fd4eef7 100644
--- a/app/src/main/java/com/mobyle/abbay/presentation/booklist/BooksListScreen.kt
+++ b/app/src/main/java/com/mobyle/abbay/presentation/booklist/BooksListScreen.kt
@@ -462,23 +462,6 @@ fun BooksListScreen(
progress = book.progress,
currentPosition = book.getBookPosition()
)
-
- if (!player.isPlaying) {
- if (book is MultipleBooks) {
- player.prepareMultipleBooks(
- currentPosition = book.currentBookPosition,
- idList = book.bookFileList.map { it.id },
- progress = book.progress,
- isPlaying = viewModel.isPlaying
- )
- } else {
- player.prepareBook(
- id = id,
- progress = book.progress,
- isPlaying = viewModel.isPlaying
- )
- }
- }
}
}
}
diff --git a/app/src/main/java/com/mobyle/abbay/presentation/booklist/BooksListViewModel.kt b/app/src/main/java/com/mobyle/abbay/presentation/booklist/BooksListViewModel.kt
index 461e4fb..718487d 100644
--- a/app/src/main/java/com/mobyle/abbay/presentation/booklist/BooksListViewModel.kt
+++ b/app/src/main/java/com/mobyle/abbay/presentation/booklist/BooksListViewModel.kt
@@ -349,7 +349,7 @@ class BooksListViewModel @Inject constructor(
data object NoPermissionsGranted : BooksListUiState()
}
- companion object {
+ private companion object {
val API_32_OR_LESS_PERMISSIONS_LIST = listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
val PERMISSIONS_LIST = listOf(Manifest.permission.READ_MEDIA_AUDIO)
}
diff --git a/app/src/main/java/com/mobyle/abbay/presentation/booklist/widgets/MiniPlayer.kt b/app/src/main/java/com/mobyle/abbay/presentation/booklist/widgets/MiniPlayer.kt
index 4c58411..e6b2594 100644
--- a/app/src/main/java/com/mobyle/abbay/presentation/booklist/widgets/MiniPlayer.kt
+++ b/app/src/main/java/com/mobyle/abbay/presentation/booklist/widgets/MiniPlayer.kt
@@ -393,10 +393,6 @@ private fun MultipleFilePlayer(
val currentIndex = book.currentBookPosition
val duration = book.bookFileList[book.currentBookPosition].duration
- LaunchedEffect(showChapters.value) {
- onDisableGesture(showChapters.value)
- }
-
LaunchedEffect(swipeProgress) {
if (swipeProgress < 1f) {
showChapters.value = false
@@ -647,26 +643,17 @@ private fun PlayerController(
onPlayingChange(false)
player.pause()
} else {
- if (player.playbackState == Player.STATE_READY) {
- player.seekTo(position)
- onPlayingChange(true)
- player.play()
- } else {
- playerIcon.value = PlayingState.LOADING
- if (!player.playWhenReady) {
- player.prepareBook(id, position, MutableStateFlow(true))
- }
- player.addListener(object : Player.Listener {
- override fun onPlaybackStateChanged(state: Int) {
- if (state == Player.STATE_READY) {
- player.seekTo(position)
- onPlayingChange(true)
- player.play()
- player.removeListener(this)
- }
+ player.prepareBook(id, position)
+ player.addListener(object : Player.Listener {
+ override fun onPlaybackStateChanged(state: Int) {
+ if (state == Player.STATE_READY) {
+ player.seekTo(position)
+ onPlayingChange(true)
+ player.play()
+ player.removeListener(this)
}
- })
- }
+ }
+ })
}
}
}
@@ -860,12 +847,7 @@ private fun BookImage(
onPlayingChange(true)
player.play()
} else {
- playerIcon.value = PlayingState.LOADING
-
- if (!player.playWhenReady) {
- player.prepareBook(book.id, progress, MutableStateFlow(true))
- }
-
+ player.prepareBook(book.id, progress, MutableStateFlow(true))
player.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(state: Int) {
if (state == Player.STATE_READY) {
diff --git a/app/src/main/java/com/mobyle/abbay/presentation/utils/Extensions.kt b/app/src/main/java/com/mobyle/abbay/presentation/utils/Extensions.kt
index a262c9d..02c5dc1 100644
--- a/app/src/main/java/com/mobyle/abbay/presentation/utils/Extensions.kt
+++ b/app/src/main/java/com/mobyle/abbay/presentation/utils/Extensions.kt
@@ -162,9 +162,9 @@ fun MediaController.playMultipleBooks(
fun MediaController.prepareBook(
id: String,
progress: Long,
- isPlaying: MutableStateFlow,
+ isPlaying: MutableStateFlow? = null,
) {
- isPlaying.value = false
+ isPlaying?.value = false
pause()
clearMediaItems()
val uri =
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 889d6df..fd6743d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -73,6 +73,7 @@ androidx-activity-compose = { group = "androidx.activity", name = "activity-comp
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
androidx-workManager = { module = "androidx.work:work-runtime-ktx", version = "2.10.0" }
+androidx-startup = { group = "androidx.startup", name = "startup-runtime", version = "1.1.1" }
# Compose BOM and UI
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
@@ -190,7 +191,8 @@ hilt-kapt = [
lifecycle = [
"androidx-lifecycle-runtime-ktx",
- "androidx-lifecycle-runtime-compose"
+ "androidx-lifecycle-runtime-compose",
+ "androidx-startup"
]
coil = [