mirror of
https://github.com/Michatec/Radio.git
synced 2026-05-30 23:52:40 +02:00
feat(ui): add manual language selection to settings
This commit is contained in:
@@ -1,14 +1,40 @@
|
||||
package com.michatec.radio
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.view.Menu
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
|
||||
import com.michatec.radio.helpers.PreferencesHelper
|
||||
import java.util.Locale
|
||||
|
||||
class ExpandedControllerActivity : ExpandedControllerActivity() {
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
val languageCode = PreferencesHelper.loadSelectedLanguage()
|
||||
val context = if (languageCode.isEmpty() || languageCode == "system") {
|
||||
// Use system default locale
|
||||
newBase
|
||||
} else {
|
||||
val locale = Locale.forLanguageTag(languageCode)
|
||||
val config = Configuration(newBase.resources.configuration)
|
||||
config.setLocale(locale)
|
||||
newBase.createConfigurationContext(config)
|
||||
}
|
||||
super.attachBaseContext(context)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.expanded_controller, menu)
|
||||
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
try {
|
||||
super.onResume()
|
||||
} catch (_: ClassCastException) {
|
||||
// Fix for lifecycle exception on some devices (e.g. Samsung)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ object Keys {
|
||||
const val PREF_PRESET_REVERB: String = "PRESET_REVERB"
|
||||
const val PREF_PRESET_DRC: String = "PRESET_DRC"
|
||||
const val PREF_PRESET_STEREO_WIDTH: String = "PRESET_STEREO_WIDTH"
|
||||
const val PREF_LANGUAGE_SELECTED: String = "PRESET_LANGUAGE_SELECTED"
|
||||
|
||||
// default const values
|
||||
const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.michatec.radio
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -14,10 +16,13 @@ import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.NavigationUI
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import com.michatec.radio.dialogs.LanguageSelectionDialog
|
||||
import com.michatec.radio.helpers.AppThemeHelper
|
||||
import com.michatec.radio.helpers.FileHelper
|
||||
import com.michatec.radio.helpers.LanguageHelper
|
||||
import com.michatec.radio.helpers.PreferencesHelper
|
||||
import org.woheller69.freeDroidWarn.FreeDroidWarn
|
||||
import java.util.Locale
|
||||
|
||||
/*
|
||||
* MainActivity class
|
||||
@@ -27,6 +32,21 @@ class MainActivity : AppCompatActivity() {
|
||||
/* Main class variables */
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
|
||||
/* Overrides attachBaseContext from AppCompatActivity */
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
val languageCode = PreferencesHelper.loadSelectedLanguage()
|
||||
val context = if (languageCode.isEmpty() || languageCode == "system") {
|
||||
// Use system default locale
|
||||
newBase
|
||||
} else {
|
||||
val locale = Locale.forLanguageTag(languageCode)
|
||||
val config = Configuration(newBase.resources.configuration)
|
||||
config.setLocale(locale)
|
||||
newBase.createConfigurationContext(config)
|
||||
}
|
||||
super.attachBaseContext(context)
|
||||
}
|
||||
|
||||
/* Overrides onCreate from AppCompatActivity */
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
@@ -112,6 +132,9 @@ class MainActivity : AppCompatActivity() {
|
||||
Keys.PREF_THEME_SELECTION -> {
|
||||
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
||||
}
|
||||
Keys.PREF_LANGUAGE_SELECTED -> {
|
||||
LanguageHelper.setLanguage(this, PreferencesHelper.loadSelectedLanguage())
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.michatec.radio
|
||||
|
||||
import android.app.Application
|
||||
import com.michatec.radio.helpers.AppThemeHelper
|
||||
import com.michatec.radio.helpers.LanguageHelper
|
||||
import com.michatec.radio.helpers.PreferencesHelper
|
||||
import com.michatec.radio.helpers.PreferencesHelper.initPreferences
|
||||
|
||||
@@ -18,6 +19,7 @@ class Radio : Application() {
|
||||
initPreferences()
|
||||
// set Dark / Light theme state
|
||||
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
||||
LanguageHelper.setLanguage(this, PreferencesHelper.loadSelectedLanguage())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.*
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.michatec.radio.dialogs.ErrorDialog
|
||||
import com.michatec.radio.dialogs.LanguageSelectionDialog
|
||||
import com.michatec.radio.dialogs.PresetSelectionDialog
|
||||
import com.michatec.radio.dialogs.ThemeSelectionDialog
|
||||
import com.michatec.radio.dialogs.YesNoDialog
|
||||
@@ -31,7 +32,7 @@ import java.util.*
|
||||
/*
|
||||
* SettingsFragment class
|
||||
*/
|
||||
class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogListener, ThemeSelectionDialog.ThemeSelectionDialogListener, PresetSelectionDialog.PresetSelectionDialogListener {
|
||||
class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogListener, ThemeSelectionDialog.ThemeSelectionDialogListener, PresetSelectionDialog.PresetSelectionDialogListener, LanguageSelectionDialog.LanguageSelectionDialogListener {
|
||||
|
||||
|
||||
/* Define log tag */
|
||||
@@ -308,6 +309,18 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
val preferenceLanguageSelection = Preference(context)
|
||||
preferenceLanguageSelection.title = getString(R.string.pref_language_selection_title)
|
||||
preferenceLanguageSelection.setIcon(R.drawable.ic_language_24dp)
|
||||
preferenceLanguageSelection.key = Keys.PREF_LANGUAGE_SELECTED
|
||||
preferenceLanguageSelection.summary = "${getString(R.string.pref_language_selection_summary)}: ${
|
||||
LanguageHelper.getCurrentLanguage(activity as Context)
|
||||
}"
|
||||
preferenceLanguageSelection.setOnPreferenceClickListener {
|
||||
LanguageSelectionDialog(this).show(activity as Context)
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
|
||||
// set preference categories
|
||||
val preferenceCategoryGeneral = PreferenceCategory(activity as Context)
|
||||
@@ -334,6 +347,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
|
||||
screen.addPreference(preferenceCategoryGeneral)
|
||||
preferenceCategoryGeneral.addPreference(preferenceThemeSelection)
|
||||
preferenceCategoryGeneral.addPreference(preferenceLanguageSelection)
|
||||
|
||||
screen.addPreference(preferenceCategoryAudioEffects)
|
||||
preferenceCategoryAudioEffects.addPreference(preferenceBassBoost)
|
||||
@@ -401,6 +415,20 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
}
|
||||
}
|
||||
|
||||
/* Overrides onLanguageSelectionDialog from LanguageSelectionDialogListener */
|
||||
override fun onLanguageSelectionDialog(dialogResult: Boolean, selectedLanguage: String) {
|
||||
if (dialogResult) {
|
||||
// update summary
|
||||
val languagePreference = findPreference<Preference>(Keys.PREF_LANGUAGE_SELECTED)
|
||||
val languageSummary = if (selectedLanguage.isEmpty()) {
|
||||
getString(R.string.pref_language_system)
|
||||
} else {
|
||||
LanguageHelper.getCurrentLanguage(activity as Context)
|
||||
}
|
||||
languagePreference?.summary = "${getString(R.string.pref_language_selection_summary)}: $languageSummary"
|
||||
}
|
||||
}
|
||||
|
||||
/* Updates the enabled/disabled state of EQ controls based on preset selection */
|
||||
private fun updateEqControlStates() {
|
||||
val currentPreset = PreferencesHelper.loadSelectedPreset()
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.michatec.radio.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.michatec.radio.R
|
||||
import com.michatec.radio.helpers.PreferencesHelper
|
||||
|
||||
|
||||
/*
|
||||
* LanguageSelectionDialog class
|
||||
*/
|
||||
class LanguageSelectionDialog(private var languageSelectionDialogListener: LanguageSelectionDialogListener) {
|
||||
|
||||
/* Interface used to communicate back to activity */
|
||||
interface LanguageSelectionDialogListener {
|
||||
fun onLanguageSelectionDialog(dialogResult: Boolean, selectedLanguage: String)
|
||||
}
|
||||
|
||||
|
||||
/* Main class variables */
|
||||
private lateinit var dialog: AlertDialog
|
||||
|
||||
|
||||
/* Data class representing a supported language */
|
||||
data class Language(
|
||||
val code: String,
|
||||
val nameResId: Int
|
||||
)
|
||||
|
||||
|
||||
/* List of supported languages - displayed in their own language */
|
||||
private val supportedLanguages = listOf(
|
||||
Language("system", R.string.pref_language_system),
|
||||
Language("en", R.string.pref_language_en),
|
||||
Language("de", R.string.pref_language_de),
|
||||
Language("fr", R.string.pref_language_fr),
|
||||
Language("ru", R.string.pref_language_ru),
|
||||
Language("ja", R.string.pref_language_ja),
|
||||
Language("nl", R.string.pref_language_nl),
|
||||
Language("pl", R.string.pref_language_pl),
|
||||
Language("el", R.string.pref_language_el),
|
||||
Language("da", R.string.pref_language_da)
|
||||
)
|
||||
|
||||
|
||||
/* Counter for generating unique view IDs */
|
||||
private var viewIdCounter = 0x7F010001 // Starting after android.R.id.home
|
||||
|
||||
|
||||
/* Construct and show dialog */
|
||||
fun show(context: Context) {
|
||||
// prepare dialog builder
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
|
||||
// inflate custom layout
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val view = inflater.inflate(R.layout.dialog_language_selection, null)
|
||||
|
||||
// find radio group
|
||||
val radioGroup = view.findViewById<RadioGroup>(R.id.language_radio_group)
|
||||
val currentLanguage = PreferencesHelper.loadSelectedLanguage()
|
||||
|
||||
// add radio buttons for each supported language
|
||||
for (language in supportedLanguages) {
|
||||
val radioButton = RadioButton(context).apply {
|
||||
id = generateViewId()
|
||||
tag = language.code
|
||||
text = context.getString(language.nameResId)
|
||||
textSize = if (isTelevision(context)) 20f else 16f
|
||||
setPadding(dpToPx(context, 8), dpToPx(context, 16), dpToPx(context, 16), dpToPx(context, 16))
|
||||
}
|
||||
radioGroup.addView(radioButton)
|
||||
}
|
||||
|
||||
// set current selection
|
||||
for (i in 0 until radioGroup.childCount) {
|
||||
val radioButton = radioGroup.getChildAt(i) as RadioButton
|
||||
if (radioButton.tag == currentLanguage) {
|
||||
radioButton.isChecked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if no language is selected, check the first one (system)
|
||||
if (radioGroup.checkedRadioButtonId == -1) {
|
||||
val firstButton = radioGroup.getChildAt(0) as RadioButton
|
||||
firstButton.isChecked = true
|
||||
}
|
||||
|
||||
// set up radio group listener
|
||||
radioGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||
val selectedButton = radioGroup.findViewById<RadioButton>(checkedId)
|
||||
val selectedLanguageCode = selectedButton?.tag as? String ?: "system"
|
||||
|
||||
// save language selection to preferences
|
||||
PreferencesHelper.saveSelectedLanguage(selectedLanguageCode)
|
||||
|
||||
// notify listener
|
||||
languageSelectionDialogListener.onLanguageSelectionDialog(true, selectedLanguageCode)
|
||||
|
||||
// dismiss dialog
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
// set custom view
|
||||
builder.setView(view)
|
||||
|
||||
// handle outside-click as cancel
|
||||
builder.setOnCancelListener {
|
||||
languageSelectionDialogListener.onLanguageSelectionDialog(false, "")
|
||||
}
|
||||
|
||||
// display dialog
|
||||
dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
|
||||
/* Generate a unique view ID */
|
||||
private fun generateViewId(): Int {
|
||||
return viewIdCounter++
|
||||
}
|
||||
|
||||
|
||||
/* Helper function to check if device is a TV */
|
||||
private fun isTelevision(context: Context): Boolean {
|
||||
val uiMode = context.resources.configuration.uiMode
|
||||
return (uiMode and android.content.res.Configuration.UI_MODE_TYPE_MASK) == android.content.res.Configuration.UI_MODE_TYPE_TELEVISION
|
||||
}
|
||||
|
||||
|
||||
/* Helper function to convert dp to pixels */
|
||||
private fun dpToPx(context: Context, dp: Int): Int {
|
||||
return (dp * context.resources.displayMetrics.density).toInt()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.michatec.radio.helpers
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.util.Log
|
||||
import com.michatec.radio.R
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
/*
|
||||
* LanguageHelper object
|
||||
*/
|
||||
object LanguageHelper {
|
||||
|
||||
/* Define log tag */
|
||||
private val TAG: String = LanguageHelper::class.java.simpleName
|
||||
|
||||
|
||||
/* Sets the app language on the activity */
|
||||
fun setLanguage(context: Context, languageCode: String): Boolean {
|
||||
if (languageCode.isEmpty()) {
|
||||
Log.i(TAG, "No language code provided, using system default")
|
||||
return false
|
||||
}
|
||||
|
||||
if (languageCode == "system") {
|
||||
Log.i(TAG, "Reverting to system default locale")
|
||||
if (context is Activity) {
|
||||
context.recreate()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val locale = Locale.forLanguageTag(languageCode)
|
||||
Locale.setDefault(locale)
|
||||
|
||||
|
||||
if (context is Activity) {
|
||||
context.recreate()
|
||||
}
|
||||
|
||||
Log.i(TAG, "Locale changed to: $languageCode")
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/* Returns a localized resources object */
|
||||
fun getCurrentLanguage(context: Context): String {
|
||||
return when (val languageCode = PreferencesHelper.loadSelectedLanguage()) {
|
||||
"system" -> context.getString(R.string.pref_language_system)
|
||||
"en" -> context.getString(R.string.pref_language_en)
|
||||
"de" -> context.getString(R.string.pref_language_de)
|
||||
"fr" -> context.getString(R.string.pref_language_fr)
|
||||
"ru" -> context.getString(R.string.pref_language_ru)
|
||||
"ja" -> context.getString(R.string.pref_language_ja)
|
||||
"nl" -> context.getString(R.string.pref_language_nl)
|
||||
"pl" -> context.getString(R.string.pref_language_pl)
|
||||
"el" -> context.getString(R.string.pref_language_el)
|
||||
"da" -> context.getString(R.string.pref_language_da)
|
||||
else -> languageCode
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,6 +316,16 @@ object PreferencesHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/* Loads selected language */
|
||||
fun loadSelectedLanguage(): String {
|
||||
return sharedPreferences.getString(Keys.PREF_LANGUAGE_SELECTED, "system") ?: "system"
|
||||
}
|
||||
|
||||
/* Saves selected language */
|
||||
fun saveSelectedLanguage(language: String) {
|
||||
sharedPreferences.edit { putString(Keys.PREF_LANGUAGE_SELECTED, language) }
|
||||
}
|
||||
|
||||
/* Loads preset Bass Boost */
|
||||
fun loadPresetBassBoost(): Float {
|
||||
return sharedPreferences.getFloat(Keys.PREF_PRESET_BASS_BOOST, 0f)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/icon_default"
|
||||
android:pathData="M280,680L560,680L560,600L280,600L280,680ZM280,520L680,520L680,440L280,440L280,520ZM280,360L680,360L680,280L280,280L280,360ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/pref_language_selection_title"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/language_radio_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/pref_language_selection_title"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingBottom="12dp" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/language_radio_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -58,6 +58,21 @@
|
||||
<string name="player_sheet_h2_station_metadata">Currently playing</string>
|
||||
<string name="player_sheet_h2_stream_url">Streaming link</string>
|
||||
|
||||
<!-- Language Selection -->
|
||||
<string name="pref_language_selection_title">Language</string>
|
||||
<string name="pref_language_selection_summary">Current language</string>
|
||||
<string name="pref_language_system">🗺️ System</string>
|
||||
<string name="pref_language_en" translatable="false">🇬🇧 English</string>
|
||||
<string name="pref_language_de" translatable="false">🇩🇪 Deutsch</string>
|
||||
<string name="pref_language_fr" translatable="false">🇫🇷 Français</string>
|
||||
<string name="pref_language_ru" translatable="false">🇷🇺 Русский</string>
|
||||
<string name="pref_language_ja" translatable="false">🇯🇵 日本語</string>
|
||||
<string name="pref_language_nl" translatable="false">🇳🇱 Nederlands</string>
|
||||
<string name="pref_language_pl" translatable="false">🇵🇱 Polski</string>
|
||||
<string name="pref_language_el" translatable="false">🇬🇷 Ελληνικά</string>
|
||||
<string name="pref_language_da" translatable="false">🇩🇰 Dansk</string>
|
||||
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="pref_update_collection_title">Update Stations</string>
|
||||
<string name="pref_update_collection_summary">Download latest version of all station.</string>
|
||||
|
||||
Reference in New Issue
Block a user