diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 04a0e83..3836c2b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,3 +1,21 @@
+/*
+ *
+ * * Copyright (c) 2024 Angel Studio
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ */
+
import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension
plugins {
@@ -23,8 +41,8 @@ android {
applicationId = "fr.angel.soundtap"
minSdk = 30
targetSdk = 34
- versionCode = 35
- versionName = "1.1.1"
+ versionCode = 38
+ versionName = "1.1.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -131,4 +149,7 @@ dependencies {
implementation(libs.androidx.datastore)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.serialization.json)
+
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.appcompat.resources)
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a9d3fbb..1abe2e6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,4 +1,21 @@
-
+
+
@@ -26,6 +43,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
+ android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SoundTap">
@@ -43,7 +61,7 @@
@@ -88,5 +106,14 @@
+
+
+
+
diff --git a/app/src/main/java/fr/angel/soundtap/MainActivity.kt b/app/src/main/java/fr/angel/soundtap/MainActivity.kt
index a5176a7..8e8b5dc 100644
--- a/app/src/main/java/fr/angel/soundtap/MainActivity.kt
+++ b/app/src/main/java/fr/angel/soundtap/MainActivity.kt
@@ -59,6 +59,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -164,7 +165,7 @@ class MainActivity : ComponentActivity() {
horizontal = 16.dp,
vertical = 8.dp,
),
- text = "SoundTap",
+ text = stringResource(id = R.string.app_name).uppercase(),
style = MaterialTheme.typography.displayMedium,
fontFamily = FontPilowlava,
fontWeight = FontWeight.ExtraBold,
diff --git a/app/src/main/java/fr/angel/soundtap/NotificationHelper.kt b/app/src/main/java/fr/angel/soundtap/NotificationHelper.kt
index 3d6565b..edf30d6 100644
--- a/app/src/main/java/fr/angel/soundtap/NotificationHelper.kt
+++ b/app/src/main/java/fr/angel/soundtap/NotificationHelper.kt
@@ -1,3 +1,21 @@
+/*
+ *
+ * * Copyright (c) 2024 Angel Studio
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
+ *
+ */
+
package fr.angel.soundtap
import android.app.NotificationChannel
@@ -30,7 +48,7 @@ class NotificationHelper
// id =
CHANNEL_ID_SLEEP_TIMER,
// name =
- "Sleep Timer",
+ context.getString(R.string.app_sleep_timer),
// importance =
NotificationManager.IMPORTANCE_MIN,
)
@@ -40,8 +58,8 @@ class NotificationHelper
fun showSleepTimerNotification(millisUntilFinished: Long) {
val notification =
NotificationCompat.Builder(context, CHANNEL_ID_SLEEP_TIMER)
- .setContentTitle("Sleep Timer")
- .setContentText("Music will stop when the timer ends.")
+ .setContentTitle(context.getString(R.string.app_sleep_timer))
+ .setContentText(context.getString(R.string.app_sleep_timer_message))
.setSilent(true)
.setOngoing(true)
.setUsesChronometer(true)
@@ -54,14 +72,14 @@ class NotificationHelper
// icon =
R.drawable.twotone_stop_circle_24,
// title =
- "Stop",
+ context.getString(R.string.stop),
// intent =
GlobalHelper.createStopSleepTimerIntent(context),
).addAction(
// icon =
R.drawable.twotone_more_time_24,
// title =
- "Add 15 minutes",
+ context.getString(R.string.app_sleep_timer_add_time_15_min),
// intent =
GlobalHelper.createAddTimeSleepTimerIntent(context, 15 * 60 * 1000),
).setContentIntent(GlobalHelper.createNotificationOpenAppIntent(context))
diff --git a/app/src/main/java/fr/angel/soundtap/data/enums/AutoPlayMode.kt b/app/src/main/java/fr/angel/soundtap/data/enums/AutoPlayMode.kt
index fb17ca9..194337d 100644
--- a/app/src/main/java/fr/angel/soundtap/data/enums/AutoPlayMode.kt
+++ b/app/src/main/java/fr/angel/soundtap/data/enums/AutoPlayMode.kt
@@ -1,20 +1,23 @@
/*
- * Copyright 2024 Angel Studio
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * * Copyright (c) 2024 Angel Studio
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
*
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
*/
package fr.angel.soundtap.data.enums
+import androidx.annotation.StringRes
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
@@ -32,13 +35,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.unit.dp
+import fr.angel.soundtap.R
enum class AutoPlayMode(
- val title: String,
+ @StringRes val title: Int,
val selectedComposable: @Composable BoxScope.(selected: Boolean) -> Unit,
) {
ON_HEADSET_CONNECTED(
- title = "On headset connected",
+ title = R.string.auto_play_mode_on_headset_connected,
selectedComposable = { selected ->
val alpha by animateFloatAsState(
if (selected) 1f else 0.2f,
@@ -60,7 +64,7 @@ enum class AutoPlayMode(
},
),
ON_DOUBLE_VOLUME_LONG_PRESS(
- title = "On double volume long press",
+ title = R.string.auto_play_mode_on_double_volume_long_press,
selectedComposable = { selected ->
val alpha by animateFloatAsState(
if (selected) 1f else 0.2f,
@@ -82,7 +86,7 @@ enum class AutoPlayMode(
},
),
BOTH(
- title = "Both",
+ title = R.string.auto_play_mode_both,
selectedComposable = { selected ->
val alpha by animateFloatAsState(
if (selected) 1f else 0.2f,
diff --git a/app/src/main/java/fr/angel/soundtap/data/enums/HapticFeedbackLevel.kt b/app/src/main/java/fr/angel/soundtap/data/enums/HapticFeedbackLevel.kt
index 4546790..a6d8aee 100644
--- a/app/src/main/java/fr/angel/soundtap/data/enums/HapticFeedbackLevel.kt
+++ b/app/src/main/java/fr/angel/soundtap/data/enums/HapticFeedbackLevel.kt
@@ -1,29 +1,44 @@
/*
- * Copyright 2024 Angel Studio
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * * Copyright (c) 2024 Angel Studio
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
*
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
*/
package fr.angel.soundtap.data.enums
import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
import fr.angel.soundtap.R
enum class HapticFeedbackLevel(
- val title: String,
+ @StringRes val title: Int,
@DrawableRes val icon: Int,
) {
- NONE("None", R.drawable.haptic_feedback_none),
- LIGHT("Light", R.drawable.haptic_feedback_light),
- MEDIUM("Medium", R.drawable.haptic_feedback_medium),
- STRONG("Strong", R.drawable.haptic_feedback_strong),
+ NONE(
+ R.string.haptic_feedback_none,
+ R.drawable.haptic_feedback_none,
+ ),
+ LIGHT(
+ R.string.haptic_feedback_light,
+ R.drawable.haptic_feedback_light,
+ ),
+ MEDIUM(
+ R.string.haptic_feedback_medium,
+ R.drawable.haptic_feedback_medium,
+ ),
+ STRONG(
+ R.string.haptic_feedback_strong,
+ R.drawable.haptic_feedback_strong,
+ ),
}
diff --git a/app/src/main/java/fr/angel/soundtap/data/enums/WorkingMode.kt b/app/src/main/java/fr/angel/soundtap/data/enums/WorkingMode.kt
index c80923c..7fc6ac9 100644
--- a/app/src/main/java/fr/angel/soundtap/data/enums/WorkingMode.kt
+++ b/app/src/main/java/fr/angel/soundtap/data/enums/WorkingMode.kt
@@ -1,20 +1,23 @@
/*
- * Copyright 2024 Angel Studio
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * * Copyright (c) 2024 Angel Studio
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
*
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
*/
package fr.angel.soundtap.data.enums
+import androidx.annotation.StringRes
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.aspectRatio
@@ -30,15 +33,17 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
+import fr.angel.soundtap.R
enum class WorkingMode(
- val title: String,
+ @StringRes val title: Int,
val selectedComposable: @Composable BoxScope.(selected: Boolean) -> Unit,
) {
SCREEN_ON_OFF(
- title = "Screen ON and OFF",
+ title = R.string.working_mode_screen_on_off,
selectedComposable = { selected ->
val alpha by animateFloatAsState(
if (selected) 1f else 0.2f,
@@ -59,7 +64,7 @@ enum class WorkingMode(
)
},
),
- SCREEN_ON(title = "Screen ON", selectedComposable = { selected ->
+ SCREEN_ON(title = R.string.working_mode_screen_on, selectedComposable = { selected ->
val alpha by animateFloatAsState(
if (selected) 1f else 0.2f,
label = "alpha",
@@ -70,13 +75,13 @@ enum class WorkingMode(
Modifier
.padding(4.dp)
.alpha(alpha),
- text = "ON",
+ text = stringResource(id = R.string.awake),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Black,
)
}),
- SCREEN_OFF(title = "Screen OFF", selectedComposable = { selected ->
+ SCREEN_OFF(title = R.string.working_mode_screen_off, selectedComposable = { selected ->
val alpha by animateFloatAsState(
if (selected) 1f else 0.2f,
label = "alpha",
@@ -87,7 +92,7 @@ enum class WorkingMode(
Modifier
.padding(4.dp)
.alpha(alpha),
- text = "SLEEP",
+ text = stringResource(id = R.string.sleep),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Black,
diff --git a/app/src/main/java/fr/angel/soundtap/data/models/BottomSheetState.kt b/app/src/main/java/fr/angel/soundtap/data/models/BottomSheetState.kt
index 930e038..c247ab3 100644
--- a/app/src/main/java/fr/angel/soundtap/data/models/BottomSheetState.kt
+++ b/app/src/main/java/fr/angel/soundtap/data/models/BottomSheetState.kt
@@ -48,9 +48,11 @@ import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import fr.angel.soundtap.R
import fr.angel.soundtap.data.settings.customization.CustomControlMediaAction
import fr.angel.soundtap.data.settings.customization.HardwareButtonsEvent
import fr.angel.soundtap.data.settings.customization.MediaAction
@@ -183,7 +185,7 @@ sealed class BottomSheetState(
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Text(
- text = "Create a sequence of hardware button events to trigger the action",
+ text = stringResource(R.string.custom_control_media_action_sequence_description),
style = MaterialTheme.typography.labelMedium,
)
FlowRow(
@@ -212,7 +214,7 @@ sealed class BottomSheetState(
HorizontalDivider()
Text(
- text = "The current sequence is:",
+ text = stringResource(R.string.custom_control_media_action_sequence_current),
style = MaterialTheme.typography.labelMedium,
)
FlowRow(
@@ -222,7 +224,7 @@ sealed class BottomSheetState(
) {
if (sequence.isEmpty()) {
Text(
- text = "No events in the sequence",
+ text = stringResource(R.string.custom_control_media_action_sequence_empty),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
)
@@ -270,7 +272,7 @@ sealed class BottomSheetState(
state.onDismiss?.invoke()
},
) {
- Text("Cancel")
+ Text(stringResource(R.string.cancel))
}
Button(
modifier = Modifier.weight(1f),
@@ -280,7 +282,7 @@ sealed class BottomSheetState(
state.onDismiss?.invoke()
},
) {
- Text("Done")
+ Text(stringResource(R.string.save))
}
}
}
diff --git a/app/src/main/java/fr/angel/soundtap/ui/OnboardingScreen.kt b/app/src/main/java/fr/angel/soundtap/ui/OnboardingScreen.kt
index 698f403..2eb492e 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/OnboardingScreen.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/OnboardingScreen.kt
@@ -56,6 +56,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
@@ -121,13 +122,10 @@ fun OnboardingScreen(
val onboardingPages: List =
listOf(
OnboardingPage(
- title = "Welcome to SoundTap",
+ title = stringResource(id = R.string.onboarding_welcome_message),
description =
AnnotatedString(
- "SoundTap is your ultimate companion for controlling your music playback effortlessly." +
- "\n\nWith intuitive volume button controls, customizable settings, and seamless integration" +
- " with your favorite media players, SoundTap makes managing your music a breeze." +
- "\n\nJoin us on this journey to enhance your music listening experience!",
+ stringResource(R.string.onboarding_welcome_description),
),
nextButtonEnabled = hasAcceptedPrivacyPolicy,
animationImage = R.raw.welcome_music,
@@ -143,7 +141,7 @@ fun OnboardingScreen(
Text(
text =
buildAnnotatedString {
- append("I have read and agree to the ")
+ append(stringResource(R.string.onboarding_accept_privacy_policy))
withLink(link = LinkAnnotation.Url(GlobalHelper.PRIVACY_POLICY_URL)) {
withStyle(
@@ -153,11 +151,11 @@ fun OnboardingScreen(
fontWeight = FontWeight.Bold,
),
) {
- append("privacy policy")
+ append(stringResource(R.string.onboarding_privacy_policy_link))
}
}
- append(" and the ")
+ append(stringResource(R.string.onboarding_and))
withLink(link = LinkAnnotation.Url(GlobalHelper.TERMS_OF_SERVICE_URL)) {
withStyle(
@@ -167,7 +165,7 @@ fun OnboardingScreen(
fontWeight = FontWeight.Bold,
),
) {
- append("terms of service")
+ append(stringResource(R.string.onboarding_terms_of_service_link))
}
}
@@ -180,58 +178,48 @@ fun OnboardingScreen(
},
),
OnboardingPage(
- title = "Access Media Controls",
+ title = stringResource(id = R.string.onboarding_notification_access),
description =
AnnotatedString(
- "SoundTap requires access to your notifications to detect media playback and control your music." +
- "The app does not collect any personal data or information." +
- "\n\nThis permission is necessary for SoundTap to function properly. Please enable the Notifications access permission to continue." +
- "\n\nTo enable the permission, tap the button below and enable SoundTap from the list.",
+ stringResource(R.string.onboarding_notification_access_description),
),
nextButtonEnabled = uiState.hasNotificationListenerPermission,
animationImage = R.raw.music_player,
- actionButtonLabel = "Open Notification Access Settings",
+ actionButtonLabel = stringResource(id = R.string.onboarding_open_notification_settings),
actionButtonOnClick = { GlobalHelper.openNotificationListenerSettings(context) },
),
OnboardingPage(
- title = "Battery Optimization",
+ title = stringResource(id = R.string.onboarding_background_optimization),
description =
AnnotatedString(
- "SoundTap runs in the background to provide you with seamless music playback controls. To ensure that SoundTap works reliably, please disable battery optimization for the app." +
- "\n\nThis will prevent the system from killing SoundTap in the background and ensure that you can control your music playback at all times." +
- "\n\nTo disable battery optimization, tap the button below and select 'Don't optimize' for SoundTap.",
+ stringResource(R.string.onboarding_background_optimization_description),
),
nextButtonEnabled = uiState.isBackgroundOptimizationDisabled,
animationImage = R.raw.battery,
tintedAnimation = true,
- actionButtonLabel = "Disable Battery Optimization",
+ actionButtonLabel = stringResource(id = R.string.onboarding_open_battery_optimization),
actionButtonOnClick = { GlobalHelper.requestBatteryOptimization(context) },
),
OnboardingPage(
- title = "Notifications Access",
+ title = stringResource(id = R.string.onboarding_notifications),
description =
AnnotatedString(
- "SoundTap will post notifications for diverse purposes, such as for the sleep timer." +
- "\n\nTo ensure that SoundTap functions correctly, please grant the app access to your notifications." +
- "\n\nTo enable the Notifications access permission, tap the button below and enable SoundTap from the list." +
- "\n\nIf you have denied the permission previously, you can enable it from the app settings.",
+ stringResource(R.string.onboarding_notifications_description),
),
nextButtonEnabled = notificationsPermissionState.status.isGranted,
animationImage = R.raw.notifications,
- actionButtonLabel = "Grant Notifications Access",
+ actionButtonLabel = stringResource(id = R.string.onboarding_open_notification_settings),
actionButtonOnClick = { notificationsPermissionState.launchPermissionRequest() },
),
OnboardingPage(
- title = "Accessibility Service",
+ title = stringResource(id = R.string.onboarding_accessibility_service),
description =
AnnotatedString(
- "SoundTap requires the Accessibility Service permission to detect volume button presses and control your music playback." +
- "\n\nThe Accessibility Service only allows SoundTap to detect volume button presses and does not collect any other personal data or information. It does NOT read the screen." +
- "\n\nTo enable the Accessibility Service, tap the button below and enable SoundTap from the list.",
+ stringResource(R.string.onboarding_accessibility_service_description),
),
nextButtonEnabled = accessibilityServiceUiState.isRunning && hasAcceptedAccessibilityServiceConditions,
animationImage = R.raw.empty,
- actionButtonLabel = "Open Accessibility Settings",
+ actionButtonLabel = stringResource(id = R.string.onboarding_open_accessibility_settings),
actionButtonOnClick = {
if (hasAcceptedAccessibilityServiceConditions.not()) {
showBottomSheet = true
@@ -241,18 +229,16 @@ fun OnboardingScreen(
},
),
OnboardingPage(
- title = "All Set!",
+ title = stringResource(id = R.string.onboarding_done),
description =
AnnotatedString(
- "Congratulations! You're all set to start using SoundTap." +
- "\n\nTo get started, press the volume up and down buttons simultaneously to toggle the music playback." +
- "\n\nIf you have any questions or need help, feel free to reach out to us.",
+ stringResource(R.string.onboarding_done_description),
),
animationImage = R.raw.confetti,
bottomContent = {
Spacer(modifier = Modifier.weight(1f))
Text(
- text = "Happy listening!",
+ text = stringResource(id = R.string.onboarding_done_bottom_content),
style = MaterialTheme.typography.bodyMedium,
)
Spacer(modifier = Modifier.weight(1f))
@@ -262,9 +248,10 @@ fun OnboardingScreen(
var currentPage by rememberSaveable { mutableIntStateOf(0) }
+ val accessibilityTitle = stringResource(id = R.string.onboarding_accessibility_service)
LaunchedEffect(key1 = currentPage) {
if (hasAcceptedAccessibilityServiceConditions.not() &&
- currentPage == onboardingPages.indexOfLast { it.title == "Accessibility Service" }
+ currentPage == onboardingPages.indexOfLast { it.title == accessibilityTitle }
) {
showBottomSheet = true
}
@@ -306,7 +293,7 @@ fun OnboardingScreen(
TextButton(
onClick = { currentPage = (currentPage - 1).coerceAtLeast(0) },
) {
- Text("Previous")
+ Text(stringResource(id = R.string.previous))
}
}
@@ -326,9 +313,9 @@ fun OnboardingScreen(
modifier = Modifier.animateContentSize(),
text =
when (currentPage) {
- 0 -> "Start"
- onboardingPages.size - 1 -> "Finish"
- else -> "Next"
+ 0 -> stringResource(id = R.string.start)
+ onboardingPages.size - 1 -> stringResource(id = R.string.finish)
+ else -> stringResource(id = R.string.next)
},
)
}
@@ -349,47 +336,45 @@ fun OnboardingScreen(
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Text(
- "Accessibility Service Usage",
+ stringResource(id = R.string.onboarding_accessibility_service_usage),
style = MaterialTheme.typography.headlineMedium,
)
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
- "Personal and Sensitive User Data:",
+ stringResource(id = R.string.onboarding_personal_sensitive_data),
style = MaterialTheme.typography.titleSmall,
)
Text(
- "SoundTap uses the accessibility service to register volume button click events only. This allows you to control your music playback using volume buttons without unlocking your phone or switching apps. No personal or sensitive user data is collected, stored, or shared.",
+ stringResource(id = R.string.onboarding_personal_sensitive_data_description),
style = MaterialTheme.typography.bodySmall,
)
}
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
- "Core Functionality:",
+ stringResource(id = R.string.onboarding_core_functionality),
style = MaterialTheme.typography.titleSmall,
)
Text(
- "To function properly, SoundTap requires accessibility permission to detect volume button presses. This permission is necessary for the core functionality of the app and ensures that you can seamlessly control your music playback. By granting this permission, you acknowledge and consent to its use for this purpose only.",
+ stringResource(id = R.string.onboarding_core_functionality_description),
style = MaterialTheme.typography.bodySmall,
)
}
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
- "Data Security & Privacy:",
+ stringResource(id = R.string.onboarding_data_security_privacy),
style = MaterialTheme.typography.titleSmall,
)
Text(
- "SoundTap strictly uses the accessibility service for registering volume button events and does not access, collect, or transmit any personal or sensitive data. Your privacy is our top priority." +
- "\n\nSoundTap does not collect or share any personal data or information through the accessibility service. All operations are conducted locally on your device, ensuring your data remains secure and private." +
- "\n\nFor more information, please refer to our Privacy Policy.",
+ stringResource(id = R.string.onboarding_data_security_privacy_description),
style = MaterialTheme.typography.bodySmall,
)
}
Text(
- "By enabling the accessibility service for SoundTap, you consent to the app using this service solely for detecting volume button events to control media playback. You can disable this permission at any time through your device's accessibility settings.",
+ stringResource(id = R.string.onboarding_accessibility_service_usage_consent),
style = MaterialTheme.typography.bodySmall,
)
@@ -405,7 +390,7 @@ fun OnboardingScreen(
}
}
}) {
- Text("Decline")
+ Text(stringResource(id = R.string.decline))
}
Button(onClick = {
hasAcceptedAccessibilityServiceConditions = true
@@ -415,7 +400,7 @@ fun OnboardingScreen(
}
}
}) {
- Text("Agree & Continue")
+ Text(stringResource(id = R.string.agree_continue))
}
}
}
diff --git a/app/src/main/java/fr/angel/soundtap/ui/app/AppScreen.kt b/app/src/main/java/fr/angel/soundtap/ui/app/AppScreen.kt
index da4a2db..a7392c1 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/app/AppScreen.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/app/AppScreen.kt
@@ -31,7 +31,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@@ -55,12 +54,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.angel.soundtap.GlobalHelper
import fr.angel.soundtap.MainViewModel
+import fr.angel.soundtap.R
import fr.angel.soundtap.data.models.BottomSheetState
import fr.angel.soundtap.service.SleepTimerService
import fr.angel.soundtap.service.SoundTapAccessibilityService
@@ -107,7 +108,7 @@ fun SharedTransitionScope.App(
Modifier
.align(Alignment.CenterHorizontally)
.padding(horizontal = 8.dp),
- text = "Take Control of Your Music",
+ text = stringResource(id = R.string.app_take_control),
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
@@ -123,10 +124,8 @@ fun SharedTransitionScope.App(
.padding(horizontal = 8.dp),
cardType = InfoCardType.Notification,
icon = Icons.Outlined.NotificationImportant,
- title = "Notification Listener Permission",
- body =
- "SoundTap needs the Notification Listener permission to receive media information and control your music." +
- " Please enable it by pressing the button at the bottom of the screen.",
+ title = stringResource(id = R.string.app_notification_listener_permission),
+ body = stringResource(id = R.string.app_notification_listener_permission_description),
onCardClick = {
GlobalHelper.openNotificationListenerSettings(context = context)
},
@@ -141,10 +140,8 @@ fun SharedTransitionScope.App(
.padding(horizontal = 8.dp),
cardType = InfoCardType.Accessibility,
icon = Icons.Outlined.SettingsAccessibility,
- title = "Accessibility Service",
- body =
- "SoundTap needs the Accessibility Service to receive volume key events." +
- " Please enable it by pressing the button at the bottom of the screen.",
+ title = stringResource(id = R.string.app_accessibility_service),
+ body = stringResource(id = R.string.app_accessibility_service_description),
onCardClick = {
GlobalHelper.openAccessibilitySettings(context = context)
},
@@ -171,15 +168,17 @@ fun SharedTransitionScope.App(
) {
GridCard(
modifier = Modifier.weight(1f),
+ animationId = "Customize",
icon = Icons.Default.Tune,
- label = "Customize",
+ label = stringResource(id = R.string.app_customize),
animatedVisibilityScope = animatedVisibilityScope,
onClick = navigateToCustomization,
)
GridCard(
modifier = Modifier.weight(1f),
+ animationId = "Settings",
icon = Icons.Default.Settings,
- label = "Settings",
+ label = stringResource(id = R.string.app_settings),
animatedVisibilityScope = animatedVisibilityScope,
onClick = navigateToSettings,
)
@@ -194,15 +193,17 @@ fun SharedTransitionScope.App(
) {
GridCard(
modifier = Modifier.weight(1f),
+ animationId = "History",
icon = Icons.Default.History,
- label = "History",
+ label = stringResource(id = R.string.app_history),
animatedVisibilityScope = animatedVisibilityScope,
onClick = navigateToHistory,
)
GridCard(
modifier = Modifier.weight(1f),
+ animationId = "Support",
icon = Icons.Default.Support,
- label = "Support",
+ label = stringResource(id = R.string.app_support),
animatedVisibilityScope = animatedVisibilityScope,
onClick = navigateToSupport,
)
@@ -220,17 +221,19 @@ fun SharedTransitionScope.App(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- Spacer(modifier = Modifier.width(8.dp))
Text(
+ modifier =
+ Modifier
+ .padding(start = 8.dp, end = 16.dp)
+ .weight(1f),
text =
if (SleepTimerService.isRunning) {
- "Music will stop in ${GlobalHelper.formatTime(SleepTimerService.remainingTime)}"
+ stringResource(id = R.string.app_music_will_stop, GlobalHelper.formatTime(SleepTimerService.remainingTime))
} else {
- "Sleep Timer"
+ stringResource(id = R.string.app_sleep_timer)
},
style = MaterialTheme.typography.titleMedium,
)
- Spacer(modifier = Modifier.weight(1f))
Button(
shape = MaterialTheme.shapes.large,
colors =
@@ -257,9 +260,10 @@ fun SharedTransitionScope.App(
modifier = Modifier.animateContentSize(),
text =
when (SleepTimerService.isRunning) {
- true -> "Cancel Timer"
- false -> "Set Timer"
+ true -> stringResource(id = R.string.app_cancel_timer)
+ false -> stringResource(id = R.string.app_set_timer)
},
+ textAlign = TextAlign.Center,
)
}
}
diff --git a/app/src/main/java/fr/angel/soundtap/ui/app/HistoryScreen.kt b/app/src/main/java/fr/angel/soundtap/ui/app/HistoryScreen.kt
index 5bfba01..c9afcbf 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/app/HistoryScreen.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/app/HistoryScreen.kt
@@ -51,6 +51,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asComposeRenderEffect
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -121,7 +122,7 @@ fun SharedTransitionScope.HistoryScreen(
)
Text(
- text = "History",
+ text = stringResource(id = R.string.history_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier =
@@ -193,13 +194,13 @@ fun SharedTransitionScope.HistoryScreen(
.then(shaderModifier),
)
Text(
- text = "No history",
+ text = stringResource(id = R.string.history_no_history),
modifier = Modifier.align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.SemiBold,
)
Text(
- text = "Your history and stats will be displayed here",
+ text = stringResource(id = R.string.history_your_history_and_stats_will_be_displayed_here),
modifier = Modifier.align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.bodyMedium,
)
@@ -224,7 +225,7 @@ fun SharedTransitionScope.HistoryScreen(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
- text = "Total songs played",
+ text = stringResource(id = R.string.history_total_songs_played),
style = MaterialTheme.typography.labelSmall,
)
Text(
@@ -239,7 +240,7 @@ fun SharedTransitionScope.HistoryScreen(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
- text = "Total songs skipped",
+ text = stringResource(id = R.string.history_total_songs_skipped),
style = MaterialTheme.typography.labelSmall,
)
Text(
diff --git a/app/src/main/java/fr/angel/soundtap/ui/app/SettingsScreen.kt b/app/src/main/java/fr/angel/soundtap/ui/app/SettingsScreen.kt
index 8f8b86e..67b995e 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/app/SettingsScreen.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/app/SettingsScreen.kt
@@ -75,6 +75,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -144,7 +145,7 @@ fun SharedTransitionScope.SettingsScreen(
)
Text(
- text = "Settings",
+ text = stringResource(id = R.string.settings_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier =
@@ -173,8 +174,8 @@ fun SharedTransitionScope.SettingsScreen(
exit = fadeOut() + shrinkVertically(),
) {
SettingsItem(
- title = "Add quick tile",
- subtitle = "Add a tile to the quick settings panel to quickly access SoundTap",
+ title = stringResource(id = R.string.settings_add_quick_tile),
+ subtitle = stringResource(id = R.string.settings_add_quick_tile_subtitle),
icon = Icons.Rounded.GridView,
onClick = {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return@SettingsItem
@@ -203,8 +204,8 @@ fun SharedTransitionScope.SettingsScreen(
item {
SettingsItem(
- title = "Notifications settings",
- subtitle = "Configure the notifications that SoundTap should show",
+ title = stringResource(id = R.string.settings_notifications),
+ subtitle = stringResource(id = R.string.settings_notifications_subtitle),
icon = Icons.Rounded.NotificationsNone,
onClick = {
GlobalHelper.openNotificationSettings(context)
@@ -219,8 +220,8 @@ fun SharedTransitionScope.SettingsScreen(
)
}
SettingsItemCustomBottom(
- title = "Supported players",
- subtitle = "Select the players you want to control with SoundTap",
+ title = stringResource(id = R.string.settings_supported_players),
+ subtitle = stringResource(id = R.string.settings_supported_players_subtitle),
icon = Icons.Rounded.Radio,
expanded = supportedMediaPlayersExpanded,
onClick = {
@@ -277,8 +278,8 @@ fun SharedTransitionScope.SettingsScreen(
)
}
SettingsItemCustomBottom(
- title = "Preferred player",
- subtitle = "Select the preferred player that SoundTap should control by default",
+ title = stringResource(id = R.string.settings_preferred_player),
+ subtitle = stringResource(id = R.string.settings_preferred_player_subtitle),
icon = Icons.Rounded.StarBorder,
expanded = preferredMediaPlayerExpanded,
onClick = {
diff --git a/app/src/main/java/fr/angel/soundtap/ui/app/SupportScreen.kt b/app/src/main/java/fr/angel/soundtap/ui/app/SupportScreen.kt
index 54883a7..cb29c3c 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/app/SupportScreen.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/app/SupportScreen.kt
@@ -48,9 +48,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import fr.angel.soundtap.GlobalHelper
+import fr.angel.soundtap.R
import fr.angel.soundtap.ui.components.settings.SettingsItem
@OptIn(ExperimentalSharedTransitionApi::class)
@@ -100,7 +102,7 @@ fun SharedTransitionScope.SupportScreen(
)
Text(
- text = "Support",
+ text = stringResource(id = R.string.support_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier =
@@ -125,32 +127,32 @@ fun SharedTransitionScope.SupportScreen(
) {
Spacer(modifier = Modifier.height(8.dp))
SettingsItem(
- title = "Report a Bug or Suggest a Feature",
- subtitle = "Report a bug or issue with the app or suggest a feature that you would like to see",
+ title = stringResource(id = R.string.support_report_bug_feature),
+ subtitle = stringResource(id = R.string.support_report_bug_feature_description),
icon = Icons.Default.BugReport,
onClick = { uriHandler.openUri("https://github.com/Angel-Studio/SoundTap/issues/new/choose") },
)
SettingsItem(
- title = "View on GitHub",
- subtitle = "View the source code and contribute to the project",
+ title = stringResource(id = R.string.support_view_github),
+ subtitle = stringResource(id = R.string.support_view_github_description),
icon = Icons.Default.Code,
onClick = { uriHandler.openUri("https://github.com/Angel-Studio/SoundTap") },
)
SettingsItem(
- title = "Discord Server",
- subtitle = "Join the community and get support",
+ title = stringResource(id = R.string.support_discord_server),
+ subtitle = stringResource(id = R.string.support_discord_server_description),
icon = Icons.Default.Groups,
onClick = { uriHandler.openUri("https://discord.gg/8NfBrxKs4T") },
)
SettingsItem(
- title = "Privacy Policy",
- subtitle = "Read the privacy policy of the app",
+ title = stringResource(id = R.string.support_privacy_policy),
+ subtitle = stringResource(id = R.string.support_privacy_policy_description),
icon = Icons.Default.Policy,
onClick = { uriHandler.openUri(GlobalHelper.PRIVACY_POLICY_URL) },
)
SettingsItem(
- title = "Terms of Service",
- subtitle = "Read the terms of service of the app",
+ title = stringResource(id = R.string.support_terms_of_service),
+ subtitle = stringResource(id = R.string.support_terms_of_service_description),
icon = Icons.Default.Gavel,
onClick = { uriHandler.openUri(GlobalHelper.TERMS_OF_SERVICE_URL) },
)
diff --git a/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationControls.kt b/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationControls.kt
index 0175dd0..73555f2 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationControls.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationControls.kt
@@ -46,10 +46,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.angel.soundtap.MainViewModel
+import fr.angel.soundtap.R
import fr.angel.soundtap.data.settings.customization.ControlMediaAction
import fr.angel.soundtap.data.settings.customization.CustomControlMediaAction
import fr.angel.soundtap.ui.components.settings.SettingsItemCustomBottom
@@ -105,7 +107,7 @@ fun CustomizationControls(
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
- text = "CUSTOM ACTIONS",
+ text = stringResource(R.string.customization_custom_actions).uppercase(),
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium,
)
@@ -124,7 +126,7 @@ fun CustomizationControls(
onClick = { mainViewModel.addCustomControlMediaAction() },
shape = MaterialTheme.shapes.medium,
) {
- Text(text = "Add custom action")
+ Text(text = stringResource(R.string.customization_add_custom_action))
}
}
items(uiState.customizationSettings.customMediaActions, key = { it.id }) { controlMediaAction ->
@@ -182,7 +184,7 @@ private fun ControlCard(
Button(
onClick = onActionChange,
) {
- Text(text = "Change")
+ Text(text = stringResource(R.string.change))
}
}
},
@@ -233,7 +235,7 @@ private fun CustomControlCard(
Button(
onClick = onActionChange,
) {
- Text(text = "Change")
+ Text(text = stringResource(R.string.change))
}
}
Row(
@@ -251,7 +253,7 @@ private fun CustomControlCard(
contentDescription = null,
)
Text(
- text = "Sequence",
+ text = stringResource(R.string.sequence),
modifier = Modifier.weight(1f),
fontWeight = FontWeight.Bold,
)
@@ -259,7 +261,7 @@ private fun CustomControlCard(
Button(
onClick = onSequenceChange,
) {
- Text(text = "Edit")
+ Text(text = stringResource(R.string.edit))
}
}
FilledTonalButton(
@@ -267,7 +269,7 @@ private fun CustomControlCard(
onClick = onRemove,
shape = MaterialTheme.shapes.medium,
) {
- Text(text = "Remove")
+ Text(text = stringResource(R.string.remove))
}
},
)
diff --git a/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationHome.kt b/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationHome.kt
index 37907d6..03c84bc 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationHome.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationHome.kt
@@ -21,7 +21,6 @@ package fr.angel.soundtap.ui.app.customization
import android.graphics.RenderEffect
import android.graphics.RuntimeShader
import android.os.Build
-import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.withInfiniteAnimationFrameMillis
@@ -81,10 +80,12 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.angel.soundtap.MainViewModel
+import fr.angel.soundtap.R
import fr.angel.soundtap.VibratorHelper
import fr.angel.soundtap.animations.PERLIN_NOISE
import fr.angel.soundtap.data.enums.AutoPlayMode
@@ -95,7 +96,7 @@ import fr.angel.soundtap.ui.components.settings.SettingsItemCustomBottom
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
-@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomizationHome(
modifier: Modifier = Modifier,
@@ -123,8 +124,8 @@ fun CustomizationHome(
) {
item {
SettingsItem(
- title = "Customize controls",
- subtitle = "Change the behavior of the volume buttons as you like.",
+ title = stringResource(id = R.string.customization_customize_controls),
+ subtitle = stringResource(id = R.string.customization_customize_controls_subtitle),
icon = Icons.Rounded.ControlCamera,
onClick = navigateToControls,
)
@@ -132,8 +133,8 @@ fun CustomizationHome(
item {
SettingsItemCustomBottom(
- title = "Working mode",
- subtitle = "Select when the skipping action should be available.",
+ title = stringResource(id = R.string.customization_working_mode),
+ subtitle = stringResource(id = R.string.customization_working_mode_subtitle),
icon = Icons.Default.ToggleOn,
content = {
Row(
@@ -200,7 +201,7 @@ fun CustomizationHome(
uiState.customizationSettings.workingMode == item,
)
Text(
- text = item.title,
+ text = stringResource(item.title),
style = MaterialTheme.typography.bodyMedium,
modifier =
Modifier
@@ -215,8 +216,8 @@ fun CustomizationHome(
}
item {
SettingsItemCustomBottom(
- title = "Haptic feedback",
- subtitle = "Vibrate when an action is performed.",
+ title = stringResource(id = R.string.customization_haptic_feedback),
+ subtitle = stringResource(id = R.string.customization_haptic_feedback_subtitle),
icon = Icons.Default.Vibration,
content = {
Row(
@@ -343,8 +344,8 @@ fun CustomizationHome(
}
item {
SettingsItemCustomBottom(
- title = "Long press duration - ${longPressDurationTempValue.roundToInt()} ms",
- subtitle = "Set the duration for a long press action.",
+ title = stringResource(id = R.string.customization_long_press_duration, longPressDurationTempValue.roundToInt()),
+ subtitle = stringResource(id = R.string.customization_long_press_duration_subtitle),
icon = Icons.Default.TouchApp,
content = {
val interactionSource = remember { MutableInteractionSource() }
@@ -480,8 +481,8 @@ fun CustomizationHome(
}
item {
SettingsItemCustomBottom(
- title = "Auto play",
- subtitle = "Automatically resume your selected favorite media player music when you connect your headphones.",
+ title = stringResource(id = R.string.customization_auto_play),
+ subtitle = stringResource(id = R.string.customization_auto_play_subtitle),
icon = Icons.Default.PlayCircleOutline,
trailing = {
Switch(
@@ -567,7 +568,7 @@ fun CustomizationHome(
uiState.customizationSettings.autoPlayMode == item,
)
Text(
- text = item.title,
+ text = stringResource(item.title),
style = MaterialTheme.typography.bodyMedium,
modifier =
Modifier
@@ -590,7 +591,7 @@ fun CustomizationHome(
contentDescription = null,
)
Text(
- text = "Set preferred media player",
+ text = stringResource(id = R.string.customization_set_preferred_media_player),
style = MaterialTheme.typography.bodyMedium,
)
}
diff --git a/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationScreen.kt b/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationScreen.kt
index aea2964..0c43556 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationScreen.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/app/customization/CustomizationScreen.kt
@@ -45,6 +45,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -53,6 +54,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import fr.angel.soundtap.MainViewModel
+import fr.angel.soundtap.R
import fr.angel.soundtap.navigation.Screens
@OptIn(ExperimentalSharedTransitionApi::class)
@@ -124,7 +126,7 @@ fun SharedTransitionScope.CustomizationScreen(
)
Text(
- text = "Customization",
+ text = stringResource(id = R.string.customization_title),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier =
diff --git a/app/src/main/java/fr/angel/soundtap/ui/components/BottomControlBar.kt b/app/src/main/java/fr/angel/soundtap/ui/components/BottomControlBar.kt
index a1a733c..4e9efb6 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/components/BottomControlBar.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/components/BottomControlBar.kt
@@ -1,17 +1,19 @@
/*
- * Copyright 2024 Angel Studio
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * * Copyright (c) 2024 Angel Studio
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
*
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
*/
package fr.angel.soundtap.ui.components
@@ -21,6 +23,7 @@ import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
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.Row
@@ -38,11 +41,9 @@ import androidx.compose.material.icons.filled.NotificationsNone
import androidx.compose.material.icons.filled.NotificationsOff
import androidx.compose.material.icons.filled.SettingsAccessibility
import androidx.compose.material.icons.outlined.Battery0Bar
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
@@ -50,15 +51,30 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import androidx.core.graphics.BlendModeColorFilterCompat
+import androidx.core.graphics.BlendModeCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.RenderMode
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieClipSpec
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.rememberLottieAnimatable
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
import fr.angel.soundtap.GlobalHelper
import fr.angel.soundtap.MainViewModel
+import fr.angel.soundtap.R
import fr.angel.soundtap.VibratorHelper
import fr.angel.soundtap.service.AccessibilityServiceState
import kotlinx.coroutines.delay
@@ -142,7 +158,7 @@ fun BottomControlBar(
),
)
- Button(
+ Box(
modifier =
Modifier
.graphicsLayer(
@@ -150,31 +166,96 @@ fun BottomControlBar(
scaleY = 2f * animatedScale,
translationX = with(LocalDensity.current) { 16.dp.toPx() },
)
- .aspectRatio(1f),
- onClick = {
- scale = 0.9f
+ .aspectRatio(1f)
+ .alpha(if (permissionsGranted) 1f else 0.5f)
+ .zIndex(-1f)
+ .clip(RoundedCornerShape(50))
+ .background(buttonBackgroundColor)
+ .clickable(enabled = permissionsGranted) {
+ scale = 0.9f
- scope.launch {
- delay(150)
- scale = 1f
- }
+ scope.launch {
+ delay(150)
+ scale = 1f
+ }
- VibratorHelper(context = context).doubleClick()
- mainViewModel.onToggleService()
- },
- colors =
- ButtonDefaults.buttonColors(
- containerColor = buttonBackgroundColor,
- ),
- enabled = permissionsGranted,
+ VibratorHelper(context = context).doubleClick()
+ mainViewModel.onToggleService()
+ },
+ contentAlignment = Alignment.Center,
) {
AnimatedContent(
- targetState = isSelectedServiceMethodRunningAndActivated,
- label = "Toggle Service Button",
- ) { running ->
- Text(
- text = if (running) "Pause" else "Start",
- )
+ modifier = Modifier.fillMaxSize(0.5f),
+ targetState = isSelectedServiceMethodRunningAndActivated && permissionsGranted,
+ label = "Animated Content for Lottie Animation",
+ ) { targetState ->
+ if (targetState) {
+ val dynamicProperties =
+ rememberLottieDynamicProperties(
+ rememberLottieDynamicProperty(
+ property = LottieProperty.COLOR_FILTER,
+ value =
+ BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
+ // color =
+ MaterialTheme.colorScheme.onError.toArgb(),
+ // blendModeCompat =
+ BlendModeCompat.SRC_ATOP,
+ ),
+ keyPath = arrayOf("**"),
+ ),
+ )
+ val composition by rememberLottieComposition(spec = LottieCompositionSpec.RawRes(R.raw.power))
+ val anim = rememberLottieAnimatable()
+
+ LaunchedEffect(composition) {
+ anim.animate(
+ composition = composition,
+ speed = 1f,
+ clipSpec = LottieClipSpec.Progress(0.5f, 1f),
+ )
+ }
+
+ LottieAnimation(
+ composition = composition,
+ progress = { anim.value },
+ renderMode = RenderMode.HARDWARE,
+ modifier = Modifier.fillMaxSize(),
+ dynamicProperties = dynamicProperties,
+ )
+ } else {
+ val dynamicProperties =
+ rememberLottieDynamicProperties(
+ rememberLottieDynamicProperty(
+ property = LottieProperty.COLOR_FILTER,
+ value =
+ BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
+ // color =
+ MaterialTheme.colorScheme.onSurface.toArgb(),
+ // blendModeCompat =
+ BlendModeCompat.SRC_ATOP,
+ ),
+ keyPath = arrayOf("**"),
+ ),
+ )
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.power))
+ val anim = rememberLottieAnimatable()
+
+ LaunchedEffect(composition) {
+ anim.animate(
+ composition = composition,
+ speed = -1f,
+ clipSpec = LottieClipSpec.Progress(0.5f, 1f),
+ )
+ }
+
+ LottieAnimation(
+ composition = composition,
+ progress = { anim.value },
+ renderMode = RenderMode.HARDWARE,
+ modifier = Modifier.fillMaxSize(),
+ dynamicProperties = dynamicProperties,
+ )
+ }
}
}
}
diff --git a/app/src/main/java/fr/angel/soundtap/ui/components/GridCard.kt b/app/src/main/java/fr/angel/soundtap/ui/components/GridCard.kt
index de9a238..24cd9dc 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/components/GridCard.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/components/GridCard.kt
@@ -1,17 +1,19 @@
/*
- * Copyright 2024 Angel Studio
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * * Copyright (c) 2024 Angel Studio
+ * *
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * *
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * *
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and
+ * * limitations under the License.
*
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
*/
package fr.angel.soundtap.ui.components
@@ -37,6 +39,7 @@ import androidx.compose.ui.unit.dp
@Composable
fun SharedTransitionScope.GridCard(
modifier: Modifier = Modifier,
+ animationId: String,
animatedVisibilityScope: AnimatedVisibilityScope,
icon: ImageVector,
label: String,
@@ -49,7 +52,7 @@ fun SharedTransitionScope.GridCard(
.sharedElement(
state =
rememberSharedContentState(
- key = "$label-card",
+ key = "$animationId-card",
),
animatedVisibilityScope = animatedVisibilityScope,
),
@@ -69,7 +72,7 @@ fun SharedTransitionScope.GridCard(
.sharedElement(
state =
rememberSharedContentState(
- key = "$label-icon",
+ key = "$animationId-icon",
),
animatedVisibilityScope = animatedVisibilityScope,
),
@@ -85,7 +88,7 @@ fun SharedTransitionScope.GridCard(
.align(Alignment.BottomCenter)
.sharedBounds(
rememberSharedContentState(
- key = label,
+ key = animationId,
),
animatedVisibilityScope = animatedVisibilityScope,
),
diff --git a/app/src/main/java/fr/angel/soundtap/ui/components/MediaCards.kt b/app/src/main/java/fr/angel/soundtap/ui/components/MediaCards.kt
index 294caa9..ffa8b4b 100644
--- a/app/src/main/java/fr/angel/soundtap/ui/components/MediaCards.kt
+++ b/app/src/main/java/fr/angel/soundtap/ui/components/MediaCards.kt
@@ -73,6 +73,7 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -84,6 +85,7 @@ import coil.compose.AsyncImage
import coil.imageLoader
import fr.angel.soundtap.GlobalHelper
import fr.angel.soundtap.MainViewModel
+import fr.angel.soundtap.R
import fr.angel.soundtap.data.StorageHelper
import fr.angel.soundtap.service.media.MediaCallback
import fr.angel.soundtap.service.media.MediaReceiver
@@ -206,7 +208,7 @@ fun EmptyPlayerCard(
)
},
) {
- Text(text = "Start media player")
+ Text(text = stringResource(id = R.string.start_player))
}
}
Spacer(modifier = Modifier.weight(1f))
diff --git a/app/src/main/res/raw/power.json b/app/src/main/res/raw/power.json
new file mode 100644
index 0000000..8e534cb
--- /dev/null
+++ b/app/src/main/res/raw/power.json
@@ -0,0 +1,771 @@
+{
+ "v": "4.10.1",
+ "fr": 24,
+ "ip": 0,
+ "op": 60,
+ "w": 2835,
+ "h": 1701,
+ "nm": "power",
+ "ddd": 0,
+ "assets": [],
+ "layers": [
+ {
+ "ddd": 0,
+ "ind": 1,
+ "ty": 4,
+ "nm": "Layer 12 Outlines",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": 0.833,
+ "y": 0.833
+ },
+ "o": {
+ "x": 0.167,
+ "y": 0.167
+ },
+ "n": "0p833_0p833_0p167_0p167",
+ "t": -50,
+ "s": [
+ 1450.182,
+ 372.985,
+ 0
+ ],
+ "e": [
+ 1450.182,
+ 372.985,
+ 0
+ ],
+ "to": [
+ 0,
+ 0,
+ 0
+ ],
+ "ti": [
+ 0,
+ 0,
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": 0.667,
+ "y": 1
+ },
+ "o": {
+ "x": 0.167,
+ "y": 0.167
+ },
+ "n": "0p667_1_0p167_0p167",
+ "t": 0,
+ "s": [
+ 1450.182,
+ 372.985,
+ 0
+ ],
+ "e": [
+ 1450.182,
+ 579.985,
+ 0
+ ],
+ "to": [
+ 0,
+ 34.5,
+ 0
+ ],
+ "ti": [
+ 0,
+ -31.6666660308838,
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": 0.667,
+ "y": 1
+ },
+ "o": {
+ "x": 0.333,
+ "y": 0
+ },
+ "n": "0p667_1_0p333_0",
+ "t": 7,
+ "s": [
+ 1450.182,
+ 579.985,
+ 0
+ ],
+ "e": [
+ 1450.182,
+ 562.985,
+ 0
+ ],
+ "to": [
+ 0,
+ 31.6666660308838,
+ 0
+ ],
+ "ti": [
+ 0,
+ 2.83333325386047,
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": 0.667,
+ "y": 0.667
+ },
+ "o": {
+ "x": 0.167,
+ "y": 0.167
+ },
+ "n": "0p667_0p667_0p167_0p167",
+ "t": 10,
+ "s": [
+ 1450.182,
+ 562.985,
+ 0
+ ],
+ "e": [
+ 1450.182,
+ 562.985,
+ 0
+ ],
+ "to": [
+ 0,
+ 0,
+ 0
+ ],
+ "ti": [
+ 0,
+ 0,
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": 0.667,
+ "y": 1
+ },
+ "o": {
+ "x": 0.167,
+ "y": 0
+ },
+ "n": "0p667_1_0p167_0",
+ "t": 35,
+ "s": [
+ 1450.182,
+ 562.985,
+ 0
+ ],
+ "e": [
+ 1450.182,
+ 319.985,
+ 0
+ ],
+ "to": [
+ 0,
+ -40.5,
+ 0
+ ],
+ "ti": [
+ 0,
+ 28.3333339691162,
+ 0
+ ]
+ },
+ {
+ "i": {
+ "x": 0.667,
+ "y": 1
+ },
+ "o": {
+ "x": 0.167,
+ "y": 0
+ },
+ "n": "0p667_1_0p167_0",
+ "t": 42,
+ "s": [
+ 1450.182,
+ 319.985,
+ 0
+ ],
+ "e": [
+ 1450.182,
+ 392.985,
+ 0
+ ],
+ "to": [
+ 0,
+ -28.3333339691162,
+ 0
+ ],
+ "ti": [
+ 0,
+ -12.1666669845581,
+ 0
+ ]
+ },
+ {
+ "t": 45
+ }
+ ],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ 83.625,
+ 238.437,
+ 0
+ ],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 100,
+ 100,
+ 100
+ ],
+ "ix": 6
+ }
+ },
+ "ao": 0,
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "o": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ 0
+ ]
+ ],
+ "v": [
+ [
+ -83.375,
+ -238.187
+ ],
+ [
+ 83.375,
+ -238.187
+ ],
+ [
+ 83.375,
+ 238.187
+ ],
+ [
+ -83.375,
+ 238.187
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "fl",
+ "c": {
+ "a": 0,
+ "k": [
+ 0.941000007181,
+ 0.322000002394,
+ 0.156999999402,
+ 1
+ ],
+ "ix": 4
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 5
+ },
+ "r": 1,
+ "nm": "Fill 1",
+ "mn": "ADBE Vector Graphic - Fill",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [
+ 83.625,
+ 238.437
+ ],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 100,
+ 100
+ ],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 1",
+ "np": 2,
+ "cix": 2,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ],
+ "ip": 0,
+ "op": 29880,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 2,
+ "ty": 4,
+ "nm": "Layer 13 Outlines",
+ "sr": 1,
+ "ks": {
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "n": [
+ "0p833_0p833_0p167_0p167"
+ ],
+ "t": 9,
+ "s": [
+ 100
+ ],
+ "e": [
+ 22
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "n": [
+ "0p833_0p833_0p167_0p167"
+ ],
+ "t": 11,
+ "s": [
+ 22
+ ],
+ "e": [
+ 22
+ ]
+ },
+ {
+ "i": {
+ "x": [
+ 0.833
+ ],
+ "y": [
+ 0.833
+ ]
+ },
+ "o": {
+ "x": [
+ 0.167
+ ],
+ "y": [
+ 0.167
+ ]
+ },
+ "n": [
+ "0p833_0p833_0p167_0p167"
+ ],
+ "t": 44,
+ "s": [
+ 22
+ ],
+ "e": [
+ 100
+ ]
+ },
+ {
+ "t": 46
+ }
+ ],
+ "ix": 11
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 10
+ },
+ "p": {
+ "a": 0,
+ "k": [
+ 1450.182,
+ 865.324,
+ 0
+ ],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ 595.781,
+ 583.856,
+ 0
+ ],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 100,
+ 100,
+ 100
+ ],
+ "ix": 6
+ }
+ },
+ "ao": 0,
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ind": 0,
+ "ty": "sh",
+ "ix": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "i": [
+ [
+ 247.714,
+ 72.132
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ -177.343
+ ],
+ [
+ 236.425,
+ 0
+ ],
+ [
+ 0,
+ 236.412
+ ],
+ [
+ -153.846,
+ 65.195
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ -271.001
+ ],
+ [
+ -328.901,
+ 0
+ ],
+ [
+ 0,
+ 328.887
+ ]
+ ],
+ "o": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 153.846,
+ 65.194
+ ],
+ [
+ 0,
+ 236.412
+ ],
+ [
+ -236.426,
+ 0
+ ],
+ [
+ 0,
+ -177.329
+ ],
+ [
+ 0,
+ 0
+ ],
+ [
+ -247.714,
+ 72.132
+ ],
+ [
+ 0.013,
+ 328.887
+ ],
+ [
+ 328.901,
+ 0
+ ],
+ [
+ 0,
+ -271.001
+ ]
+ ],
+ "v": [
+ [
+ 166.744,
+ -583.606
+ ],
+ [
+ 166.744,
+ -406.946
+ ],
+ [
+ 428.767,
+ -11.906
+ ],
+ [
+ -0.006,
+ 416.868
+ ],
+ [
+ -428.768,
+ -11.906
+ ],
+ [
+ -166.744,
+ -406.947
+ ],
+ [
+ -166.744,
+ -583.606
+ ],
+ [
+ -595.531,
+ -11.906
+ ],
+ [
+ 0.006,
+ 583.606
+ ],
+ [
+ 595.531,
+ -11.906
+ ]
+ ],
+ "c": true
+ },
+ "ix": 2
+ },
+ "nm": "Path 1",
+ "mn": "ADBE Vector Shape - Group",
+ "hd": false
+ },
+ {
+ "ty": "fl",
+ "c": {
+ "a": 0,
+ "k": [
+ 0.941000007181,
+ 0.322000002394,
+ 0.156999999402,
+ 1
+ ],
+ "ix": 4
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 5
+ },
+ "r": 1,
+ "nm": "Fill 1",
+ "mn": "ADBE Vector Graphic - Fill",
+ "hd": false
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [
+ 595.781,
+ 583.856
+ ],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [
+ 0,
+ 0
+ ],
+ "ix": 1
+ },
+ "s": {
+ "a": 0,
+ "k": [
+ 100,
+ 100
+ ],
+ "ix": 3
+ },
+ "r": {
+ "a": 0,
+ "k": 0,
+ "ix": 6
+ },
+ "o": {
+ "a": 0,
+ "k": 100,
+ "ix": 7
+ },
+ "sk": {
+ "a": 0,
+ "k": 0,
+ "ix": 4
+ },
+ "sa": {
+ "a": 0,
+ "k": 0,
+ "ix": 5
+ },
+ "nm": "Transform"
+ }
+ ],
+ "nm": "Group 1",
+ "np": 2,
+ "cix": 2,
+ "ix": 1,
+ "mn": "ADBE Vector Group",
+ "hd": false
+ }
+ ],
+ "ip": 0,
+ "op": 29880,
+ "st": 0,
+ "bm": 0
+ }
+ ]
+}
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..2c260e6
--- /dev/null
+++ b/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,159 @@
+
+
+
+ SoundTap
+ SoundTap verwendet den Barrierefreiheitsdienst, um Ereignisse der Lautstärketastenbetätigung zu empfangen.\n\nEs werden keine Daten außer Ereignissen der Lautstärketasten erfasst.
+
+
+ Vorherige
+ Starten
+ Beenden
+ Weiter
+ Ablehnen
+ Zustimmen und fortfahren
+ Ändern
+ Sequenz
+ Bearbeiten
+ Entfernen
+ Abbrechen
+ Fertig
+ Speichern
+ SCHLAFEN
+ WACH
+ Medienplayer starten
+ Pause
+ Stoppen
+
+
+ Willkommen bei SoundTap
+ SoundTap ist Ihr ultimativer Begleiter zur mühelosen Steuerung Ihrer Musikwiedergabe.\n\nMit intuitiven Steuertasten für die Lautstärke, anpassbaren Einstellungen und nahtloser Integration mit Ihren bevorzugten Medienplayern macht SoundTap das Verwalten Ihrer Musik zum Kinderspiel.\n\nBegleiten Sie uns auf dieser Reise, um Ihr Musikhörerlebnis zu verbessern!
+ Ich habe die
+ Datenschutzrichtlinie
+ und die
+ Nutzungsbedingungen
+ Zugriff auf Benachrichtigungen
+ SoundTap benötigt Zugriff auf Ihre Benachrichtigungen, um Mediawiedergabe zu erkennen und Ihre Musik zu steuern. Die App sammelt keine persönlichen Daten oder Informationen.\n\nDiese Berechtigung ist erforderlich, damit SoundTap ordnungsgemäß funktioniert. Bitte aktivieren Sie die Berechtigung für den Zugriff auf Benachrichtigungen, um fortzufahren.\n\nUm die Berechtigung zu aktivieren, tippen Sie auf die Schaltfläche unten und aktivieren Sie SoundTap in der Liste.
+ Benachrichtigungszugriffseinstellungen öffnen
+ Batterieoptimierung
+ SoundTap läuft im Hintergrund, um Ihnen nahtlose Musikwiedergabesteuerungen zu bieten. Um sicherzustellen, dass SoundTap zuverlässig funktioniert, deaktivieren Sie bitte die Batterieoptimierung für die App.\n\nDadurch wird verhindert, dass das System SoundTap im Hintergrund beendet, und stellt sicher, dass Sie Ihre Musikwiedergabe jederzeit steuern können.\n\nUm die Batterieoptimierung zu deaktivieren, tippen Sie auf die Schaltfläche unten und wählen Sie \'Nicht optimieren\' für SoundTap aus.
+ Batterieoptimierung deaktivieren
+ Benachrichtigungszugriff
+ SoundTap sendet Benachrichtigungen für verschiedene Zwecke, wie z. B. den Sleep-Timer.\n\nUm sicherzustellen, dass SoundTap korrekt funktioniert, gewähren Sie der App bitte Zugriff auf Ihre Benachrichtigungen.\n\nUm die Berechtigung für den Benachrichtigungszugriff zu aktivieren, tippen Sie auf die Schaltfläche unten und aktivieren Sie SoundTap in der Liste.\n\nWenn Sie die Berechtigung zuvor verweigert haben, können Sie sie in den App-Einstellungen aktivieren.
+ Barrierefreiheitsdienst
+ SoundTap benötigt die Berechtigung des Barrierefreiheitsdienstes, um die Betätigung der Lautstärketasten zu erkennen und Ihre Musikwiedergabe zu steuern.\n\nDer Barrierefreiheitsdienst ermöglicht es SoundTap nur, die Betätigung der Lautstärketasten zu erkennen, und sammelt keine anderen persönlichen Daten oder Informationen. Er liest NICHT den Bildschirm.\n\nUm den Barrierefreiheitsdienst zu aktivieren, tippen Sie auf die Schaltfläche unten und aktivieren Sie SoundTap in der Liste.
+ Barrierefreiheitseinstellungen öffnen
+ Alles erledigt!
+ Herzlichen Glückwunsch! Sie sind bereit, SoundTap zu verwenden.\n\nUm loszulegen, drücken Sie gleichzeitig die Lautstärketasten nach oben und unten, um die Musikwiedergabe umzuschalten.\n\nWenn Sie Fragen haben oder Hilfe benötigen, können Sie sich gerne an uns wenden.
+ Viel Spaß beim Hören!
+ Verwendung des Barrierefreiheitsdienstes
+ Persönliche und sensible Benutzerdaten:
+ SoundTap verwendet den Barrierefreiheitsdienst nur zum Erfassen von Ereignissen durch Drücken der Lautstärketasten. Dadurch können Sie Ihre Musikwiedergabe mit den Lautstärketasten steuern, ohne Ihr Telefon zu entsperren oder zwischen Apps zu wechseln. Es werden keine persönlichen oder sensiblen Benutzerdaten erfasst, gespeichert oder weitergegeben.
+ Kernfunktionen:
+ Um ordnungsgemäß zu funktionieren, benötigt SoundTap die Barrierefreiheitsberechtigung, um das Drücken der Lautstärketasten zu erkennen. Diese Berechtigung ist für die Kernfunktionalität der App erforderlich und gewährleistet, dass Sie Ihre Musikwiedergabe nahtlos steuern können. Durch Erteilen dieser Berechtigung erkennen Sie an und stimmen zu, dass sie ausschließlich zu diesem Zweck verwendet wird.
+ Datensicherheit und Datenschutz:
+ SoundTap verwendet ausschließlich den Barrierefreiheitsdienst zum Erfassen von Ereignissen der Lautstärketasten und greift nicht auf persönliche oder sensitive Daten zu, erfasst sie nicht und übermittelt sie nicht. Ihre Privatsphäre hat für uns oberste Priorität.\n\nSoundTap erfasst oder teilt über den Barrierefreiheitsdienst keine persönlichen Daten oder Informationen. Alle Operationen werden lokal auf Ihrem Gerät durchgeführt, um sicherzustellen, dass Ihre Daten sicher und privat bleiben.\n\nFür weitere Informationen lesen Sie bitte unsere Datenschutzrichtlinie.
+ Durch Aktivieren des Barrierefreiheitsdienstes für SoundTap stimmen Sie zu, dass die App diesen Dienst ausschließlich zur Erkennung von Ereignissen der Lautstärketasten zur Steuerung der Medienwiedergabe verwendet. Sie können diese Berechtigung jederzeit über die Barrierefreiheitseinstellungen Ihres Geräts deaktivieren.
+
+
+ Übernehmen Sie die Kontrolle über Ihre Musik
+ Berechtigung zum Abhören von Benachrichtigungen
+ SoundTap benötigt die Berechtigung zum Abhören von Benachrichtigungen, um Mediainformationen zu empfangen und Ihre Musik zu steuern. Bitte aktivieren Sie sie, indem Sie auf die Schaltfläche am unteren Bildschirmrand drücken.
+ Barrierefreiheitsdienst
+ SoundTap benötigt den Barrierefreiheitsdienst, um Ereignisse der Lautstärketasten zu empfangen. Bitte aktivieren Sie ihn, indem Sie auf die Schaltfläche am unteren Bildschirmrand drücken.
+ Anpassen
+ Einstellungen
+ Verlauf
+ Support
+ Schlaf-Timer
+ Musik wird in %1$s stoppen
+ Timer abbrechen
+ Timer einstellen
+
+
+ Verlauf
+ Kein Verlauf
+ Ihr Verlauf und Ihre Statistiken werden hier angezeigt
+ Gespielte Gesamtsongs
+ Gesamtsongs übersprungen
+
+
+ Einstellungen
+ Schnellzugriffs-Schaltfläche hinzufügen
+ Fügen Sie eine Schaltfläche zum Schnellzugriffspanel hinzu, um schnell auf SoundTap zuzugreifen
+ Benachrichtigungseinstellungen
+ Konfigurieren Sie die Benachrichtigungen, die SoundTap anzeigen soll
+ Unterstützte Player
+ Wählen Sie die Player aus, die Sie mit SoundTap steuern möchten
+ Bevorzugter Player
+ Wählen Sie den bevorzugten Player aus, den SoundTap standardmäßig steuern soll
+
+
+ Support
+ Fehler melden oder Funktion vorschlagen
+ Melden Sie einen Fehler oder ein Problem mit der App oder schlagen Sie eine Funktion vor, die Sie gerne sehen würden
+ Auf GitHub anzeigen
+ Sehen Sie sich den Quellcode an und tragen Sie zum Projekt bei
+ Discord-Server
+ Treten Sie der Community bei und erhalten Sie Unterstützung
+ Datenschutzrichtlinie
+ Lesen Sie die Datenschutzrichtlinie der App
+ Nutzungsbedingungen
+ Lesen Sie die Nutzungsbedingungen der App
+
+
+ Anpassung
+ Steuerungen anpassen
+ Ändern Sie das Verhalten der Lautstärketasten nach Ihren Wünschen.
+ Arbeitsmodus
+ Wählen Sie aus, wann die Übersprungaktion verfügbar sein soll.
+ Haptisches Feedback
+ Vibrieren, wenn eine Aktion ausgeführt wird.
+ Lange Druckdauer - %1$s ms
+ Legen Sie die Dauer für eine lange Druckaktion fest.
+ Automatisch abspielen
+ Starten Sie automatisch die Musik Ihres ausgewählten Lieblingsmediaplayers, wenn Sie Ihre Kopfhörer anschließen.
+ Bevorzugten Mediaplayer festlegen
+ Benutzerdefinierte Aktionen
+ Benutzerdefinierte Aktion hinzufügen
+
+ Erstellen Sie eine Sequenz von Hardware-Tastenereignissen, um die Aktion auszulösen
+ Die aktuelle Sequenz lautet:
+ Keine Ereignisse in der Sequenz
+
+
+ Bei Anschluss des Headsets
+ Bei doppeltem Drücken der Lautstärke lange
+ Beide
+
+
+ Bildschirm an und aus
+ Bildschirm an
+ Bildschirm aus
+
+
+ Kein
+ Leicht
+ Mittel
+ Stark
+
+
+ Die Musik wird gestoppt, wenn der Timer endet.
+ 15 Minuten hinzufügen
+
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..a612a0c
--- /dev/null
+++ b/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,159 @@
+
+
+
+ SoundTap utilise le service d\'accessibilité pour recevoir des événements lorsque les boutons de volume sont pressés.\n\nAucune donnée autre que les événements des boutons de volume n\'est collectée.
+
+
+ Précédent
+ Démarrer
+ Terminer
+ Suivant
+ Refuser
+ Accepter & Continuer
+ Changer
+ Séquence
+ Modifier
+ Supprimer
+ Annuler
+ Terminé
+ Enregistrer
+ ÉTEINT
+ ALLUMÉ
+ Lancer la musique
+ Pause
+ Stop
+
+
+ Bienvenue sur SoundTap
+ SoundTap est votre compagnon ultime pour contrôler la lecture de votre musique sans effort.\n\nAvec des commandes intuitives des boutons de volume, des paramètres personnalisables et une intégration transparente avec vos lecteurs multimédias préférés, SoundTap facilite la gestion de votre musique.\n\nRejoignez-nous dans ce voyage pour améliorer votre expérience d\'écoute musicale !
+ "J\'ai lu et j\'accepte la "
+ politique de confidentialité
+ " et les "
+ conditions d\'utilisation
+ Accéder aux contrôles multimédias
+ SoundTap nécessite l\'accès à vos notifications pour détecter la lecture des médias et contrôler votre musique. L\'application ne collecte aucune donnée personnelle ou information.\n\nCette permission est nécessaire pour que SoundTap fonctionne correctement. Veuillez activer l\'autorisation d\'accès aux notifications pour continuer.\n\nPour activer l\'autorisation, appuyez sur le bouton ci-dessous et activez SoundTap dans la liste.
+ Ouvrir les paramètres d\'accès aux notifications
+ Optimisation de la batterie
+ SoundTap fonctionne en arrière-plan pour vous fournir des commandes de lecture musicale sans interruption. Pour garantir que SoundTap fonctionne de manière fiable, veuillez désactiver l\'optimisation de la batterie pour l\'application.\n\nCela empêchera le système de fermer SoundTap en arrière-plan et garantira que vous pouvez toujours contrôler la lecture de votre musique.\n\nPour désactiver l\'optimisation de la batterie, appuyez sur le bouton ci-dessous et sélectionnez "Ne pas optimiser" pour SoundTap.
+ Désactiver l\'optimisation de la batterie
+ Accès aux notifications
+ SoundTap publiera des notifications pour diverses fins, telles que pour la minuterie de sommeil.\n\nPour garantir que SoundTap fonctionne correctement, veuillez accorder à l\'application l\'accès à vos notifications.\n\nPour activer l\'autorisation d\'accès aux notifications, appuyez sur le bouton ci-dessous et activez SoundTap dans la liste.\n\nSi vous avez refusé l\'autorisation précédemment, vous pouvez l\'activer à partir des paramètres de l\'application.
+ Service d\'accessibilité
+ SoundTap nécessite l\'autorisation du service d\'accessibilité pour détecter les pressions des boutons de volume et contrôler la lecture de votre musique.\n\nLe service d\'accessibilité permet uniquement à SoundTap de détecter les pressions des boutons de volume et ne collecte aucune autre donnée personnelle ou information. Il ne lit PAS l\'écran.\n\nPour activer le service d\'accessibilité, appuyez sur le bouton ci-dessous et activez SoundTap dans la liste.
+ Ouvrir les paramètres d\'accessibilité
+ Tout est prêt !
+ Félicitations ! Vous êtes prêt à commencer à utiliser SoundTap.\n\nPour commencer, appuyez simultanément sur les boutons de volume haut et bas pour basculer la lecture de la musique.\n\nSi vous avez des questions ou avez besoin d\'aide, n\'hésitez pas à nous contacter.
+ Bonne écoute !
+ Utilisation du service d\'accessibilité
+ Données personnelles et sensibles :
+ SoundTap utilise le service d\'accessibilité uniquement pour enregistrer les événements de clics sur les boutons de volume. Cela vous permet de contrôler la lecture de votre musique en utilisant les boutons de volume sans déverrouiller votre téléphone ou changer d\'application. Aucune donnée personnelle ou sensible de l\'utilisateur n\'est collectée, stockée ou partagée.
+ Fonctionnalité principale :
+ Pour fonctionner correctement, SoundTap nécessite l\'autorisation d\'accessibilité pour détecter les pressions des boutons de volume. Cette autorisation est nécessaire pour la fonctionnalité principale de l\'application et garantit que vous pouvez contrôler la lecture de votre musique sans interruption. En accordant cette autorisation, vous reconnaissez et consentez à son utilisation à cette seule fin.
+ Sécurité et confidentialité des données :
+ SoundTap utilise strictement le service d\'accessibilité pour enregistrer les événements des boutons de volume et n\'accède, ne collecte ni ne transmet aucune donnée personnelle ou sensible. Votre vie privée est notre priorité absolue.\n\nSoundTap ne collecte ni ne partage aucune donnée personnelle ou information via le service d\'accessibilité. Toutes les opérations sont effectuées localement sur votre appareil, garantissant que vos données restent sécurisées et privées.\n\nPour plus d\'informations, veuillez consulter notre Politique de confidentialité.
+ En activant le service d\'accessibilité pour SoundTap, vous consentez à ce que l\'application utilise ce service uniquement pour détecter les événements des boutons de volume afin de contrôler la lecture des médias. Vous pouvez désactiver cette autorisation à tout moment via les paramètres d\'accessibilité de votre appareil.
+
+
+ Prenez le contrôle de votre musique
+ Autorisation d\'écoute des notifications
+ SoundTap nécessite l\'autorisation d\'écoute des notifications pour recevoir des informations sur les médias et contrôler votre musique. Veuillez l\'activer en appuyant sur le bouton en bas de l\'écran.
+ Service d\'accessibilité
+ SoundTap nécessite le service d\'accessibilité pour recevoir les événements des touches de volume. Veuillez l\'activer en appuyant sur le bouton en bas de l\'écran.
+ Personnaliser
+ Paramètres
+ Historique
+ Support
+ Minuteur de sommeil
+ La musique s\'arrêtera dans %1$s
+ Annuler le minuteur
+ Définir le minuteur
+
+
+ Historique
+ Aucun historique
+ Votre historique et vos statistiques seront affichés ici
+ Total des chansons jouées
+ Total des chansons ignorées
+
+
+ Paramètres
+ Ajouter un raccourci
+ Ajouter une tuile au panneau de paramètres rapides pour accéder rapidement à SoundTap
+ Paramètres des notifications
+ Configurer les notifications que SoundTap doit afficher
+ Lecteurs pris en charge
+ Sélectionnez les lecteurs que vous souhaitez contrôler avec SoundTap
+ Lecteur préféré
+ Sélectionnez le lecteur préféré que SoundTap doit contrôler par défaut
+
+
+ Support
+ Signaler un bug ou suggérer une fonctionnalité
+ Signaler un bug ou un problème avec l\'application ou suggérer une fonctionnalité que vous aimeriez voir
+ Voir sur GitHub
+ Voir le code source et contribuer au projet
+ Serveur Discord
+ Rejoignez la communauté et obtenez de l\'aide
+ Politique de confidentialité
+ Lire la politique de confidentialité de l\'application
+ Conditions d\'utilisation
+ Lire les conditions d\'utilisation de l\'application
+
+
+ Personnalisation
+ Personnaliser les commandes
+ Modifier le comportement des boutons de volume comme vous le souhaitez.
+ Mode de fonctionnement
+ Sélectionnez quand l\'action de saut doit être disponible.
+ Retour haptique
+ Vibrer lorsqu\'une action est effectuée.
+ Durée de pression longue - %1$s ms
+ Définir la durée pour une action de pression longue.
+ Lecture automatique
+ Reprendre automatiquement la lecture de votre lecteur multimédia préféré lorsque vous connectez vos écouteurs.
+ Définir le lecteur multimédia préféré
+ Actions personnalisées
+ Ajouter une action personnalisée
+
+
+ Créer une séquence d\'événements de boutons matériels pour déclencher l\'action
+ La séquence actuelle est :
+ Aucun événement dans la séquence
+
+
+ À la connexion du casque
+ À la double pression longue sur le volume
+ Les deux
+
+
+ Écran allumé et éteint
+ Écran allumé
+ Écran éteint
+
+
+ Aucun
+ Léger
+ Moyen
+ Fort
+
+
+ La musique s\'arrêtera lorsque le minuteur se terminera.
+ Ajouter 15 minutes
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0b83fa5..e908797 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,160 @@
+
+
- SoundTap
- SoundTap
+ SoundTap
SoundTap uses the accessibility service to receive events of the volume buttons getting pressed.\n\nNo data other than volume buttons events is collected.
+
+
+ Previous
+ Start
+ Finish
+ Next
+ Decline
+ Agree & Continue
+ Change
+ Sequence
+ Edit
+ Remove
+ Cancel
+ Done
+ Save
+ SLEEP
+ AWAKE
+ Start media player
+ Pause
+ Stop
+
+
+ Welcome to SoundTap
+ SoundTap is your ultimate companion for controlling your music playback effortlessly.\n\nWith intuitive volume button controls, customizable settings, and seamless integration with your favorite media players, SoundTap makes managing your music a breeze.\n\nJoin us on this journey to enhance your music listening experience!
+ I have read and agree to the
+ privacy policy
+ and the
+ terms of service
+ Access Media Controls
+ SoundTap requires access to your notifications to detect media playback and control your music. The app does not collect any personal data or information.\n\nThis permission is necessary for SoundTap to function properly. Please enable the Notifications access permission to continue.\n\nTo enable the permission, tap the button below and enable SoundTap from the list.
+ Open Notification Access Settings
+ Battery Optimization
+ SoundTap runs in the background to provide you with seamless music playback controls. To ensure that SoundTap works reliably, please disable battery optimization for the app.\n\nThis will prevent the system from killing SoundTap in the background and ensure that you can control your music playback at all times.\n\nTo disable battery optimization, tap the button below and select \'Don\'t optimize\' for SoundTap.
+ Disable Battery Optimization
+ Notifications Access
+ SoundTap will post notifications for diverse purposes, such as for the sleep timer.\n\nTo ensure that SoundTap functions correctly, please grant the app access to your notifications.\n\nTo enable the Notifications access permission, tap the button below and enable SoundTap from the list.\n\nIf you have denied the permission previously, you can enable it from the app settings.
+ Accessibility Service
+ SoundTap requires the Accessibility Service permission to detect volume button presses and control your music playback.\n\nThe Accessibility Service only allows SoundTap to detect volume button presses and does not collect any other personal data or information. It does NOT read the screen.\n\nTo enable the Accessibility Service, tap the button below and enable SoundTap from the list.
+ Open Accessibility Settings
+ All Set!
+ Congratulations! You\'re all set to start using SoundTap.\n\nTo get started, press the volume up and down buttons simultaneously to toggle the music playback.\n\nIf you have any questions or need help, feel free to reach out to us.
+ Happy listening!
+ Accessibility Service Usage
+ Personal and Sensitive User Data:
+ SoundTap uses the accessibility service to register volume button click events only. This allows you to control your music playback using volume buttons without unlocking your phone or switching apps. No personal or sensitive user data is collected, stored, or shared.
+ Core Functionality:
+ To function properly, SoundTap requires accessibility permission to detect volume button presses. This permission is necessary for the core functionality of the app and ensures that you can seamlessly control your music playback. By granting this permission, you acknowledge and consent to its use for this purpose only.
+ Data Security & Privacy:
+ SoundTap strictly uses the accessibility service for registering volume button events and does not access, collect, or transmit any personal or sensitive data. Your privacy is our top priority.\n\nSoundTap does not collect or share any personal data or information through the accessibility service. All operations are conducted locally on your device, ensuring your data remains secure and private.\n\nFor more information, please refer to our Privacy Policy.
+ By enabling the accessibility service for SoundTap, you consent to the app using this service solely for detecting volume button events to control media playback. You can disable this permission at any time through your device\'s accessibility settings.
+
+
+ Take Control of Your Music
+ Notification Listener Permission
+ SoundTap needs the Notification Listener permission to receive media information and control your music. Please enable it by pressing the button at the bottom of the screen.
+ Accessibility Service
+ SoundTap needs the Accessibility Service to receive volume key events. Please enable it by pressing the button at the bottom of the screen.
+ Customize
+ Settings
+ History
+ Support
+ Sleep Timer
+ Music will stop in %1$s
+ Cancel Timer
+ Set Timer
+
+
+ History
+ No history
+ Your history and stats will be displayed here
+ Total songs played
+ Total songs skipped
+
+
+ Settings
+ Add quick tile
+ Add a tile to the quick settings panel to quickly access SoundTap
+ Notifications settings
+ Configure the notifications that SoundTap should show
+ Supported players
+ Select the players you want to control with SoundTap
+ Preferred player
+ Select the preferred player that SoundTap should control by default
+
+
+ Support
+ Report a Bug or Suggest a Feature
+ Report a bug or issue with the app or suggest a feature that you would like to see
+ View on GitHub
+ View the source code and contribute to the project
+ Discord Server
+ Join the community and get support
+ Privacy Policy
+ Read the privacy policy of the app
+ Terms of Service
+ Read the terms of service of the app
+
+
+ Customization
+ Customize controls
+ Change the behavior of the volume buttons as you like.
+ Working mode
+ Select when the skipping action should be available.
+ Haptic feedback
+ Vibrate when an action is performed.
+ Long press duration - %1$s ms
+ Set the duration for a long press action.
+ Auto play
+ Automatically resume your selected favorite media player music when you connect your headphones.
+ Set preferred media player
+ Custom actions
+ Add custom action
+
+
+ Create a sequence of hardware button events to trigger the action
+ The current sequence is:
+ No events in the sequence
+
+
+ On headset connected
+ On double volume long press
+ Both
+
+
+ Screen ON and OFF
+ Screen ON
+ Screen OFF
+
+
+ None
+ Light
+ Medium
+ Strong
+
+
+ Music will stop when the timer ends.
+ Add 15 minutes
+
diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml
new file mode 100644
index 0000000..a017262
--- /dev/null
+++ b/app/src/main/res/xml/locales_config.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 917f71c..ca9ebc0 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,9 +2,11 @@
accompanistDrawablepainter = "0.34.0"
accompanistPermissions = "0.34.0"
agp = "8.4.1"
+appcompat = "1.7.0"
+appcompatResources = "1.7.0"
coilCompose = "2.6.0"
datastorePreferences = "1.1.1"
-firebaseBom = "33.0.0"
+firebaseBom = "33.1.0"
gradle = "8.4.1"
gson = "2.11.0"
hiltAndroid = "2.51.1"
@@ -16,27 +18,28 @@ junitVersion = "1.1.5"
espressoCore = "3.5.1"
kotlinxCollectionsImmutable = "0.3.7"
kotlinxSerializationJson = "1.6.3"
-lifecycleRuntimeKtx = "2.8.0"
+lifecycleRuntimeKtx = "2.8.1"
activityCompose = "1.9.0"
composeBom = "2024.05.00"
core-splashscreen = "1.0.1"
lottieCompose = "6.4.1"
material-icons-extended = "1.6.7"
-navigationCompose = "2.8.0-beta01"
-uiCompose = "1.7.0-beta01"
-animationCompose = "1.7.0-beta01"
-foundationCompose = "1.7.0-beta01"
+navigationCompose = "2.8.0-beta02"
+uiCompose = "1.7.0-beta02"
+animationCompose = "1.7.0-beta02"
+foundationCompose = "1.7.0-beta02"
paletteKtx = "1.0.0"
ksp = "1.9.24-1.0.20"
-google-services = "4.4.1"
+google-services = "4.4.2"
firebase-crashlytics = "3.0.1"
firebase-perfs = "1.4.2"
-lifecycleCommon = "2.8.0"
-lifecycleService = "2.8.0"
+lifecycle = "2.8.1"
ktlint = "12.1.1"
[libraries]
accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanistDrawablepainter" }
+androidx-appcompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "appcompatResources" }
+androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastorePreferences" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationFragment" }
@@ -75,8 +78,8 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" }
lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottieCompose" }
androidx-palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "paletteKtx" }
-lifecycle-common = { group = "androidx.lifecycle", name = "lifecycle-common", version.ref = "lifecycleCommon" }
-lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "lifecycleService" }
+lifecycle-common = { group = "androidx.lifecycle", name = "lifecycle-common", version.ref = "lifecycle" }
+lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "lifecycle" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }