50 Commits

Author SHA1 Message Date
Michatec 213246a0c4 chore(build): add Gradle Plugin Portal to repository list
Include `gradlePluginPortal()` in the `dependencyResolutionManagement`
repositories block within `settings.gradle.kts`. This ensures
dependencies can be resolved from the official Gradle plugin repository.
2026-06-02 00:05:19 +02:00
Michatec ae1e2aa784 chore(notification): update notification IDs
Increment the notification IDs used in `SettingsFragment` and
`PlayerFragment`. This updates the identifiers for the test,
share-app, and update-available notifications.
2026-06-01 19:52:26 +02:00
Michatec d654d79bd5 feat(notification): support custom notification IDs
Update `NotificationSys.showNotification` to accept an optional `id`
parameter. This allows different notifications to be displayed
simultaneously by the system instead of overwriting each other.

In `SettingsFragment` and `PlayerFragment`, assign specific unique IDs
for test, share, and update notifications.
2026-06-01 19:44:56 +02:00
Michatec 181ebd47df feat(ui): add custom theme color support and share app functionality
Introduce a new custom theme feature that allows users to personalize the application background color using RGB sliders, hex input, or a set of predefined palettes.

Key changes include:
* **Custom Theme Engine**: Added `CustomThemeFragment` and `ThemeHelper` to manage color selection and application. The UI dynamically updates the background color across the `MainActivity` and `PlayerFragment` when preferences change.
* **Android TV Support**: Provided a specialized layout for television devices (`fragment_custom_theme.xml` in `layout-television`) with optimized focus handling for D-pad navigation.
* **Share Feature**: Implemented a "Share App" preference in `SettingsFragment` that triggers a standard Android share intent and displays a thank-you notification upon use.
* **Localization**: Added Ukrainian language support and updated string resources for multiple locales (DE, DA, EL, FR, JA, NL, PL, RU) to include the new theme and share options.
* **Persistence**: Updated `PreferencesHelper` and `Keys` to store theme-related settings, including the enabled state, selected color, and predefined color index.
2026-06-01 19:32:46 +02:00
Michatec 4429ed4057 refactor(ui): use anchor view for Snackbars above bottom sheet
Replace manual bottom margin adjustments with `setAnchorView` for Snackbars to ensure they are positioned correctly above the bottom sheet on mobile devices.

- In `LayoutHolder`, make `bottomSheet` public to allow access from other components.
- In `PlayerFragment` and `MainActivity`, replace `FrameLayout.LayoutParams` modifications with `setAnchorView(bottomSheet)`.
- Maintain standard Snackbar behavior for Android TV where the bottom sheet is not present.
2026-05-31 23:06:57 +02:00
Michatec 83752c356f feat(android): refine notification permissions and UI layout
Add explicit `POST_NOTIFICATIONS` permission checks in `SettingsFragment` and `PlayerFragment` before triggering notifications or showing related preferences.

Update the permission launcher in `MainActivity` to provide a "Settings" action that links to the system application details when the notification permission is permanently denied.

Improve UI and system integration by:
* Adjusting `Snackbar` positioning with bottom margins in `MainActivity` and `PlayerFragment`.
* Updating `NotificationSys` to use a new channel ID with `IMPORTANCE_LOW`.
* Configuring `MainActivity` in `activity_main.xml` to handle orientation and screen size configuration changes manually.
2026-05-31 19:27:51 +02:00
Michatec c1bc8b3e19 refactor(android): simplify notification permission request
Remove the explicit permission type from the `RequestPermission`
contract in `MainActivity` to rely on default behavior or
internal contract handling.
2026-05-30 23:11:19 +02:00
Michatec 8835cadd6a fix(ui): specify notification permission type in launcher
Explicitly pass `Manifest.permission.POST_NOTIFICATIONS` to the
`RequestPermission` contract to ensure the correct permission is
requested on Android 13+.
2026-05-30 23:09:50 +02:00
Michatec 384926cf08 chore(build): disable debug mode in build configuration
Set `IS_DEBUG_ENABLED` to `false` in `app/build.gradle.kts` to prepare
the build configuration for release.
2026-05-30 22:51:25 +02:00
Michatec 2cf98b89b3 feat(notification): allow custom intent for notifications
Update `NotificationSys` to support passing a custom `Intent` when
showing notifications. This enables more flexible navigation, such as
opening a web URL for update notifications.

In `PlayerFragment`, use this new capability to trigger a browser
intent for update notifications on non-Android TV devices.
2026-05-30 22:48:26 +02:00
Michatec 6fa1e5e2c0 style(ui): update test notification icon
Replace the white notification icon with a standard 24dp icon in
SettingsFragment to ensure visual consistency.
2026-05-30 22:30:32 +02:00
Michatec 64d0f3a71f fix(android): resolve compilation error in TV feature detection
Update the Android TV feature check to use the instance-based
`packageManager` instead of the static `PackageManager` class.
This ensures the `hasSystemFeature` method is called correctly on
the available context instance.
2026-05-30 22:23:22 +02:00
Michatec c9122d74f8 refactor(android): fix Android TV feature detection syntax
Correct the usage of `hasSystemFeature` by calling it on the `packageManager` instance instead of the `PackageManager` class. This fixes a compilation error in `MainActivity` and `SettingsFragment`.
2026-05-30 22:18:32 +02:00
Michatec 1879827f4b feat(ui): add test notification preference and optimize Android TV logic
Introduce a "Test Notification" preference in the settings menu to allow users to verify the notification system. This preference is automatically hidden on Android TV devices to maintain a clean UI.

Additionally, refactor notification permission handling to skip requests on Android TV and improve the internal check for Leanback support using a lazy property.

Updated string resources for the new preference across all supported languages.
2026-05-30 22:15:51 +02:00
Michatec 23079649c5 style(i18n): fix typography in Danish notification description
Update the apostrophe in `notification_channel_description` to use a
typographic curly apostrophe for better linguistic accuracy in
Danish.
2026-05-30 21:36:22 +02:00
Michatec 2c7e02889d feat(i18n): add notification channel description to all languages
Add the `notification_channel_description` string resource to the
default and all localized string files to provide context for the
notification channel.
2026-05-30 21:33:43 +02:00
Michatec b1bcfa2137 style(i18n): update French typography in string resources
Replace standard apostrophes with typographic curly apostrophes in
`notification_test_content` and `snackbar_failed_permission_notification`
to improve French localization consistency.
2026-05-30 21:23:24 +02:00
Michatec 2814ff2cfa feat(android): implement POST_NOTIFICATIONS permission handling
Add NotificationSys to manage system notifications and update
MainActivity to request POST_NOTIFICATIONS permission on Android 13+.
Includes localized string resources for notification testing and
permission error feedback.
2026-05-30 21:17:01 +02:00
Michachatz a9f8efc72d Update star history chart link in README.md 2026-05-26 00:01:34 +02:00
Michachatz a5e27149ba Add star history section to README
Added a star history section with a chart to the README.
2026-05-26 00:00:35 +02:00
Michatec 2688f22a6a chore(app): add IS_DEBUG_ENABLED build config field
Introduce a `IS_DEBUG_ENABLED` boolean flag in `BuildConfig` for both
debug and release build types. This flag is used to suppress update
notifications in `PlayerFragment` when debugging and before its a version released.
2026-05-15 22:20:23 +02:00
Michachatz 73edf9d816 Merge pull request #73 from Michatec/renovate/gradle-9.x
chore(deps): update gradle to v9.5.1
2026-05-15 21:54:37 +02:00
Michachatz 9f39b1dbbd Merge pull request #74 from Michatec/renovate/media3
fix(deps): update media3 to v1.10.1
2026-05-15 21:54:18 +02:00
Michachatz 5d82d722bd Merge pull request #75 from Michatec/renovate/material
fix(deps): update dependency com.google.android.material:material to v1.14.0
2026-05-15 21:53:50 +02:00
Michatec 5d7df9550e feat(ui): add security preference to settings
Add a new security preference in the settings screen that links to the
SECURITY.md file on GitHub. This includes a new security icon and
localized strings for multiple languages.
2026-05-15 21:51:07 +02:00
renovate[bot] c7b7114010 fix(deps): update dependency com.google.android.material:material to v1.14.0 2026-05-13 18:06:11 +00:00
renovate[bot] ed8f17e436 fix(deps): update media3 to v1.10.1 2026-05-12 16:00:26 +00:00
renovate[bot] 2cc2a23e35 chore(deps): update gradle to v9.5.1 2026-05-12 16:00:20 +00:00
Michachatz f7ddc30127 Merge pull request #72 from Michatec/renovate/freedroidwarn
fix(deps): update dependency com.github.woheller69:freedroidwarn to v1.13
2026-05-11 07:50:32 +02:00
renovate[bot] bc0eb60e04 fix(deps): update dependency com.github.woheller69:freedroidwarn to v1.13 2026-05-10 11:51:08 +00:00
Michatec dcae7bafb5 feat: apply custom theme to ExpandedControllerActivity 2026-05-07 13:49:37 +02:00
Michatec cf3fd0bc56 refactor(media): move MEDIA_BUTTON action to MediaSessionService 2026-05-07 13:43:58 +02:00
Michatec 7d6b0fe136 feat: implement station navigation and update to SDK 37
- Implement `seekToNext` and `seekToPrevious` in `PlayerService` to allow switching between radio stations.
- Update `compileSdk` and `targetSdk` to 37 and bump version to 14.6.
- Modify `PlayerService` to remain active while paused to improve playback resumption.
- Modernize media resumption logic using Media3 `isRecent` check instead of legacy extras.
- Refactor `DefaultRenderersFactory` to use non-nullable `AudioSink` return types.
- General code cleanup using KTX extensions (e.g., `View.isEmpty()`) and Kotlin property access syntax.
2026-05-07 13:40:35 +02:00
Michachatz 0faeea7631 Merge pull request #70 from Michatec/renovate/agp
chore(deps): update agp to v9.2.1
2026-05-07 12:18:23 +02:00
Michachatz c45adf07c8 Merge pull request #71 from Michatec/renovate/media
fix(deps): update dependency androidx.media:media to v1.8.0
2026-05-07 12:18:08 +02:00
renovate[bot] acd1b067b3 fix(deps): update dependency androidx.media:media to v1.8.0 2026-05-06 20:08:16 +00:00
renovate[bot] e3a325f568 chore(deps): update agp to v9.2.1 2026-05-05 17:45:30 +00:00
Michachatz b537df898b Merge pull request #69 from Michatec/renovate/freedroidwarn
fix(deps): update dependency com.github.woheller69:freedroidwarn to v1.12
2026-05-01 13:50:50 +02:00
Michachatz 61675601f3 Merge pull request #68 from Michatec/renovate/gradle-9.x
chore(deps): update gradle to v9.5.0
2026-05-01 13:48:43 +02:00
renovate[bot] 6dc5c47b86 fix(deps): update dependency com.github.woheller69:freedroidwarn to v1.12 2026-04-30 21:46:44 +00:00
renovate[bot] c40ad0cc4a chore(deps): update gradle to v9.5.0 2026-04-28 15:17:44 +00:00
Michachatz 066206c6dc Merge pull request #67 from Michatec/renovate/gson
fix(deps): update dependency com.google.code.gson:gson to v2.14.0
2026-04-26 14:48:57 +02:00
renovate[bot] e8e66c24ef fix(deps): update dependency com.google.code.gson:gson to v2.14.0 2026-04-23 21:10:57 +00:00
Michachatz 56647e7f02 Merge pull request #66 from Michatec/renovate/kotlin-monorepo
chore(deps): update dependency org.jetbrains.kotlin.android to v2.3.21
2026-04-23 17:57:41 +02:00
renovate[bot] a7e0efc913 chore(deps): update dependency org.jetbrains.kotlin.android to v2.3.21 2026-04-23 09:56:51 +00:00
Michachatz 0569bed475 Merge pull request #65 from Michatec/renovate/navigation
fix(deps): update navigation to v2.9.8
2026-04-23 11:52:21 +02:00
renovate[bot] 6e3b187b56 fix(deps): update navigation to v2.9.8 2026-04-22 21:41:22 +00:00
Michachatz c8c0d1783b Merge pull request #64 from Michatec/renovate/agp
chore(deps): update agp to v9.2.0
2026-04-22 06:25:14 +02:00
Michachatz 4974514a81 refactor(ui): Change from normal switch to marquee switches 2026-04-22 06:22:31 +02:00
renovate[bot] afd34c6485 chore(deps): update agp to v9.2.0 2026-04-21 20:38:52 +00:00
47 changed files with 1326 additions and 84 deletions
+9
View File
@@ -105,6 +105,15 @@ When **Edit Stations** is enabled:
</details> </details>
----------------------------------------
<details>
<summary>⭐ Star History</summary>
[![Star History Chart](https://api.star-history.com/chart?repos=michatec/radio&type=date&legend=top-left)](https://www.star-history.com/?repos=michatec%2Fradio&type=date&legend=top-left)
</details>
<div align="right"> <div align="right">
<table><td> <table><td>
<a href="#start-of-content">↥ Scroll to top</a> <a href="#start-of-content">↥ Scroll to top</a>
+6 -4
View File
@@ -5,14 +5,14 @@ plugins {
android { android {
namespace = "com.michatec.radio" namespace = "com.michatec.radio"
compileSdk = 36 compileSdk = 37
defaultConfig { defaultConfig {
applicationId = "com.michatec.radio" applicationId = "com.michatec.radio"
minSdk = 28 minSdk = 28
targetSdk = 36 targetSdk = 37
versionCode = 145 versionCode = 146
versionName = "14.5" versionName = "14.6"
} }
compileOptions { compileOptions {
@@ -31,6 +31,7 @@ android {
isCrunchPngs = false isCrunchPngs = false
proguardFiles.addAll(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), file("proguard-rules.pro"))) proguardFiles.addAll(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), file("proguard-rules.pro")))
applicationIdSuffix = ".debug" applicationIdSuffix = ".debug"
buildConfigField("boolean", "IS_DEBUG_ENABLED", "true")
} }
release { release {
@@ -38,6 +39,7 @@ android {
isShrinkResources = true isShrinkResources = true
isCrunchPngs = true isCrunchPngs = true
proguardFiles.addAll(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), file("proguard-rules.pro"))) proguardFiles.addAll(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), file("proguard-rules.pro")))
buildConfigField("boolean", "IS_DEBUG_ENABLED", "false")
} }
} }
+5 -9
View File
@@ -9,10 +9,12 @@
android:required="false" /> android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:name=".Radio" android:name=".Radio"
@@ -48,6 +50,7 @@
<activity <activity
android:name=".ExpandedControllerActivity" android:name=".ExpandedControllerActivity"
android:exported="false" android:exported="false"
android:theme="@style/CustomCastExpandedControllerStyle"
android:launchMode="singleTask" /> android:launchMode="singleTask" />
<!-- Main activity for radio station playback on phone and TV --> <!-- Main activity for radio station playback on phone and TV -->
@@ -136,6 +139,7 @@
<intent-filter> <intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" /> <action android:name="androidx.media3.session.MediaSessionService" />
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="com.michatec.radio.action.START_PLAYER_SERVICE" /> <action android:name="com.michatec.radio.action.START_PLAYER_SERVICE" />
</intent-filter> </intent-filter>
</service> </service>
@@ -148,14 +152,6 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
@@ -120,7 +120,7 @@ class AddStationFragment : Fragment(),
if (searchEditText != null) { if (searchEditText != null) {
searchEditText.requestFocus() searchEditText.requestFocus()
val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT) imm.showSoftInput(searchEditText, 0)
} }
} }
} }
@@ -0,0 +1,207 @@
package com.michatec.radio
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.toColorInt
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText
import com.michatec.radio.helpers.PreferencesHelper
import com.michatec.radio.helpers.ThemeHelper
class CustomThemeFragment : Fragment() {
private lateinit var colorPreview: View
private lateinit var hexCode: TextInputEditText
private lateinit var seekRed: SeekBar
private lateinit var seekGreen: SeekBar
private lateinit var seekBlue: SeekBar
private lateinit var recyclerView: RecyclerView
private var currentColor: Int = Color.BLACK
private var isUpdatingFromHex = false
private fun applyColor(
color: Int
) {
updateSeekBars(color)
updatePreview(color)
}
private val isAndroidTV: Boolean by lazy {
requireContext().packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_custom_theme, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as? AppCompatActivity)?.supportActionBar?.title = getString(R.string.pref_custom_theme_title)
colorPreview = view.findViewById(R.id.color_preview)
hexCode = view.findViewById(R.id.hex_code)
seekRed = view.findViewById(R.id.seek_red)
seekGreen = view.findViewById(R.id.seek_green)
seekBlue = view.findViewById(R.id.seek_blue)
recyclerView = view.findViewById(R.id.color_recycler_view)
currentColor = PreferencesHelper.loadCustomThemeColor(requireContext())
applyColor(currentColor)
val seekBarListener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
val r = seekRed.progress
val g = seekGreen.progress
val b = seekBlue.progress
currentColor = Color.rgb(r, g, b)
updatePreview(currentColor)
PreferencesHelper.saveCustomTheme(currentColor, -1)
(recyclerView.adapter as? ColorAdapter)?.resetSelection()
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
}
seekRed.setOnSeekBarChangeListener(seekBarListener)
seekGreen.setOnSeekBarChangeListener(seekBarListener)
seekBlue.setOnSeekBarChangeListener(seekBarListener)
// Clipboard logic (Non-TV)
if (!isAndroidTV) {
hexCode.setOnClickListener {
copyToClipboard(hexCode.text.toString())
}
hexCode.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (!isUpdatingFromHex) {
try {
val color = s.toString().toColorInt()
currentColor = color
isUpdatingFromHex = true
applyColor(color)
PreferencesHelper.saveCustomTheme(currentColor, -1)
(recyclerView.adapter as? ColorAdapter)?.resetSelection()
isUpdatingFromHex = false
} catch (_: Exception) {}
}
}
override fun afterTextChanged(s: Editable?) {}
})
} else {
hexCode.isFocusable = false
hexCode.isFocusableInTouchMode = false
}
setupRecyclerView()
}
private fun copyToClipboard(text: String) {
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.hex_code), text)
clipboard.setPrimaryClip(clip)
Toast.makeText(requireContext(), R.string.toastmessage_copied_to_clipboard, Toast.LENGTH_SHORT).show()
}
private fun updateSeekBars(color: Int) {
seekRed.progress = Color.red(color)
seekGreen.progress = Color.green(color)
seekBlue.progress = Color.blue(color)
}
private fun updatePreview(color: Int) {
colorPreview.setBackgroundColor(color)
if (!isUpdatingFromHex) {
isUpdatingFromHex = true
hexCode.setText(String.format("#%08X", 0xFFFFFF and color))
isUpdatingFromHex = false
}
}
private fun setupRecyclerView() {
recyclerView.layoutManager = GridLayoutManager(requireContext(), 5)
val colors = ThemeHelper.getPredefinedColors(requireContext())
val adapter = ColorAdapter(colors) { color, index ->
currentColor = color
applyColor(color)
PreferencesHelper.saveCustomTheme(currentColor, index)
}
recyclerView.adapter = adapter
}
private inner class ColorAdapter(
private val colors: List<Int>,
private val onColorSelected: (Int, Int) -> Unit
) : RecyclerView.Adapter<ColorAdapter.ViewHolder>() {
private var selectedPosition: Int = -1
init {
selectedPosition = PreferencesHelper.loadCustomThemeIndex()
}
fun resetSelection() {
val oldPos = selectedPosition
selectedPosition = -1
if (oldPos != -1) notifyItemChanged(oldPos)
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val circle: View = view.findViewById(R.id.color_circle)
init {
view.isFocusable = true
view.isFocusableInTouchMode = isAndroidTV
view.setOnClickListener {
val pos = bindingAdapterPosition
if (pos != RecyclerView.NO_POSITION) {
val oldPos = selectedPosition
selectedPosition = pos
if (oldPos != -1) notifyItemChanged(oldPos)
notifyItemChanged(selectedPosition)
onColorSelected(colors[pos], pos)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.element_color_circle, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val color = colors[position]
val drawable = holder.circle.background as GradientDrawable
drawable.setColor(color)
// Set selection state
holder.itemView.isSelected = (position == selectedPosition)
}
override fun getItemCount() = colors.size
}
}
@@ -92,6 +92,9 @@ object Keys {
const val PREF_PRESET_DRC: String = "PRESET_DRC" const val PREF_PRESET_DRC: String = "PRESET_DRC"
const val PREF_PRESET_STEREO_WIDTH: String = "PRESET_STEREO_WIDTH" const val PREF_PRESET_STEREO_WIDTH: String = "PRESET_STEREO_WIDTH"
const val PREF_LANGUAGE_SELECTED: String = "PRESET_LANGUAGE_SELECTED" const val PREF_LANGUAGE_SELECTED: String = "PRESET_LANGUAGE_SELECTED"
const val PREF_CUSTOM_THEME_COLOR: String = "CUSTOM_THEME_COLOR"
const val PREF_CUSTOM_THEME_ENABLED: String = "CUSTOM_THEME_ENABLED"
const val PREF_CUSTOM_THEME_INDEX: String = "CUSTOM_THEME_INDEX"
// default const values // default const values
const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25 const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25
@@ -1,25 +1,35 @@
package com.michatec.radio package com.michatec.radio
import android.Manifest
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri
import android.util.TypedValue
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.Settings
import android.view.View import android.view.View
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.navigateUp import androidx.navigation.ui.navigateUp
import com.google.android.material.snackbar.Snackbar
import com.michatec.radio.helpers.AppThemeHelper import com.michatec.radio.helpers.AppThemeHelper
import com.michatec.radio.helpers.FileHelper import com.michatec.radio.helpers.FileHelper
import com.michatec.radio.helpers.LanguageHelper import com.michatec.radio.helpers.LanguageHelper
import com.michatec.radio.helpers.PreferencesHelper import com.michatec.radio.helpers.PreferencesHelper
import com.michatec.radio.helpers.ThemeHelper
import org.woheller69.freeDroidWarn.FreeDroidWarn import org.woheller69.freeDroidWarn.FreeDroidWarn
import java.util.Locale import java.util.Locale
@@ -30,6 +40,36 @@ class MainActivity : AppCompatActivity() {
/* Main class variables */ /* Main class variables */
private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var mainRoot: View
// Check if the device running the app is an Android TV instance
private val isAndroidTV: Boolean by lazy {
packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
}
// request notification permission (for Android 13+)
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (!isGranted) {
val snackbar = Snackbar.make(
findViewById(android.R.id.content),
R.string.snackbar_failed_permission_notification,
Snackbar.LENGTH_LONG
)
// If the user permanently denied the permission, show a link to settings
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
snackbar.setAction(R.string.fragment_settings_title) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}
}
snackbar.setAnchorView(findViewById(R.id.bottom_sheet))
snackbar.show()
}
}
/* Overrides attachBaseContext from AppCompatActivity */ /* Overrides attachBaseContext from AppCompatActivity */
override fun attachBaseContext(newBase: Context) { override fun attachBaseContext(newBase: Context) {
@@ -57,6 +97,8 @@ class MainActivity : AppCompatActivity() {
// set up views // set up views
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
mainRoot = findViewById(R.id.main_root)
applyCustomTheme()
// create .nomedia file - if not yet existing // create .nomedia file - if not yet existing
FileHelper.createNomediaFile(getExternalFilesDir(null)) FileHelper.createNomediaFile(getExternalFilesDir(null))
@@ -72,7 +114,7 @@ class MainActivity : AppCompatActivity() {
supportActionBar?.hide() supportActionBar?.hide()
// TV-specific loading logic: Hide the overlay once the app is ready // TV-specific loading logic: Hide the overlay once the app is ready
if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { if (isAndroidTV) {
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
hideLoadingOverlay() hideLoadingOverlay()
}, 1200) }, 1200)
@@ -82,6 +124,11 @@ class MainActivity : AppCompatActivity() {
// register listener for changes in shared preferences // register listener for changes in shared preferences
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
// request permissions
if (!isAndroidTV && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
} }
/* Hides the loading/splash overlay */ /* Hides the loading/splash overlay */
@@ -94,6 +141,33 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun applyCustomTheme() {
val enabled = PreferencesHelper.loadCustomThemeEnabled()
if (enabled) {
var color = PreferencesHelper.loadCustomThemeColor(this)
val index = PreferencesHelper.loadCustomThemeIndex()
if (index != -1) {
// Color belongs to a predefined group. Update it based on current mode.
val colors = ThemeHelper.getPredefinedColors(this)
if (index < colors.size) {
val updatedColor = colors[index]
if (updatedColor != color) {
color = updatedColor
// Save the updated color to keep preferences in sync with the current mode
PreferencesHelper.saveCustomThemeColor(color)
}
}
}
mainRoot.setBackgroundColor(color)
} else {
// Reset to default theme background color
val typedValue = TypedValue()
theme.resolveAttribute(android.R.attr.colorBackground, typedValue, true)
mainRoot.setBackgroundColor(typedValue.data)
}
}
/* Overrides onResume from AppCompatActivity */ /* Overrides onResume from AppCompatActivity */
override fun onResume() { override fun onResume() {
@@ -134,6 +208,9 @@ class MainActivity : AppCompatActivity() {
Keys.PREF_LANGUAGE_SELECTED -> { Keys.PREF_LANGUAGE_SELECTED -> {
LanguageHelper.setLanguage(this, PreferencesHelper.loadSelectedLanguage()) LanguageHelper.setLanguage(this, PreferencesHelper.loadSelectedLanguage())
} }
Keys.PREF_CUSTOM_THEME_COLOR, Keys.PREF_CUSTOM_THEME_ENABLED, Keys.PREF_CUSTOM_THEME_INDEX -> {
applyCustomTheme()
}
} }
} }
/* /*
@@ -0,0 +1,56 @@
package com.michatec.radio
import androidx.core.app.NotificationCompat
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
object NotificationSys {
private const val CHANNEL_ID = "com.michatec.radio.channel_messages"
private const val CHANNEL_NAME = "Notifications"
private const val NOTIFICATION_ID = 5000
fun createNotificationChannel(context: Context) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
).apply {
description = context.getString(R.string.notification_channel_description)
}
notificationManager.createNotificationChannel(channel)
}
}
fun showNotification(context: Context, title: String, content: String, intent: Intent? = null, id: Int = NOTIFICATION_ID) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
createNotificationChannel(context)
val targetIntent = intent ?: Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
}
val pendingIntent = PendingIntent.getActivity(
context,
0,
targetIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_app_icon_white_24dp)
.setContentTitle(title)
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
notificationManager.notify(id, notification)
}
}
@@ -1,5 +1,6 @@
package com.michatec.radio package com.michatec.radio
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
@@ -95,6 +96,15 @@ class PlayerFragment : Fragment(),
private var tempStationUuid: String = String() private var tempStationUuid: String = String()
private var itemTouchHelper: ItemTouchHelper? = null private var itemTouchHelper: ItemTouchHelper? = null
// Check if the device running the app is an Android TV instance
private val isAndroidTV: Boolean by lazy {
context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_LEANBACK) == true
}
private fun isPermissionGranted(context: Context, permission: String): Boolean {
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
}
/* Overrides onCreate from Fragment */ /* Overrides onCreate from Fragment */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -139,7 +149,11 @@ class PlayerFragment : Fragment(),
pickSingleMediaLauncher = pickSingleMediaLauncher =
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { imageUri -> registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { imageUri ->
if (imageUri == null) { if (imageUri == null) {
Snackbar.make(requireView(), R.string.toastalert_failed_picking_media, Snackbar.LENGTH_LONG).show() val snackbar = Snackbar.make(requireView(), R.string.toastalert_failed_picking_media, Snackbar.LENGTH_LONG)
if (!isAndroidTV) {
snackbar.setAnchorView(layout.bottomSheet)
}
snackbar.show()
} else { } else {
collection = CollectionHelper.setStationImageWithStationUuid( collection = CollectionHelper.setStationImageWithStationUuid(
activity as Context, activity as Context,
@@ -294,6 +308,9 @@ class PlayerFragment : Fragment(),
if (key == Keys.PREF_PLAYER_METADATA_HISTORY) { if (key == Keys.PREF_PLAYER_METADATA_HISTORY) {
requestMetadataUpdate() requestMetadataUpdate()
} }
if (key == Keys.PREF_CUSTOM_THEME_COLOR || key == Keys.PREF_CUSTOM_THEME_ENABLED || key == Keys.PREF_CUSTOM_THEME_INDEX) {
layout.applyCustomTheme(activity as Context)
}
} }
@@ -490,11 +507,17 @@ class PlayerFragment : Fragment(),
// display the TimePicker dialog // display the TimePicker dialog
timePicker.show(requireActivity().supportFragmentManager, "tag") timePicker.show(requireActivity().supportFragmentManager, "tag")
} }
else -> Snackbar.make( else -> {
requireView(), val snackbar = Snackbar.make(
R.string.toastmessage_sleep_timer_unable_to_start, requireView(),
Snackbar.LENGTH_SHORT R.string.toastmessage_sleep_timer_unable_to_start,
).show() Snackbar.LENGTH_SHORT
)
if (!isAndroidTV) {
snackbar.setAnchorView(layout.bottomSheet)
}
snackbar.show()
}
} }
} }
@@ -831,10 +854,10 @@ class PlayerFragment : Fragment(),
} else { } else {
activity?.packageManager?.getPackageInfo(requireActivity().packageName, 0)?.versionName activity?.packageManager?.getPackageInfo(requireActivity().packageName, 0)?.versionName
} }
if (latestVersion != current) { if (latestVersion != current && !BuildConfig.IS_DEBUG_ENABLED) {
// We have an update available, tell our user about it // We have an update available, tell our user about it
view?.let { view?.let {
Snackbar.make(it, getString(R.string.app_name) + " " + latestVersion + " " + getString(R.string.snackbar_update_available), 10000) val snackbar = Snackbar.make(it, getString(R.string.app_name) + " " + latestVersion + " " + getString(R.string.snackbar_update_available), 10000)
.setAction(R.string.snackbar_show) { .setAction(R.string.snackbar_show) {
val releaseurl = getString(R.string.snackbar_url_app_home_page) val releaseurl = getString(R.string.snackbar_url_app_home_page)
val i = Intent(Intent.ACTION_VIEW) val i = Intent(Intent.ACTION_VIEW)
@@ -847,7 +870,24 @@ class PlayerFragment : Fragment(),
ContextCompat.getColor( ContextCompat.getColor(
requireActivity(), requireActivity(),
R.color.default_neutral_white)) R.color.default_neutral_white))
.show()
if (!isAndroidTV) {
snackbar.setAnchorView(layout.bottomSheet)
}
snackbar.show()
}
if (!isAndroidTV && isPermissionGranted(requireContext(), Manifest.permission.POST_NOTIFICATIONS)) {
val releaseUrl = getString(R.string.snackbar_url_app_home_page)
val updateIntent = Intent(Intent.ACTION_VIEW, releaseUrl.toUri()).apply {
putExtra("SOURCE", "SELF")
}
NotificationSys.showNotification(
requireContext(),
"${getString(R.string.app_name)} $latestVersion",
getString(R.string.snackbar_update_available),
intent = updateIntent,
id = 1002
)
} }
} }
}, { error -> }, { error ->
@@ -9,7 +9,6 @@ import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
import android.util.Log import android.util.Log
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT
import androidx.media3.cast.CastPlayer import androidx.media3.cast.CastPlayer
import androidx.media3.common.* import androidx.media3.common.*
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@@ -29,6 +28,7 @@ import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.michatec.radio.core.Collection import com.michatec.radio.core.Collection
import com.michatec.radio.core.Station
import com.michatec.radio.helpers.* import com.michatec.radio.helpers.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
@@ -132,7 +132,7 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
context: Context, context: Context,
enableFloatOutput: Boolean, enableFloatOutput: Boolean,
enableAudioTrackPlaybackParams: Boolean enableAudioTrackPlaybackParams: Boolean
): AudioSink? { ): AudioSink {
return DefaultAudioSink.Builder(context) return DefaultAudioSink.Builder(context)
.setAudioProcessors(arrayOf(nativeAudioProcessor)) .setAudioProcessors(arrayOf(nativeAudioProcessor))
.build() .build()
@@ -170,6 +170,14 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
override fun getDuration(): Long { override fun getDuration(): Long {
return C.TIME_UNSET // this will hide progress bar for HLS stations in the notification return C.TIME_UNSET // this will hide progress bar for HLS stations in the notification
} }
override fun seekToNext() {
playNextStation()
}
override fun seekToPrevious() {
playPreviousStation()
}
} }
player.addListener(playerListener) player.addListener(playerListener)
} }
@@ -347,6 +355,37 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
} }
/* Switches to the next radio station in collection */
private fun playNextStation() {
val currentMediaId = player.currentMediaItem?.mediaId ?: PreferencesHelper.loadLastPlayedStationUuid()
val currentPosition = CollectionHelper.getStationPosition(collection, currentMediaId)
if (currentPosition != -1) {
val nextPosition = if (currentPosition < collection.stations.size - 1) currentPosition + 1 else 0
playStation(collection.stations[nextPosition])
}
}
/* Switches to the previous radio station in collection */
private fun playPreviousStation() {
val currentMediaId = player.currentMediaItem?.mediaId ?: PreferencesHelper.loadLastPlayedStationUuid()
val currentPosition = CollectionHelper.getStationPosition(collection, currentMediaId)
if (currentPosition != -1) {
val previousPosition = if (currentPosition > 0) currentPosition - 1 else collection.stations.size - 1
playStation(collection.stations[previousPosition])
}
}
/* Starts playback of a radio station */
private fun playStation(station: Station) {
val mediaItem = CollectionHelper.buildMediaItem(this, station)
player.setMediaItem(mediaItem)
player.prepare()
player.play()
}
/* /*
* Custom MediaSession Callback that handles player commands * Custom MediaSession Callback that handles player commands
*/ */
@@ -407,8 +446,8 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
browser: MediaSession.ControllerInfo, browser: MediaSession.ControllerInfo,
params: LibraryParams? params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> { ): ListenableFuture<LibraryResult<MediaItem>> {
return if (params?.extras?.containsKey(EXTRA_RECENT) == true) { return if (params?.isRecent == true) {
// special case: system requested media resumption via EXTRA_RECENT // special case: system requested media resumption via isRecent
playLastStation = true playLastStation = true
Futures.immediateFuture(LibraryResult.ofItem(CollectionHelper.getRecent(this@PlayerService, collection), params)) Futures.immediateFuture(LibraryResult.ofItem(CollectionHelper.getRecent(this@PlayerService, collection), params))
} else { } else {
@@ -552,8 +591,7 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
stopSelf() stopSelf()
} }
Player.STATE_READY -> { Player.STATE_READY -> {
// Playback is paused. For radio, we can stop the service to remove the notification. // Playback is paused. For radio, we keep the service running to allow resumption from headphones.
stopSelf()
} }
Player.STATE_BUFFERING -> { Player.STATE_BUFFERING -> {
// DO NOT stop the service while buffering (especially important for Cast) // DO NOT stop the service while buffering (especially important for Cast)
@@ -562,17 +600,6 @@ class PlayerService : MediaLibraryService(), SharedPreferences.OnSharedPreferenc
} }
} }
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
super.onPlayWhenReadyChanged(playWhenReady, reason)
if (!playWhenReady) {
// Only stop if not buffering and not ready to play (i.e. truly stopped/paused)
if (player.playbackState != Player.STATE_BUFFERING) {
stopSelf()
}
}
}
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error) super.onPlayerError(error)
Log.d(TAG, "PlayerError occurred: ${error.errorCodeName}") Log.d(TAG, "PlayerError occurred: ${error.errorCodeName}")
@@ -22,6 +22,7 @@ import com.michatec.radio.dialogs.PresetSelectionDialog
import com.michatec.radio.dialogs.ThemeSelectionDialog import com.michatec.radio.dialogs.ThemeSelectionDialog
import com.michatec.radio.dialogs.YesNoDialog import com.michatec.radio.dialogs.YesNoDialog
import com.michatec.radio.helpers.* import com.michatec.radio.helpers.*
import android.content.pm.PackageManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -38,6 +39,15 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
/* Define log tag */ /* Define log tag */
private val TAG: String = SettingsFragment::class.java.simpleName private val TAG: String = SettingsFragment::class.java.simpleName
// Check if the device running the app is an Android TV instance
private val isAndroidTV: Boolean by lazy {
context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_LEANBACK) == true
}
private fun isPermissionGranted(context: Context, permission: String): Boolean {
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
}
/* Overrides onViewCreated from PreferenceFragmentCompat */ /* Overrides onViewCreated from PreferenceFragmentCompat */
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -149,7 +159,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
// set up "Buffer Size" preference // set up "Buffer Size" preference
val preferenceBufferSize = SwitchPreferenceCompat(activity as Context) val preferenceBufferSize = MarqueeSwitchPreference(context)
preferenceBufferSize.title = getString(R.string.pref_buffer_size_title) preferenceBufferSize.title = getString(R.string.pref_buffer_size_title)
preferenceBufferSize.setIcon(R.drawable.ic_network_check_24dp) preferenceBufferSize.setIcon(R.drawable.ic_network_check_24dp)
preferenceBufferSize.key = Keys.PREF_LARGE_BUFFER_SIZE preferenceBufferSize.key = Keys.PREF_LARGE_BUFFER_SIZE
@@ -159,7 +169,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
// set up "Edit Stream Address" preference // set up "Edit Stream Address" preference
val preferenceEnableEditingStreamUri = SwitchPreferenceCompat(activity as Context) val preferenceEnableEditingStreamUri = MarqueeSwitchPreference(context)
preferenceEnableEditingStreamUri.title = getString(R.string.pref_edit_station_stream_title) preferenceEnableEditingStreamUri.title = getString(R.string.pref_edit_station_stream_title)
preferenceEnableEditingStreamUri.setIcon(R.drawable.ic_music_note_24dp) preferenceEnableEditingStreamUri.setIcon(R.drawable.ic_music_note_24dp)
preferenceEnableEditingStreamUri.key = Keys.PREF_EDIT_STREAMS_URIS preferenceEnableEditingStreamUri.key = Keys.PREF_EDIT_STREAMS_URIS
@@ -174,7 +184,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
// set up "Edit Stations" preference // set up "Edit Stations" preference
val preferenceEnableEditingGeneral = SwitchPreferenceCompat(activity as Context) val preferenceEnableEditingGeneral = MarqueeSwitchPreference(context)
preferenceEnableEditingGeneral.title = getString(R.string.pref_edit_station_title) preferenceEnableEditingGeneral.title = getString(R.string.pref_edit_station_title)
preferenceEnableEditingGeneral.setIcon(R.drawable.ic_edit_24dp) preferenceEnableEditingGeneral.setIcon(R.drawable.ic_edit_24dp)
preferenceEnableEditingGeneral.key = Keys.PREF_EDIT_STATIONS preferenceEnableEditingGeneral.key = Keys.PREF_EDIT_STATIONS
@@ -255,6 +265,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
// set up "Visualizer" preference entry
val preferenceVisualizer = Preference(context) val preferenceVisualizer = Preference(context)
preferenceVisualizer.title = getString(R.string.pref_visualizer_title) preferenceVisualizer.title = getString(R.string.pref_visualizer_title)
preferenceVisualizer.setIcon(R.drawable.ic_visualizer_24dp) preferenceVisualizer.setIcon(R.drawable.ic_visualizer_24dp)
@@ -309,6 +320,38 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
// set up "Test Notification" preference
val preferenceTestNotification = Preference(context)
preferenceTestNotification.title = getString(R.string.pref_test_notification_title)
preferenceTestNotification.setIcon(R.drawable.ic_notification_app_icon_24dp)
preferenceTestNotification.summary = getString(R.string.pref_test_notification_summary)
preferenceTestNotification.setOnPreferenceClickListener {
// show test notification
NotificationSys.showNotification(
context,
getString(R.string.pref_test_notification_title),
getString(R.string.notification_test_content),
id = 1004
)
return@setOnPreferenceClickListener true
}
// set up "Security" preference
val preferenceSecurity = Preference(context)
preferenceSecurity.title = getString(R.string.pref_security_title)
preferenceSecurity.setIcon(R.drawable.ic_security_24dp)
preferenceSecurity.summary = getString(R.string.pref_security_summary)
preferenceSecurity.setOnPreferenceClickListener {
// open web browser
val intent = Intent().apply {
action = Intent.ACTION_VIEW
data = "https://github.com/michatec/Radio/blob/master/SECURITY.md".toUri()
}
startActivity(intent)
return@setOnPreferenceClickListener true
}
// set up "Language Selection" preference
val preferenceLanguageSelection = Preference(context) val preferenceLanguageSelection = Preference(context)
preferenceLanguageSelection.title = getString(R.string.pref_language_selection_title) preferenceLanguageSelection.title = getString(R.string.pref_language_selection_title)
preferenceLanguageSelection.setIcon(R.drawable.ic_language_24dp) preferenceLanguageSelection.setIcon(R.drawable.ic_language_24dp)
@@ -321,6 +364,62 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
// set up "Custom Theme" preference
val preferenceCustomTheme = Preference(context)
preferenceCustomTheme.title = getString(R.string.pref_custom_theme_title)
preferenceCustomTheme.setIcon(R.drawable.ic_rbrush_24dp)
preferenceCustomTheme.summary = getString(R.string.pref_custom_theme_summary)
preferenceCustomTheme.isEnabled = PreferencesHelper.loadCustomThemeEnabled()
preferenceCustomTheme.setOnPreferenceClickListener {
findNavController().navigate(R.id.action_settings_to_cstheme)
return@setOnPreferenceClickListener true
}
// set up "Custom Theme Enabled" preference
val preferenceCustomThemeEnabled = MarqueeSwitchPreference(context)
preferenceCustomThemeEnabled.title = getString(R.string.pref_custom_theme_enabled_title)
preferenceCustomThemeEnabled.setIcon(R.drawable.ic_rbrush_24dp)
preferenceCustomThemeEnabled.summaryOn = getString(R.string.pref_custom_theme_enabled_summary)
preferenceCustomThemeEnabled.summaryOff = getString(R.string.pref_custom_theme_disabled_summary)
preferenceCustomThemeEnabled.key = Keys.PREF_CUSTOM_THEME_ENABLED
preferenceCustomThemeEnabled.setDefaultValue(PreferencesHelper.loadCustomThemeEnabled())
preferenceCustomThemeEnabled.setOnPreferenceChangeListener { _, newValue ->
when (newValue) {
true -> {
// enable custom theme
preferenceCustomTheme.isEnabled = true
}
false -> {
// disable custom theme
preferenceCustomTheme.isEnabled = false
}
}
return@setOnPreferenceChangeListener true
}
// set up "Share the App" preference
val preferenceShareApp = Preference(context)
preferenceShareApp.title = getString(R.string.pref_share_app_title)
preferenceShareApp.setIcon(R.drawable.ic_share_24dp)
preferenceShareApp.summary = getString(R.string.pref_share_app_summary)
preferenceShareApp.setOnPreferenceClickListener {
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.app_name))
putExtra(Intent.EXTRA_TEXT, getString(R.string.pref_share_app_share_text))
}
startActivity(shareIntent)
if (!isAndroidTV && isPermissionGranted(activity as Context, android.Manifest.permission.POST_NOTIFICATIONS)) {
NotificationSys.showNotification(
context,
getString(R.string.pref_share_app_thank_title),
getString(R.string.pref_share_app_thank_message),
id = 1003
)
}
return@setOnPreferenceClickListener true
}
// set preference categories // set preference categories
val preferenceCategoryGeneral = PreferenceCategory(activity as Context) val preferenceCategoryGeneral = PreferenceCategory(activity as Context)
@@ -344,10 +443,17 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
// setup preference screen // setup preference screen
screen.addPreference(preferenceAppVersion) screen.addPreference(preferenceAppVersion)
screen.addPreference(preferenceShareApp)
screen.addPreference(preferenceCategoryGeneral) screen.addPreference(preferenceCategoryGeneral)
preferenceCategoryGeneral.addPreference(preferenceThemeSelection) preferenceCategoryGeneral.addPreference(preferenceThemeSelection)
preferenceCategoryGeneral.addPreference(preferenceLanguageSelection) preferenceCategoryGeneral.addPreference(preferenceLanguageSelection)
preferenceCategoryGeneral.addPreference(preferenceCustomThemeEnabled)
preferenceCategoryGeneral.addPreference(preferenceCustomTheme)
if (!isAndroidTV && isPermissionGranted(activity as Context, android.Manifest.permission.POST_NOTIFICATIONS)) {
preferenceCategoryGeneral.addPreference(preferenceTestNotification)
}
screen.addPreference(preferenceCategoryAudioEffects) screen.addPreference(preferenceCategoryAudioEffects)
preferenceCategoryAudioEffects.addPreference(preferenceBassBoost) preferenceCategoryAudioEffects.addPreference(preferenceBassBoost)
@@ -375,6 +481,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
screen.addPreference(preferenceCategoryLinks) screen.addPreference(preferenceCategoryLinks)
preferenceCategoryLinks.addPreference(preferenceGitHub) preferenceCategoryLinks.addPreference(preferenceGitHub)
preferenceCategoryLinks.addPreference(preferenceLicense) preferenceCategoryLinks.addPreference(preferenceLicense)
preferenceCategoryLinks.addPreference(preferenceSecurity)
preferenceScreen = screen preferenceScreen = screen
} }
@@ -39,6 +39,7 @@ class LanguageSelectionDialog(private var languageSelectionDialogListener: Langu
Language("de", R.string.pref_language_de), Language("de", R.string.pref_language_de),
Language("fr", R.string.pref_language_fr), Language("fr", R.string.pref_language_fr),
Language("ru", R.string.pref_language_ru), Language("ru", R.string.pref_language_ru),
Language("uk", R.string.pref_language_uk),
Language("ja", R.string.pref_language_ja), Language("ja", R.string.pref_language_ja),
Language("nl", R.string.pref_language_nl), Language("nl", R.string.pref_language_nl),
Language("pl", R.string.pref_language_pl), Language("pl", R.string.pref_language_pl),
@@ -11,6 +11,7 @@ import android.widget.FrameLayout
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import com.michatec.radio.R import com.michatec.radio.R
import androidx.core.view.isEmpty
class ExtrasHelper { class ExtrasHelper {
companion object { companion object {
@@ -69,7 +70,7 @@ class ExtrasHelper {
if (currentParent != container) { if (currentParent != container) {
currentParent?.removeView(visualizerView) currentParent?.removeView(visualizerView)
// If we injected into a standard preference, don't clear everything, just add // If we injected into a standard preference, don't clear everything, just add
if (container is FrameLayout || container.childCount == 0) { if (container is FrameLayout || container.isEmpty()) {
container.removeAllViews() container.removeAllViews()
} }
container.addView(visualizerView) container.addView(visualizerView)
@@ -2,7 +2,6 @@ package com.michatec.radio.helpers
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.res.Configuration
import android.util.Log import android.util.Log
import com.michatec.radio.R import com.michatec.radio.R
import java.util.Locale import java.util.Locale
@@ -16,7 +16,7 @@ class MarqueeSwitchPreference(context: Context) : SwitchPreferenceCompat(context
val title = holder.findViewById(android.R.id.title) as? TextView val title = holder.findViewById(android.R.id.title) as? TextView
title?.apply { title?.apply {
ellipsize = TextUtils.TruncateAt.MARQUEE ellipsize = TextUtils.TruncateAt.MARQUEE
setSingleLine(true) isSingleLine = true
marqueeRepeatLimit = -1 // Repeat indefinitely marqueeRepeatLimit = -1 // Repeat indefinitely
isSelected = true // Required for marquee to start isSelected = true // Required for marquee to start
setHorizontallyScrolling(true) setHorizontallyScrolling(true)
@@ -4,12 +4,14 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.util.Log import android.util.Log
import android.util.TypedValue
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.gson.Gson import com.google.gson.Gson
import com.michatec.radio.Keys import com.michatec.radio.Keys
import com.michatec.radio.ui.PlayerState import com.michatec.radio.ui.PlayerState
import java.util.* import java.util.Calendar
import java.util.Date
/* /*
@@ -362,4 +364,33 @@ object PreferencesHelper {
} }
} }
/* Loads custom theme color */
fun loadCustomThemeColor(context: Context): Int {
val typedValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.colorBackground, typedValue, true)
return sharedPreferences.getInt(Keys.PREF_CUSTOM_THEME_COLOR, typedValue.data)
}
/* Saves custom theme color */
fun saveCustomThemeColor(color: Int) {
sharedPreferences.edit { putInt(Keys.PREF_CUSTOM_THEME_COLOR, color) }
}
/* Loads custom theme index (predefined color index) */
fun loadCustomThemeIndex(): Int {
return sharedPreferences.getInt(Keys.PREF_CUSTOM_THEME_INDEX, -1)
}
/* Saves custom theme color and index together */
fun saveCustomTheme(color: Int, index: Int) {
sharedPreferences.edit {
putInt(Keys.PREF_CUSTOM_THEME_COLOR, color)
putInt(Keys.PREF_CUSTOM_THEME_INDEX, index)
}
}
/* Loads whether custom theme is enabled */
fun loadCustomThemeEnabled(): Boolean {
return sharedPreferences.getBoolean(Keys.PREF_CUSTOM_THEME_ENABLED, false)
}
} }
@@ -0,0 +1,40 @@
package com.michatec.radio.helpers
import android.content.Context
import android.content.res.Configuration
import androidx.core.graphics.toColorInt
object ThemeHelper {
fun getPredefinedColors(context: Context): List<Int> {
val isDarkMode = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
return if (isDarkMode) {
// Darker colors for dark mode background
listOf(
"#FF1D3E66".toColorInt(), // Default Dark
"#FF3E1D1D".toColorInt(), // Red Dark
"#FF1D3E3E".toColorInt(), // Teal Dark
"#FF3E1D2E".toColorInt(), // Pink Dark
"#FF001A33".toColorInt(), // Dark Blue Dark
"#FF1D3E1D".toColorInt(), // Green Dark
"#FF3E2E1D".toColorInt(), // Orange Dark
"#FF2E1D1D".toColorInt(), // Brown Dark
"#FF1D242E".toColorInt(), // Blue Grey Dark
"#FF000000".toColorInt() // Black
)
} else {
// Lighter colors for light mode background
listOf(
"#FFDAE2FF".toColorInt(), // Light Default
"#FFFF897D".toColorInt(), // Light Red
"#FF4DB6AC".toColorInt(), // Light Teal
"#FFF48FB1".toColorInt(), // Light Pink
"#FF90CAF9".toColorInt(), // Light Blue
"#FFA5D6A7".toColorInt(), // Light Green
"#FFFFAB91".toColorInt(), // Light Orange
"#FFBCAAA4".toColorInt(), // Light Brown
"#FFB0BEC5".toColorInt(), // Light Blue Grey
"#FFFFFFFF".toColorInt() // White
)
}
}
}
@@ -171,7 +171,7 @@ class SearchResultAdapter(
context: Context, context: Context,
enableFloatOutput: Boolean, enableFloatOutput: Boolean,
enableAudioTrackPlaybackParams: Boolean enableAudioTrackPlaybackParams: Boolean
): AudioSink? { ): AudioSink {
return DefaultAudioSink.Builder(context) return DefaultAudioSink.Builder(context)
.setAudioProcessors(arrayOf(nativeAudioProcessor)) .setAudioProcessors(arrayOf(nativeAudioProcessor))
.build() .build()
@@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.AnimatedVectorDrawable
import java.util.Locale
import android.view.View import android.view.View
import android.view.animation.Animation import android.view.animation.Animation
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
@@ -31,7 +30,9 @@ import com.michatec.radio.core.Station
import com.michatec.radio.helpers.DateTimeHelper import com.michatec.radio.helpers.DateTimeHelper
import com.michatec.radio.helpers.ImageHelper import com.michatec.radio.helpers.ImageHelper
import com.michatec.radio.helpers.PreferencesHelper import com.michatec.radio.helpers.PreferencesHelper
import com.michatec.radio.helpers.ThemeHelper
import com.michatec.radio.helpers.UiHelper import com.michatec.radio.helpers.UiHelper
import java.util.Locale
/* /*
@@ -42,7 +43,7 @@ data class LayoutHolder(var rootView: View) {
/* Main class variables */ /* Main class variables */
var recyclerView: RecyclerView = rootView.findViewById(R.id.station_list) var recyclerView: RecyclerView = rootView.findViewById(R.id.station_list)
val layoutManager: LinearLayoutManager val layoutManager: LinearLayoutManager
private var bottomSheet: ConstraintLayout? = rootView.findViewById(R.id.bottom_sheet) var bottomSheet: ConstraintLayout? = rootView.findViewById(R.id.bottom_sheet)
//private var sheetMetadataViews: Group //private var sheetMetadataViews: Group
private var sleepTimerRunningViews: Group? = rootView.findViewById(R.id.sleep_timer_running_views) private var sleepTimerRunningViews: Group? = rootView.findViewById(R.id.sleep_timer_running_views)
@@ -122,6 +123,9 @@ data class LayoutHolder(var rootView: View) {
CastButtonFactory.setUpMediaRouteButton(rootView.context, it) CastButtonFactory.setUpMediaRouteButton(rootView.context, it)
} }
// Apply custom theme color
applyCustomTheme(rootView.context)
// set layout for player // set layout for player
setupBottomSheet() setupBottomSheet()
} }
@@ -182,6 +186,9 @@ data class LayoutHolder(var rootView: View) {
// update bitrate // update bitrate
sheetBitrateView?.text = bitrateText sheetBitrateView?.text = bitrateText
// update custom theme
applyCustomTheme(context)
// update click listeners // update click listeners
sheetStreamingLinkHeadline?.setOnClickListener { sheetStreamingLinkHeadline?.setOnClickListener {
copyToClipboard( copyToClipboard(
@@ -309,6 +316,28 @@ data class LayoutHolder(var rootView: View) {
isBuffering = buffering isBuffering = buffering
} }
/* Applies custom theme color to the UI */
fun applyCustomTheme(context: Context) {
val enabled = PreferencesHelper.loadCustomThemeEnabled()
if (enabled) {
var customColor = PreferencesHelper.loadCustomThemeColor(context)
val index = PreferencesHelper.loadCustomThemeIndex()
if (index != -1) {
val colors = ThemeHelper.getPredefinedColors(context)
if (index < colors.size) {
customColor = colors[index]
}
}
rootView.setBackgroundColor(customColor)
recyclerView.setBackgroundColor(customColor)
} else {
rootView.setBackgroundResource(android.R.color.transparent)
recyclerView.setBackgroundResource(android.R.color.transparent)
}
}
/* Toggles visibility of the download progress indicator */ /* Toggles visibility of the download progress indicator */
fun toggleDownloadProgressIndicator() { fun toggleDownloadProgressIndicator() {
when (PreferencesHelper.loadActiveDownloads()) { when (PreferencesHelper.loadActiveDownloads()) {
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path
android:fillColor="@color/icon_default"
android:pathData="M7,14c-1.66,0 -3,1.34 -3,3 0,1.31 -1.16,2 -2,2 0.92,1.22 2.49,2 4,2 2.21,0 4,-1.79 4,-4 0,-1.66 -1.34,-3 -3,-3zM20.71,4.63l-1.34,-1.34c-0.39,-0.39 -1.02,-0.39 -1.41,0L9,12.25 11.75,15l8.96,-8.96c0.39,-0.39 0.39,-1.02 0,-1.41z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/icon_default"
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12c5.16-1.26 9-6.45 9-12V5l-9,-4z"/>
</vector>
+1 -1
View File
@@ -4,6 +4,6 @@
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="@color/player_sheet_icon" android:fillColor="@color/icon_default"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92 -1.31,-2.92 -2.92,-2.92z" /> android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92 -1.31,-2.92 -2.92,-2.92z" />
</vector> </vector>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Highlight when focused (TV navigation) -->
<item android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="#44888888" />
<stroke android:width="3dp" android:color="?attr/colorPrimary" />
<corners android:radius="12dp" />
</shape>
</item>
<!-- Highlight when selected (Active color) -->
<item android:state_selected="true">
<shape android:shape="rectangle">
<stroke android:width="3dp" android:color="?attr/colorSecondary" />
<corners android:radius="12dp" />
</shape>
</item>
<item android:drawable="@android:color/transparent" />
</selector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/white" />
<stroke android:width="2dp" android:color="#CCCCCC" />
</shape>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="3dp" android:color="#000000" />
<padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp" />
<corners android:radius="8dp" />
</shape>
@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:orientation="horizontal"
android:padding="24dp">
<!-- LEFT SIDE: CONTROLS -->
<androidx.core.widget.NestedScrollView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.5">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/predefined_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/theme_predefined_colors"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/color_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="5"
tools:listitem="@layout/element_color_circle" />
<TextView
android:id="@+id/custom_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/theme_custom_rgb"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge" />
<LinearLayout
android:id="@+id/rgb_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/shape_rgb_controls_border"
android:orientation="vertical">
<TextView
android:id="@+id/red_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/theme_red"
android:textSize="18sp" />
<SeekBar
android:id="@+id/seek_red"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:max="255"
android:focusable="true" />
<TextView
android:id="@+id/green_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/theme_green"
android:textSize="18sp" />
<SeekBar
android:id="@+id/seek_green"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:max="255"
android:focusable="true" />
<TextView
android:id="@+id/blue_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/theme_blue"
android:textSize="18sp" />
<SeekBar
android:id="@+id/seek_blue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:max="255"
android:focusable="true" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- RIGHT SIDE: PREVIEW & APPLY -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:padding="24dp">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_rgb_controls_border">
<View
android:id="@+id/color_preview"
android:layout_width="180dp"
android:layout_height="180dp"
android:background="@android:color/black" />
</FrameLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/hex_input_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/hex_code"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="textCapCharacters"
android:maxLength="9"
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
tools:text="#FF000000" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
@@ -2,9 +2,11 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:attr/colorBackground" android:background="?android:attr/colorBackground"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".MainActivity"> tools:context=".MainActivity">
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="4dp"
android:focusable="true"
android:clickable="true"
android:background="@drawable/selector_color_circle">
<View
android:id="@+id/color_circle"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/shape_color_circle" />
</FrameLayout>
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:padding="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/predefined_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/theme_predefined_colors"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/color_recycler_view"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/color_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/predefined_label"
tools:listitem="@layout/element_color_circle" />
<TextView
android:id="@+id/custom_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/theme_custom_rgb"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/color_recycler_view" />
<LinearLayout
android:id="@+id/rgb_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/shape_rgb_controls_border"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/custom_label">
<TextView
android:id="@+id/red_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/theme_red" />
<SeekBar
android:id="@+id/seek_red"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:focusable="true" />
<TextView
android:id="@+id/green_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/theme_green" />
<SeekBar
android:id="@+id/seek_green"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:focusable="true" />
<TextView
android:id="@+id/blue_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/theme_blue" />
<SeekBar
android:id="@+id/seek_blue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:focusable="true" />
</LinearLayout>
<FrameLayout
android:id="@+id/color_preview_border"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:background="@drawable/shape_rgb_controls_border"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rgb_controls">
<View
android:id="@+id/color_preview"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@android:color/black" />
</FrameLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/hex_input_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/color_preview_border">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/hex_code"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="textCapCharacters"
android:maxLength="9"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
tools:text="#FF000000" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
@@ -32,6 +32,9 @@
<action <action
android:id="@+id/action_settings_to_visualizer" android:id="@+id/action_settings_to_visualizer"
app:destination="@id/visualizer_destination" /> app:destination="@id/visualizer_destination" />
<action
android:id="@+id/action_settings_to_cstheme"
app:destination="@id/custom_theme_destination" />
</fragment> </fragment>
<!-- EQUALIZER --> <!-- EQUALIZER -->
@@ -52,4 +55,11 @@
android:name="com.michatec.radio.AddStationFragment" android:name="com.michatec.radio.AddStationFragment"
android:label="Add Station" android:label="Add Station"
tools:layout="@layout/dialog_find_station" /> tools:layout="@layout/dialog_find_station" />
<!-- CUSTOM THEME -->
<fragment
android:id="@+id/custom_theme_destination"
android:name="com.michatec.radio.CustomThemeFragment"
android:label="Custom Theme"
tools:layout="@layout/fragment_custom_theme" />
</navigation> </navigation>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">Stop</string> <string name="notification_stop">Stop</string>
<string name="notification_skip_to_previous">Forrige</string> <string name="notification_skip_to_previous">Forrige</string>
<string name="notification_skip_to_next">Næste</string> <string name="notification_skip_to_next">Næste</string>
<string name="notification_test_content">Dette er en testmeddelelse.</string>
<string name="notification_channel_description">Alle meddelelser om appen.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Fordyb dig i lyden du elsker!</string> <string name="onboarding_app_description">Fordyb dig i lyden du elsker!</string>
<string name="onboarding_app_get_started">Kom i gang</string> <string name="onboarding_app_get_started">Kom i gang</string>
@@ -84,6 +86,8 @@
<string name="pref_theme_selection_title">App-tema</string> <string name="pref_theme_selection_title">App-tema</string>
<string name="pref_update_station_images_summary">Download nyeste stationsbilleder.</string> <string name="pref_update_station_images_summary">Download nyeste stationsbilleder.</string>
<string name="pref_update_station_images_title">Opdater stationsbilleder</string> <string name="pref_update_station_images_title">Opdater stationsbilleder</string>
<string name="pref_test_notification_title">Testmeddelelse</string>
<string name="pref_test_notification_summary">Test om meddelelsessystemet virker.</string>
<!-- App-genveje --> <!-- App-genveje -->
<string name="shortcut_last_station_disabled_message">Genvej til seneste station er deaktiveret.</string> <string name="shortcut_last_station_disabled_message">Genvej til seneste station er deaktiveret.</string>
<string name="shortcut_last_station_long_label">Afspil seneste station</string> <string name="shortcut_last_station_long_label">Afspil seneste station</string>
@@ -111,6 +115,7 @@
<!-- Snackbars --> <!-- Snackbars -->
<string name="snackbar_show">Vis</string> <string name="snackbar_show">Vis</string>
<string name="snackbar_update_available">er tilgængelig!</string> <string name="snackbar_update_available">er tilgængelig!</string>
<string name="snackbar_failed_permission_notification">Kunne ikke anmode om meddelelsestilladelse.</string>
<!-- Language Selection --> <!-- Language Selection -->
<string name="pref_language_selection_title">Sprog</string> <string name="pref_language_selection_title">Sprog</string>
<string name="pref_language_selection_summary">Aktuelt sprog</string> <string name="pref_language_selection_summary">Aktuelt sprog</string>
@@ -123,6 +128,8 @@
<string name="pref_audio_effects_title">Lydeffekter</string> <string name="pref_audio_effects_title">Lydeffekter</string>
<string name="pref_bass_boost_title">Bas-forstærkning</string> <string name="pref_bass_boost_title">Bas-forstærkning</string>
<string name="pref_bass_boost_summary">Øg basforstærkningen.</string> <string name="pref_bass_boost_summary">Øg basforstærkningen.</string>
<string name="pref_security_title">Sikkerhed</string>
<string name="pref_security_summary">Lær mere om sikkerheden for denne applikation</string>
<string name="pref_reverb_title">Hall</string> <string name="pref_reverb_title">Hall</string>
<string name="pref_reverb_summary">Juster hall-blanding.</string> <string name="pref_reverb_summary">Juster hall-blanding.</string>
<string name="pref_drc_title">Dynamisk rækkeviddekomprimering</string> <string name="pref_drc_title">Dynamisk rækkeviddekomprimering</string>
@@ -152,4 +159,23 @@
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="pref_visualizer_title">Spektrumanalysator</string> <string name="pref_visualizer_title">Spektrumanalysator</string>
<string name="pref_visualizer_summary">Vis spektrumanalysatoren.</string> <string name="pref_visualizer_summary">Vis spektrumanalysatoren.</string>
<string name="pref_share_app_title">Del app</string>
<string name="pref_share_app_summary">Anbefal denne app til en ven.</string>
<string name="pref_share_app_share_text">Tjek denne fantastiske radio-app ud: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Tak skal du have!</string>
<string name="pref_share_app_thank_message">En stor tak til dig fra udviklerne.</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Aktiverer fanen for brugerdefineret tema.</string>
<string name="pref_custom_theme_disabled_summary">Deaktiverer fanen for brugerdefineret tema.</string>
<string name="pref_custom_theme_summary">Tilpas applikationens baggrundsfarve.</string>
<string name="pref_custom_theme_title">Brugerdefineret tema</string>
<string name="pref_custom_theme_enabled_title">Aktiver brugerdefineret tema</string>
<string name="theme_predefined_colors">Foruddefinerede farver (Lys/Mørk):</string>
<string name="theme_custom_rgb">Brugerdefineret RGB (tilpas venligst til appen):</string>
<string name="theme_red">Rød</string>
<string name="theme_green">Grøn</string>
<string name="theme_blue">Blå</string>
<string name="hex_code">Hex-kode</string>
</resources> </resources>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">Stopp</string> <string name="notification_stop">Stopp</string>
<string name="notification_skip_to_previous">Zurück</string> <string name="notification_skip_to_previous">Zurück</string>
<string name="notification_skip_to_next">Nächste</string> <string name="notification_skip_to_next">Nächste</string>
<string name="notification_test_content">Dies ist eine Testbenachrichtigung.</string>
<string name="notification_channel_description">Alle Benachrichtigungen über die App.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Tauche ein in den Sound deiner Wahl!</string> <string name="onboarding_app_description">Tauche ein in den Sound deiner Wahl!</string>
<string name="onboarding_app_get_started">Jetzt starten</string> <string name="onboarding_app_get_started">Jetzt starten</string>
@@ -93,6 +95,8 @@
<string name="pref_theme_selection_title">App-Design</string> <string name="pref_theme_selection_title">App-Design</string>
<string name="pref_update_station_images_summary">Die neueste Version aller Senderbilder herunterladen.</string> <string name="pref_update_station_images_summary">Die neueste Version aller Senderbilder herunterladen.</string>
<string name="pref_update_station_images_title">Senderbilder aktualisieren</string> <string name="pref_update_station_images_title">Senderbilder aktualisieren</string>
<string name="pref_test_notification_title">Test-Benachrichtigung</string>
<string name="pref_test_notification_summary">Testen, ob das Benachrichtigungssystem funktioniert.</string>
<!-- Sample Text --> <!-- Sample Text -->
<!-- App Shortcuts --> <!-- App Shortcuts -->
<string name="shortcut_last_station_disabled_message">Verknüpfung für Wiedergabe des letzten Senders deaktiviert.</string> <string name="shortcut_last_station_disabled_message">Verknüpfung für Wiedergabe des letzten Senders deaktiviert.</string>
@@ -122,10 +126,13 @@
<!-- Snackbars --> <!-- Snackbars -->
<string name="snackbar_show">Zeigen</string> <string name="snackbar_show">Zeigen</string>
<string name="snackbar_update_available">ist verfügbar!</string> <string name="snackbar_update_available">ist verfügbar!</string>
<string name="snackbar_failed_permission_notification">Fehler bei der Anfrage nach Benachrichtigungsberechtigung.</string>
<string name="pref_audio_effects_title">Audio-Effekte</string> <string name="pref_audio_effects_title">Audio-Effekte</string>
<string name="pref_bass_boost_title">Bass-Boost</string> <string name="pref_bass_boost_title">Bass-Boost</string>
<string name="pref_bass_boost_summary">Erhöhen Sie die Bassverstärkung.</string> <string name="pref_bass_boost_summary">Erhöhen Sie die Bassverstärkung.</string>
<string name="pref_reverb_title">Hall</string> <string name="pref_reverb_title">Hall</string>
<string name="pref_security_title">Sicherheit</string>
<string name="pref_security_summary">Erfahren Sie mehr über die Sicherheit dieser Anwendung</string>
<string name="pref_reverb_summary">Reverb-Mix anpassen.</string> <string name="pref_reverb_summary">Reverb-Mix anpassen.</string>
<string name="pref_drc_title">Dynamikkompression</string> <string name="pref_drc_title">Dynamikkompression</string>
<string name="pref_drc_summary">Den Dynamikbereich für eine gleichbleibende Lautstärke komprimieren.</string> <string name="pref_drc_summary">Den Dynamikbereich für eine gleichbleibende Lautstärke komprimieren.</string>
@@ -153,5 +160,24 @@
<string name="media_route_menu_title">Streamen</string> <string name="media_route_menu_title">Streamen</string>
<string name="pref_visualizer_title">Spektrumanzeige</string> <string name="pref_visualizer_title">Spektrumanzeige</string>
<string name="pref_visualizer_summary">Sehe die Spektrumanzeige.</string> <string name="pref_visualizer_summary">Sehe die Spektrumanzeige.</string>
<string name="pref_share_app_title">App teilen</string>
<string name="pref_share_app_summary">Empfehle diese App einem Freund.</string>
<string name="pref_share_app_share_text">Schau dir diese tolle Radio-App an: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Vielen Dank!</string>
<string name="pref_share_app_thank_message">Ein großes Dankeschön von den Entwicklern.</string>
<string name="loading">Lade…</string> <string name="loading">Lade…</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Aktiviert den Tab für das benutzerdefinierte Design.</string>
<string name="pref_custom_theme_disabled_summary">Deaktiviert den Tab für das benutzerdefinierte Design.</string>
<string name="pref_custom_theme_summary">Passen Sie die Hintergrundfarbe der Anwendung an.</string>
<string name="pref_custom_theme_title">Benutzerdefiniertes Design</string>
<string name="pref_custom_theme_enabled_title">Benutzerdefiniertes Design aktivieren</string>
<string name="theme_predefined_colors">Vordefinierte Farben (Hell/Dunkel):</string>
<string name="theme_custom_rgb">Benutzerdefiniertes RGB (bitte an die App anpassen):</string>
<string name="theme_red">Rot</string>
<string name="theme_green">Grün</string>
<string name="theme_blue">Blau</string>
<string name="hex_code">Hex-Code</string>
</resources> </resources>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">Διακοπή</string> <string name="notification_stop">Διακοπή</string>
<string name="notification_skip_to_previous">Προηγούμενο</string> <string name="notification_skip_to_previous">Προηγούμενο</string>
<string name="notification_skip_to_next">Επόμενο</string> <string name="notification_skip_to_next">Επόμενο</string>
<string name="notification_test_content">Αυτή είναι μια δοκιμαστική ειδοποίηση.</string>
<string name="notification_channel_description">Όλες οι ειδοποιήσεις σχετικά με την εφαρμογή.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Βυθιστείτε στον ήχο της επιλογής σας!</string> <string name="onboarding_app_description">Βυθιστείτε στον ήχο της επιλογής σας!</string>
<string name="onboarding_app_get_started">Ας ξεκινήσουμε</string> <string name="onboarding_app_get_started">Ας ξεκινήσουμε</string>
@@ -84,6 +86,8 @@
<string name="pref_theme_selection_title">Θέμα Εφαρμογής</string> <string name="pref_theme_selection_title">Θέμα Εφαρμογής</string>
<string name="pref_update_station_images_summary">Κατεβάστε την τελευταία έκδοση των εικόνων όλων των σταθμών.</string> <string name="pref_update_station_images_summary">Κατεβάστε την τελευταία έκδοση των εικόνων όλων των σταθμών.</string>
<string name="pref_update_station_images_title">Ενημέρωση Εικόνων Σταθμών</string> <string name="pref_update_station_images_title">Ενημέρωση Εικόνων Σταθμών</string>
<string name="pref_test_notification_title">Δοκιμαστική Ειδοποίηση</string>
<string name="pref_test_notification_summary">Έλεγχος αν το σύστημα ειδοποιήσεων λειτουργεί.</string>
<!-- Sample Text --> <!-- Sample Text -->
<!-- App Shortcuts --> <!-- App Shortcuts -->
<string name="shortcut_last_station_disabled_message">Η συντόμευση για την αναπαραγωγή του τελευταίου σταθμού έχει απενεργοποιηθεί.</string> <string name="shortcut_last_station_disabled_message">Η συντόμευση για την αναπαραγωγή του τελευταίου σταθμού έχει απενεργοποιηθεί.</string>
@@ -113,6 +117,7 @@
<!-- Snackbars --> <!-- Snackbars -->
<string name="snackbar_show">Εμφάνισε</string> <string name="snackbar_show">Εμφάνισε</string>
<string name="snackbar_update_available">είναι διαθέσιμη!</string> <string name="snackbar_update_available">είναι διαθέσιμη!</string>
<string name="snackbar_failed_permission_notification">Απέτυχε η αίτηση δικαιώματος ειδοποίησης.</string>
<!-- Language Selection --> <!-- Language Selection -->
<string name="pref_language_selection_title">Γλώσσα</string> <string name="pref_language_selection_title">Γλώσσα</string>
<string name="pref_language_selection_summary">Τρέχουσα γλώσσα</string> <string name="pref_language_selection_summary">Τρέχουσα γλώσσα</string>
@@ -123,6 +128,8 @@
<string name="dialog_yes_no_message_update_collection">Κατεβάστε την τελευταία έκδοση όλων των σταθμών;</string> <string name="dialog_yes_no_message_update_collection">Κατεβάστε την τελευταία έκδοση όλων των σταθμών;</string>
<string name="dialog_yes_no_positive_button_update_collection">Ενημέρωση</string> <string name="dialog_yes_no_positive_button_update_collection">Ενημέρωση</string>
<string name="pref_audio_effects_title">Ηχητικά Εφέ</string> <string name="pref_audio_effects_title">Ηχητικά Εφέ</string>
<string name="pref_security_title">Ασφάλεια</string>
<string name="pref_security_summary">Μάθετε περισσότερα για την ασφάλεια αυτής της εφαρμογής</string>
<string name="pref_bass_boost_title">Ενίσχυση Μπάσων</string> <string name="pref_bass_boost_title">Ενίσχυση Μπάσων</string>
<string name="pref_bass_boost_summary">Αύξηση της ενίσχυσης μπάσων.</string> <string name="pref_bass_boost_summary">Αύξηση της ενίσχυσης μπάσων.</string>
<string name="pref_reverb_title">Αντήχηση</string> <string name="pref_reverb_title">Αντήχηση</string>
@@ -154,4 +161,23 @@
<string name="media_route_menu_title">Μετάδοση</string> <string name="media_route_menu_title">Μετάδοση</string>
<string name="pref_visualizer_title">Αναλυτής Φάσματος</string> <string name="pref_visualizer_title">Αναλυτής Φάσματος</string>
<string name="pref_visualizer_summary">Εμφάνιση του Αναλυτή Φάσματος.</string> <string name="pref_visualizer_summary">Εμφάνιση του Αναλυτή Φάσματος.</string>
<string name="pref_share_app_title">Κοινοποίηση Εφαρμογής</string>
<string name="pref_share_app_summary">Προτείνετε αυτήν την εφαρμογή σε έναν φίλο.</string>
<string name="pref_share_app_share_text">Δείτε αυτήν την καταπληκτική εφαρμογή ραδιοφώνου: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Σας ευχαριστούμε!</string>
<string name="pref_share_app_thank_message">Ένα μεγάλο ευχαριστώ από τους προγραμματιστές.</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Ενεργοποιεί την καρτέλα προσαρμοσμένου θέματος.</string>
<string name="pref_custom_theme_disabled_summary">Απενεργοποιεί την καρτέλα προσαρμοσμένου θέματος.</string>
<string name="pref_custom_theme_summary">Προσαρμόστε το χρώμα φόντου της εφαρμογής.</string>
<string name="pref_custom_theme_title">Προσαρμοσμένο Θέμα</string>
<string name="pref_custom_theme_enabled_title">Ενεργοποίηση Προσαρμοσμένου Θέματος</string>
<string name="theme_predefined_colors">Προκαθορισμένα Χρώματα (Φωτεινό/Σκοτεινό):</string>
<string name="theme_custom_rgb">Προσαρμοσμένο RGB (παρακαλώ προσαρμόστε στην εφαρμογή):</string>
<string name="theme_red">Κόκκινο</string>
<string name="theme_green">Πράσινο</string>
<string name="theme_blue">Μπλε</string>
<string name="hex_code">Κωδικός Hex</string>
</resources> </resources>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">Arrêt</string> <string name="notification_stop">Arrêt</string>
<string name="notification_skip_to_previous">Précédent</string> <string name="notification_skip_to_previous">Précédent</string>
<string name="notification_skip_to_next">Suivant</string> <string name="notification_skip_to_next">Suivant</string>
<string name="notification_test_content">Il sagit dune notification de test.</string>
<string name="notification_channel_description">Toutes les notifications de lapplication.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Plongez dans le son de votre choix !</string> <string name="onboarding_app_description">Plongez dans le son de votre choix !</string>
<string name="onboarding_app_get_started">Commencer maintenant</string> <string name="onboarding_app_get_started">Commencer maintenant</string>
@@ -84,6 +86,8 @@
<string name="pref_theme_selection_title">Thème de lapplication</string> <string name="pref_theme_selection_title">Thème de lapplication</string>
<string name="pref_update_station_images_summary">Télécharger la dernière version de toutes les images des stations.</string> <string name="pref_update_station_images_summary">Télécharger la dernière version de toutes les images des stations.</string>
<string name="pref_update_station_images_title">Mettre à jour les images des stations</string> <string name="pref_update_station_images_title">Mettre à jour les images des stations</string>
<string name="pref_test_notification_title">Notification de test</string>
<string name="pref_test_notification_summary">Tester si le système de notification fonctionne.</string>
<!-- Raccourcis de l'app --> <!-- Raccourcis de l'app -->
<string name="shortcut_last_station_disabled_message">Raccourci pour lire la dernière station désactivé.</string> <string name="shortcut_last_station_disabled_message">Raccourci pour lire la dernière station désactivé.</string>
<string name="shortcut_last_station_long_label">Lire la dernière station</string> <string name="shortcut_last_station_long_label">Lire la dernière station</string>
@@ -111,6 +115,7 @@
<!-- Snackbars --> <!-- Snackbars -->
<string name="snackbar_show">Afficher</string> <string name="snackbar_show">Afficher</string>
<string name="snackbar_update_available">est disponible !</string> <string name="snackbar_update_available">est disponible !</string>
<string name="snackbar_failed_permission_notification">Échec de la demande dautorisation de notification.</string>
<!-- Language Selection --> <!-- Language Selection -->
<string name="pref_language_selection_title">Langue</string> <string name="pref_language_selection_title">Langue</string>
<string name="pref_language_selection_summary">Langue actuelle</string> <string name="pref_language_selection_summary">Langue actuelle</string>
@@ -123,6 +128,8 @@
<string name="pref_audio_effects_title">Effets Audio</string> <string name="pref_audio_effects_title">Effets Audio</string>
<string name="pref_bass_boost_title">Amplification des basses</string> <string name="pref_bass_boost_title">Amplification des basses</string>
<string name="pref_bass_boost_summary">Augmenter l amplification des basses.</string> <string name="pref_bass_boost_summary">Augmenter l amplification des basses.</string>
<string name="pref_security_title">Sécurité</string>
<string name="pref_security_summary">En savoir plus sur la sécurité de cette application</string>
<string name="pref_reverb_title">Réverbération</string> <string name="pref_reverb_title">Réverbération</string>
<string name="pref_reverb_summary">Ajuster le mix de réverbération.</string> <string name="pref_reverb_summary">Ajuster le mix de réverbération.</string>
<string name="pref_drc_title">Compression Dynamique</string> <string name="pref_drc_title">Compression Dynamique</string>
@@ -152,4 +159,23 @@
<string name="media_route_menu_title">Diffuser</string> <string name="media_route_menu_title">Diffuser</string>
<string name="pref_visualizer_title">Analyseur de spectre</string> <string name="pref_visualizer_title">Analyseur de spectre</string>
<string name="pref_visualizer_summary">Afficher l analyseur de spectre.</string> <string name="pref_visualizer_summary">Afficher l analyseur de spectre.</string>
<string name="pref_share_app_title">Partager l\'application</string>
<string name="pref_share_app_summary">Recommandez cette application à un ami.</string>
<string name="pref_share_app_share_text">Découvrez cette super application radio : https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Merci beaucoup !</string>
<string name="pref_share_app_thank_message">Un grand merci de la part des développeurs.</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Active l\'onglet du thème personnalisé.</string>
<string name="pref_custom_theme_disabled_summary">Désactive l\'onglet du thème personnalisé.</string>
<string name="pref_custom_theme_summary">Personnalisez la couleur d\'arrière-plan de l\'application.</string>
<string name="pref_custom_theme_title">Thème personnalisé</string>
<string name="pref_custom_theme_enabled_title">Activer le thème personnalisé</string>
<string name="theme_predefined_colors">Couleurs prédéfinies (Clair/Sombre) :</string>
<string name="theme_custom_rgb">RGB personnalisé (veuillez l\'adapter à l\'application) :</string>
<string name="theme_red">Rouge</string>
<string name="theme_green">Vert</string>
<string name="theme_blue">Bleu</string>
<string name="hex_code">Code Hex</string>
</resources> </resources>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">停止</string> <string name="notification_stop">停止</string>
<string name="notification_skip_to_previous">前へ</string> <string name="notification_skip_to_previous">前へ</string>
<string name="notification_skip_to_next">次へ</string> <string name="notification_skip_to_next">次へ</string>
<string name="notification_test_content">テスト通知です。</string>
<string name="notification_channel_description">アプリに関するすべての通知。</string>
<!-- オンボーディング --> <!-- オンボーディング -->
<string name="onboarding_app_description">お気に入りのサウンドの世界に飛び込もう!</string> <string name="onboarding_app_description">お気に入りのサウンドの世界に飛び込もう!</string>
<string name="onboarding_app_get_started">今すぐ始める</string> <string name="onboarding_app_get_started">今すぐ始める</string>
@@ -84,6 +86,8 @@
<string name="pref_theme_selection_title">アプリテーマ</string> <string name="pref_theme_selection_title">アプリテーマ</string>
<string name="pref_update_station_images_summary">すべての局画像を最新に更新します。</string> <string name="pref_update_station_images_summary">すべての局画像を最新に更新します。</string>
<string name="pref_update_station_images_title">局画像を更新</string> <string name="pref_update_station_images_title">局画像を更新</string>
<string name="pref_test_notification_title">テスト通知</string>
<string name="pref_test_notification_summary">通知システムが動作するかテストします。</string>
<!-- ショートカット --> <!-- ショートカット -->
<string name="shortcut_last_station_disabled_message">最後に再生した局のショートカットは無効になっています。</string> <string name="shortcut_last_station_disabled_message">最後に再生した局のショートカットは無効になっています。</string>
<string name="shortcut_last_station_long_label">最後の局を再生</string> <string name="shortcut_last_station_long_label">最後の局を再生</string>
@@ -112,6 +116,7 @@
<!-- スナックバー --> <!-- スナックバー -->
<string name="snackbar_show">表示</string> <string name="snackbar_show">表示</string>
<string name="snackbar_update_available">が利用可能です!</string> <string name="snackbar_update_available">が利用可能です!</string>
<string name="snackbar_failed_permission_notification">通知の権限リクエストに失敗しました。</string>
<!-- 言語選択 --> <!-- 言語選択 -->
<string name="pref_language_selection_title">言語</string> <string name="pref_language_selection_title">言語</string>
<string name="pref_language_selection_summary">現在の言語</string> <string name="pref_language_selection_summary">現在の言語</string>
@@ -123,6 +128,8 @@
<string name="dialog_yes_no_positive_button_update_collection">更新</string> <string name="dialog_yes_no_positive_button_update_collection">更新</string>
<string name="pref_audio_effects_title">オーディオエフェクト</string> <string name="pref_audio_effects_title">オーディオエフェクト</string>
<string name="pref_bass_boost_title">バスブースト</string> <string name="pref_bass_boost_title">バスブースト</string>
<string name="pref_security_title">セキュリティ</string>
<string name="pref_security_summary">このアプリケーションのセキュリティについて詳しく知る</string>
<string name="pref_bass_boost_summary">低音を増強します。</string> <string name="pref_bass_boost_summary">低音を増強します。</string>
<string name="pref_reverb_title">リバーブ</string> <string name="pref_reverb_title">リバーブ</string>
<string name="pref_reverb_summary">リバーブミスを調整します。</string> <string name="pref_reverb_summary">リバーブミスを調整します。</string>
@@ -153,4 +160,23 @@
<string name="media_route_menu_title">キャスト</string> <string name="media_route_menu_title">キャスト</string>
<string name="pref_visualizer_title">スペクトラムアナライザー</string> <string name="pref_visualizer_title">スペクトラムアナライザー</string>
<string name="pref_visualizer_summary">スペクトラムアナライザーを表示します。</string> <string name="pref_visualizer_summary">スペクトラムアナライザーを表示します。</string>
<string name="pref_share_app_title">アプリを共有</string>
<string name="pref_share_app_summary">このアプリを友達に勧める。</string>
<string name="pref_share_app_share_text">この素晴らしいラジオアプリをチェックしてみてください: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">ありがとうございます!</string>
<string name="pref_share_app_thank_message">開発者一同より、心から感謝申し上げます。</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">カスタムテーマタブを有効にします。</string>
<string name="pref_custom_theme_disabled_summary">カスタムテーマタブを無効にします。</string>
<string name="pref_custom_theme_summary">アプリケーションの背景色をカスタマイズします。</string>
<string name="pref_custom_theme_title">カスタムテーマ</string>
<string name="pref_custom_theme_enabled_title">カスタムテーマを有効にする</string>
<string name="theme_predefined_colors">事前定義された色(ライト/ダーク):</string>
<string name="theme_custom_rgb">カスタムRGB(アプリに合わせて調整してください):</string>
<string name="theme_red"></string>
<string name="theme_green"></string>
<string name="theme_blue"></string>
<string name="hex_code">Hexコード</string>
</resources> </resources>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">Stoppen</string> <string name="notification_stop">Stoppen</string>
<string name="notification_skip_to_previous">Vorige</string> <string name="notification_skip_to_previous">Vorige</string>
<string name="notification_skip_to_next">Volgende</string> <string name="notification_skip_to_next">Volgende</string>
<string name="notification_test_content">Dit is een testmelding.</string>
<string name="notification_channel_description">Alle meldingen over de app.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Dompel jezelf onder in het geluid van je keuze!</string> <string name="onboarding_app_description">Dompel jezelf onder in het geluid van je keuze!</string>
<string name="onboarding_app_get_started">Aan de slag</string> <string name="onboarding_app_get_started">Aan de slag</string>
@@ -84,6 +86,8 @@
<string name="pref_theme_selection_title">App Thema</string> <string name="pref_theme_selection_title">App Thema</string>
<string name="pref_update_station_images_summary">Download de laatste versie van alle zenderafbeeldingen.</string> <string name="pref_update_station_images_summary">Download de laatste versie van alle zenderafbeeldingen.</string>
<string name="pref_update_station_images_title">Update Zenderafbeeldingen</string> <string name="pref_update_station_images_title">Update Zenderafbeeldingen</string>
<string name="pref_test_notification_title">Testmelding</string>
<string name="pref_test_notification_summary">Test of het meldingsysteem werkt.</string>
<!-- Sample Text --> <!-- Sample Text -->
<!-- App Shortcuts --> <!-- App Shortcuts -->
<string name="shortcut_last_station_disabled_message">Snelkoppeling voor het afspelen van de laatste zender uitgeschakeld.</string> <string name="shortcut_last_station_disabled_message">Snelkoppeling voor het afspelen van de laatste zender uitgeschakeld.</string>
@@ -113,6 +117,7 @@
<!-- Snackbars --> <!-- Snackbars -->
<string name="snackbar_show">Weergeven</string> <string name="snackbar_show">Weergeven</string>
<string name="snackbar_update_available">is beschikbaar!</string> <string name="snackbar_update_available">is beschikbaar!</string>
<string name="snackbar_failed_permission_notification">Kan notificatierechtiging niet aanvragen.</string>
<!-- Language Selection --> <!-- Language Selection -->
<string name="pref_language_selection_title">Taal</string> <string name="pref_language_selection_title">Taal</string>
<string name="pref_language_selection_summary">Huidige taal</string> <string name="pref_language_selection_summary">Huidige taal</string>
@@ -123,6 +128,8 @@
<string name="dialog_yes_no_message_update_collection">Download de laatste versie van alle zenders?</string> <string name="dialog_yes_no_message_update_collection">Download de laatste versie van alle zenders?</string>
<string name="dialog_yes_no_positive_button_update_collection">Bijwerken</string> <string name="dialog_yes_no_positive_button_update_collection">Bijwerken</string>
<string name="pref_audio_effects_title">Audio Effecten</string> <string name="pref_audio_effects_title">Audio Effecten</string>
<string name="pref_security_title">Securiteit</string>
<string name="pref_security_summary">Meer informatie over de beveiliging van deze toepassing</string>
<string name="pref_bass_boost_title">Bass Boost</string> <string name="pref_bass_boost_title">Bass Boost</string>
<string name="pref_bass_boost_summary">Verhoog de bassversterking.</string> <string name="pref_bass_boost_summary">Verhoog de bassversterking.</string>
<string name="pref_reverb_title">Reverb</string> <string name="pref_reverb_title">Reverb</string>
@@ -154,4 +161,23 @@
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="pref_visualizer_title">Spectrum Analyser</string> <string name="pref_visualizer_title">Spectrum Analyser</string>
<string name="pref_visualizer_summary">Toon de Spectrum Analyser.</string> <string name="pref_visualizer_summary">Toon de Spectrum Analyser.</string>
<string name="pref_share_app_title">App delen</string>
<string name="pref_share_app_summary">Beveel deze app aan bij een vriend.</string>
<string name="pref_share_app_share_text">Bekijk deze geweldige radio-app: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Bedankt!</string>
<string name="pref_share_app_thank_message">Een groot dankjewel van de ontwikkelaars.</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Schakelt het tabblad voor het aangepaste thema in.</string>
<string name="pref_custom_theme_disabled_summary">Schakelt het tabblad voor het aangepaste thema uit.</string>
<string name="pref_custom_theme_summary">Pas de achtergrondkleur van de applicatie aan.</string>
<string name="pref_custom_theme_title">Aangepast Thema</string>
<string name="pref_custom_theme_enabled_title">Aangepast Thema Inschakelen</string>
<string name="theme_predefined_colors">Vooraf gedefinieerde kleuren (Licht/Donker):</string>
<string name="theme_custom_rgb">Aangepaste RGB (pas deze aan de app aan):</string>
<string name="theme_red">Rood</string>
<string name="theme_green">Groen</string>
<string name="theme_blue">Blauw</string>
<string name="hex_code">Hex-code</string>
</resources> </resources>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">Zatrzymaj</string> <string name="notification_stop">Zatrzymaj</string>
<string name="notification_skip_to_previous">Poprzedni</string> <string name="notification_skip_to_previous">Poprzedni</string>
<string name="notification_skip_to_next">Następny</string> <string name="notification_skip_to_next">Następny</string>
<string name="notification_test_content">To jest powiadomienie testowe.</string>
<string name="notification_channel_description">Wszystkie powiadomienia o aplikacji.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Zanurz się w dźwięku swojego wyboru!</string> <string name="onboarding_app_description">Zanurz się w dźwięku swojego wyboru!</string>
<string name="onboarding_app_get_started">Zaczynamy</string> <string name="onboarding_app_get_started">Zaczynamy</string>
@@ -84,6 +86,8 @@
<string name="pref_theme_selection_title">Motyw aplikacji</string> <string name="pref_theme_selection_title">Motyw aplikacji</string>
<string name="pref_update_station_images_summary">Pobierz najnowszą wersję wszystkich obrazów stacji w swojej kolekcji.</string> <string name="pref_update_station_images_summary">Pobierz najnowszą wersję wszystkich obrazów stacji w swojej kolekcji.</string>
<string name="pref_update_station_images_title">Aktualizuj zdjęcia stacji</string> <string name="pref_update_station_images_title">Aktualizuj zdjęcia stacji</string>
<string name="pref_test_notification_title">Testowe powiadomienie</string>
<string name="pref_test_notification_summary">Sprawdź, czy system powiadomień działa.</string>
<!-- Sample Text --> <!-- Sample Text -->
<!-- App Shortcuts --> <!-- App Shortcuts -->
<string name="shortcut_last_station_disabled_message">Skrót do odtwarzania ostatniej stacji jest wyłączony.</string> <string name="shortcut_last_station_disabled_message">Skrót do odtwarzania ostatniej stacji jest wyłączony.</string>
@@ -113,6 +117,7 @@
<!-- Snackbars --> <!-- Snackbars -->
<string name="snackbar_show">Wyświetl</string> <string name="snackbar_show">Wyświetl</string>
<string name="snackbar_update_available">jest dostępna!</string> <string name="snackbar_update_available">jest dostępna!</string>
<string name="snackbar_failed_permission_notification">Nie udało się poprosić o pozwolenie na powiadomienia.</string>
<!-- Language Selection --> <!-- Language Selection -->
<string name="pref_language_selection_title">Język</string> <string name="pref_language_selection_title">Język</string>
<string name="pref_language_selection_summary">Aktualny język</string> <string name="pref_language_selection_summary">Aktualny język</string>
@@ -123,6 +128,8 @@
<string name="dialog_yes_no_message_update_collection">Pobrać najnowszą wersję wszystkich stacji?</string> <string name="dialog_yes_no_message_update_collection">Pobrać najnowszą wersję wszystkich stacji?</string>
<string name="dialog_yes_no_positive_button_update_collection">Aktualizuj</string> <string name="dialog_yes_no_positive_button_update_collection">Aktualizuj</string>
<string name="pref_audio_effects_title">Efekty Dźwiękowe</string> <string name="pref_audio_effects_title">Efekty Dźwiękowe</string>
<string name="pref_security_title">Bezpieczeństwo</string>
<string name="pref_security_summary">Dowiedz się więcej o bezpieczeństwie tej aplikacji</string>
<string name="pref_bass_boost_title">Wzmocnienie Basów</string> <string name="pref_bass_boost_title">Wzmocnienie Basów</string>
<string name="pref_bass_boost_summary">Zwiększ wzmocnienie basów.</string> <string name="pref_bass_boost_summary">Zwiększ wzmocnienie basów.</string>
<string name="pref_reverb_title">Pogłos</string> <string name="pref_reverb_title">Pogłos</string>
@@ -154,4 +161,23 @@
<string name="media_route_menu_title">Przesyłanie</string> <string name="media_route_menu_title">Przesyłanie</string>
<string name="pref_visualizer_title">Analizator Widma</string> <string name="pref_visualizer_title">Analizator Widma</string>
<string name="pref_visualizer_summary">Pokaż Analizator Widma.</string> <string name="pref_visualizer_summary">Pokaż Analizator Widma.</string>
<string name="pref_share_app_title">Udostępnij aplikację</string>
<string name="pref_share_app_summary">Poleć tę aplikację znajomemu.</string>
<string name="pref_share_app_share_text">Sprawdź tę świetną aplikację radiową: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Dziękujemy!</string>
<string name="pref_share_app_thank_message">Wielkie podziękowania od deweloperów.</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Włącza kartę motywu niestandardowego.</string>
<string name="pref_custom_theme_disabled_summary">Wyłącza kartę motywu niestandardowego.</string>
<string name="pref_custom_theme_summary">Dostosuj kolor tła aplikacji.</string>
<string name="pref_custom_theme_title">Motyw niestandardowy</string>
<string name="pref_custom_theme_enabled_title">Włącz motyw niestandardowy</string>
<string name="theme_predefined_colors">Predefiniowane kolory (Jasny/Ciemny):</string>
<string name="theme_custom_rgb">Niestandardowy RGB (proszę dostosować do aplikacji):</string>
<string name="theme_red">Czerwony</string>
<string name="theme_green">Zielony</string>
<string name="theme_blue">Niebieski</string>
<string name="hex_code">Kod Hex</string>
</resources> </resources>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">Остановить</string> <string name="notification_stop">Остановить</string>
<string name="notification_skip_to_previous">Предыдущий</string> <string name="notification_skip_to_previous">Предыдущий</string>
<string name="notification_skip_to_next">Следующий</string> <string name="notification_skip_to_next">Следующий</string>
<string name="notification_test_content">Это тестовое уведомление.</string>
<string name="notification_channel_description">Все уведомления о приложении.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Погрузитесь в звук по вашему выбору!</string> <string name="onboarding_app_description">Погрузитесь в звук по вашему выбору!</string>
<string name="onboarding_app_get_started">Начать</string> <string name="onboarding_app_get_started">Начать</string>
@@ -84,6 +86,8 @@
<string name="pref_theme_selection_title">Тема приложения</string> <string name="pref_theme_selection_title">Тема приложения</string>
<string name="pref_update_station_images_summary">Скачать последнюю версию всех изображений станций.</string> <string name="pref_update_station_images_summary">Скачать последнюю версию всех изображений станций.</string>
<string name="pref_update_station_images_title">Обновить изображения станций</string> <string name="pref_update_station_images_title">Обновить изображения станций</string>
<string name="pref_test_notification_title">Тест уведомления</string>
<string name="pref_test_notification_summary">Проверить, работает ли система уведомлений.</string>
<!-- Sample Text --> <!-- Sample Text -->
<!-- App Shortcuts --> <!-- App Shortcuts -->
<string name="shortcut_last_station_disabled_message">Ярлык для воспроизведения последней станции отключён.</string> <string name="shortcut_last_station_disabled_message">Ярлык для воспроизведения последней станции отключён.</string>
@@ -113,6 +117,7 @@
<!-- Snackbars --> <!-- Snackbars -->
<string name="snackbar_show">Показать</string> <string name="snackbar_show">Показать</string>
<string name="snackbar_update_available">доступно!</string> <string name="snackbar_update_available">доступно!</string>
<string name="snackbar_failed_permission_notification">Не удалось запросить разрешение на уведомления.</string>
<!-- Language Selection --> <!-- Language Selection -->
<string name="pref_language_selection_title">Язык</string> <string name="pref_language_selection_title">Язык</string>
<string name="pref_language_selection_summary">Текущий язык</string> <string name="pref_language_selection_summary">Текущий язык</string>
@@ -123,6 +128,8 @@
<string name="dialog_yes_no_message_update_collection">Скачать последнюю версию всех станций?</string> <string name="dialog_yes_no_message_update_collection">Скачать последнюю версию всех станций?</string>
<string name="dialog_yes_no_positive_button_update_collection">Обновить</string> <string name="dialog_yes_no_positive_button_update_collection">Обновить</string>
<string name="pref_audio_effects_title">Звуковые эффекты</string> <string name="pref_audio_effects_title">Звуковые эффекты</string>
<string name="pref_security_title">Безопасность</string>
<string name="pref_security_summary">Узнать больше о безопасности этого приложения</string>
<string name="pref_bass_boost_title">Усиление басов</string> <string name="pref_bass_boost_title">Усиление басов</string>
<string name="pref_bass_boost_summary">Увеличить усиление басов.</string> <string name="pref_bass_boost_summary">Увеличить усиление басов.</string>
<string name="pref_reverb_title">Реверберация</string> <string name="pref_reverb_title">Реверберация</string>
@@ -154,4 +161,23 @@
<string name="media_route_menu_title">Трансляция</string> <string name="media_route_menu_title">Трансляция</string>
<string name="pref_visualizer_title">Анализатор спектра</string> <string name="pref_visualizer_title">Анализатор спектра</string>
<string name="pref_visualizer_summary">Показать анализатор спектра.</string> <string name="pref_visualizer_summary">Показать анализатор спектра.</string>
<string name="pref_share_app_title">Поделиться приложением</string>
<string name="pref_share_app_summary">Рекомендовать это приложение другу.</string>
<string name="pref_share_app_share_text">Посмотрите это классное радио-приложение: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Спасибо!</string>
<string name="pref_share_app_thank_message">Большое спасибо от разработчиков.</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Включает вкладку пользовательской темы.</string>
<string name="pref_custom_theme_disabled_summary">Отключает вкладку пользовательской темы.</string>
<string name="pref_custom_theme_summary">Настройка цвета фона приложения.</string>
<string name="pref_custom_theme_title">Пользовательская тема</string>
<string name="pref_custom_theme_enabled_title">Включить пользовательскую тему</string>
<string name="theme_predefined_colors">Предустановленные цвета (Светлые/Темные):</string>
<string name="theme_custom_rgb">Пользовательский RGB (пожалуйста, адаптируйте к приложению):</string>
<string name="theme_red">Красный</string>
<string name="theme_green">Зеленый</string>
<string name="theme_blue">Синий</string>
<string name="hex_code">Hex-код</string>
</resources> </resources>
+26
View File
@@ -44,6 +44,8 @@
<string name="notification_stop">Зупинити</string> <string name="notification_stop">Зупинити</string>
<string name="notification_skip_to_previous">Попередня</string> <string name="notification_skip_to_previous">Попередня</string>
<string name="notification_skip_to_next">Наступна</string> <string name="notification_skip_to_next">Наступна</string>
<string name="notification_test_content">Це тестове сповіщення.</string>
<string name="notification_channel_description">Усі сповіщення про застосунок.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Пориньте у звук на ваш вибір!</string> <string name="onboarding_app_description">Пориньте у звук на ваш вибір!</string>
<string name="onboarding_app_get_started">Початок роботи</string> <string name="onboarding_app_get_started">Початок роботи</string>
@@ -84,6 +86,8 @@
<string name="pref_theme_selection_title">Тема застосунку</string> <string name="pref_theme_selection_title">Тема застосунку</string>
<string name="pref_update_station_images_summary">Завантажити останню версію всіх зображень станцій.</string> <string name="pref_update_station_images_summary">Завантажити останню версію всіх зображень станцій.</string>
<string name="pref_update_station_images_title">Оновити зображення станцій</string> <string name="pref_update_station_images_title">Оновити зображення станцій</string>
<string name="pref_test_notification_title">Тестове сповіщення</string>
<string name="pref_test_notification_summary">Перевірити, чи працює система сповіщень.</string>
<!-- Sample Text --> <!-- Sample Text -->
<!-- App Shortcuts --> <!-- App Shortcuts -->
<string name="shortcut_last_station_disabled_message">Ярлик для відтворення останньої станції вимкнено.</string> <string name="shortcut_last_station_disabled_message">Ярлик для відтворення останньої станції вимкнено.</string>
@@ -113,6 +117,7 @@
<!-- Snackbars --> <!-- Snackbars -->
<string name="snackbar_show">Показати</string> <string name="snackbar_show">Показати</string>
<string name="snackbar_update_available">доступне!</string> <string name="snackbar_update_available">доступне!</string>
<string name="snackbar_failed_permission_notification">Не вдалося запитати дозвіл на сповіщення.</string>
<!-- Language Selection --> <!-- Language Selection -->
<string name="pref_language_selection_title">Мова</string> <string name="pref_language_selection_title">Мова</string>
<string name="pref_language_selection_summary">Поточна мова</string> <string name="pref_language_selection_summary">Поточна мова</string>
@@ -123,6 +128,8 @@
<string name="dialog_yes_no_message_update_collection">Завантажити останню версію всіх станцій?</string> <string name="dialog_yes_no_message_update_collection">Завантажити останню версію всіх станцій?</string>
<string name="dialog_yes_no_positive_button_update_collection">Оновити</string> <string name="dialog_yes_no_positive_button_update_collection">Оновити</string>
<string name="pref_audio_effects_title">Звукові ефекти</string> <string name="pref_audio_effects_title">Звукові ефекти</string>
<string name="pref_security_title">Безпека</string>
<string name="pref_security_summary">Дізнайтеся більше про безпеку цього додатку</string>
<string name="pref_bass_boost_title">Підсилення басів</string> <string name="pref_bass_boost_title">Підсилення басів</string>
<string name="pref_bass_boost_summary">Збільшити підсилення басів.</string> <string name="pref_bass_boost_summary">Збільшити підсилення басів.</string>
<string name="pref_reverb_title">Реверберація</string> <string name="pref_reverb_title">Реверберація</string>
@@ -154,4 +161,23 @@
<string name="media_route_menu_title">Трансляція</string> <string name="media_route_menu_title">Трансляція</string>
<string name="pref_visualizer_title">Аналізатор спектру</string> <string name="pref_visualizer_title">Аналізатор спектру</string>
<string name="pref_visualizer_summary">Показати аналізатор спектру.</string> <string name="pref_visualizer_summary">Показати аналізатор спектру.</string>
<string name="pref_share_app_title">Поділитися застосунком</string>
<string name="pref_share_app_summary">Рекомендувати цей застосунок другу.</string>
<string name="pref_share_app_share_text">Подивіться на цей чудовий радіозастосунок: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Дякуємо!</string>
<string name="pref_share_app_thank_message">Велике спасибі вам від розробників.</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Вмикає вкладку користувацької теми.</string>
<string name="pref_custom_theme_disabled_summary">Вимикає вкладку користувацької теми.</string>
<string name="pref_custom_theme_summary">Налаштуйте колір фону застосунку.</string>
<string name="pref_custom_theme_title">Користувацька тема</string>
<string name="pref_custom_theme_enabled_title">Увімкнути користувацьку тему</string>
<string name="theme_predefined_colors">Попередньо визначені кольори (Світлі/Темні):</string>
<string name="theme_custom_rgb">Власний RGB (будь ласка, адаптуйте до застосунку):</string>
<string name="theme_red">Червоний</string>
<string name="theme_green">Зелений</string>
<string name="theme_blue">Синій</string>
<string name="hex_code">Hex-код</string>
</resources> </resources>
+27 -1
View File
@@ -2,6 +2,7 @@
<resources> <resources>
<!-- App Name --> <!-- App Name -->
<string name="app_version_name" translatable="false">\"Red\"</string> <string name="app_version_name" translatable="false">\"Red\"</string>
<string name="app_name" translatable="false">Radio</string>
<!-- Accessibility Descriptions --> <!-- Accessibility Descriptions -->
<string name="descr_app_icon">App icon depicting an old radio</string> <string name="descr_app_icon">App icon depicting an old radio</string>
@@ -49,6 +50,8 @@
<string name="notification_stop">Stop</string> <string name="notification_stop">Stop</string>
<string name="notification_skip_to_previous">Previous</string> <string name="notification_skip_to_previous">Previous</string>
<string name="notification_skip_to_next">Next</string> <string name="notification_skip_to_next">Next</string>
<string name="notification_test_content">This is a test notification.</string>
<string name="notification_channel_description">All notifications about the app.</string>
<!-- Onboarding --> <!-- Onboarding -->
<string name="onboarding_app_description">Immerse yourself in the sound of your choice!</string> <string name="onboarding_app_description">Immerse yourself in the sound of your choice!</string>
@@ -71,6 +74,7 @@
<string name="pref_language_pl" translatable="false">🇵🇱 Polski</string> <string name="pref_language_pl" translatable="false">🇵🇱 Polski</string>
<string name="pref_language_el" translatable="false">🇬🇷 Ελληνικά</string> <string name="pref_language_el" translatable="false">🇬🇷 Ελληνικά</string>
<string name="pref_language_da" translatable="false">🇩🇰 Dansk</string> <string name="pref_language_da" translatable="false">🇩🇰 Dansk</string>
<string name="pref_language_uk" translatable="false">🇺🇦 Українська</string>
<!-- Settings --> <!-- Settings -->
@@ -124,6 +128,8 @@
<string name="pref_github_summary" translatable="false">github.com/michatec/Radio</string> <string name="pref_github_summary" translatable="false">github.com/michatec/Radio</string>
<string name="pref_license_title">This application is open source</string> <string name="pref_license_title">This application is open source</string>
<string name="pref_license_summary">Licensed under the GPLv3 License</string> <string name="pref_license_summary">Licensed under the GPLv3 License</string>
<string name="pref_security_title">Security</string>
<string name="pref_security_summary">Learn more about the security of this application</string>
<string name="pref_links_title">Links</string> <string name="pref_links_title">Links</string>
<string name="pref_m3u_export_summary">Save your radio stations to an M3U playlist file that can be imported into other players.</string> <string name="pref_m3u_export_summary">Save your radio stations to an M3U playlist file that can be imported into other players.</string>
<string name="pref_m3u_export_title">Export M3U</string> <string name="pref_m3u_export_title">Export M3U</string>
@@ -141,6 +147,13 @@
<string name="pref_theme_selection_title">App Theme</string> <string name="pref_theme_selection_title">App Theme</string>
<string name="pref_update_station_images_summary">Download latest version of all station images.</string> <string name="pref_update_station_images_summary">Download latest version of all station images.</string>
<string name="pref_update_station_images_title">Update Station Images</string> <string name="pref_update_station_images_title">Update Station Images</string>
<string name="pref_test_notification_title">Test Notification</string>
<string name="pref_test_notification_summary">Test whether the notification system works.</string>
<string name="pref_share_app_title">Share App</string>
<string name="pref_share_app_summary">Recommend this app to a friend.</string>
<string name="pref_share_app_share_text">Check out this awesome radio app: https://github.com/michatec/Radio</string>
<string name="pref_share_app_thank_title">Thank You!</string>
<string name="pref_share_app_thank_message">A big thank you to you from the developers.</string>
<!-- Sample Text --> <!-- Sample Text -->
<string name="sample_text_sleep_timer_remaining_time" translatable="false">00:00</string> <string name="sample_text_sleep_timer_remaining_time" translatable="false">00:00</string>
@@ -182,12 +195,25 @@
<string name="snackbar_update_available">is available!</string> <string name="snackbar_update_available">is available!</string>
<string name="snackbar_url_app_home_page" translatable="false">https://github.com/michatec/Radio/releases/latest</string> <string name="snackbar_url_app_home_page" translatable="false">https://github.com/michatec/Radio/releases/latest</string>
<string name="snackbar_github_update_check_url" translatable="false">https://api.github.com/repos/michatec/Radio/releases/latest</string> <string name="snackbar_github_update_check_url" translatable="false">https://api.github.com/repos/michatec/Radio/releases/latest</string>
<string name="app_name" translatable="false">Radio</string>
<string name="icon_launcher" translatable="false">Icon launcher.</string> <string name="icon_launcher" translatable="false">Icon launcher.</string>
<string name="snackbar_failed_permission_notification">Failed to request notification permission.</string>
<!-- Extras --> <!-- Extras -->
<string name="loading">Loading…</string> <string name="loading">Loading…</string>
<string name="media_route_menu_title">Cast</string> <string name="media_route_menu_title">Cast</string>
<string name="pref_visualizer_title">Spectrum Analyzer</string> <string name="pref_visualizer_title">Spectrum Analyzer</string>
<string name="pref_visualizer_summary">Show the Spectrum Analyzer.</string> <string name="pref_visualizer_summary">Show the Spectrum Analyzer.</string>
<!-- Custom Theme -->
<string name="pref_custom_theme_enabled_summary">Enables the custom theme tab.</string>
<string name="pref_custom_theme_disabled_summary">Disables the custom theme tab.</string>
<string name="pref_custom_theme_summary">Customize the application background color.</string>
<string name="pref_custom_theme_title">Custom Theme</string>
<string name="pref_custom_theme_enabled_title">Enable Custom Theme</string>
<string name="theme_predefined_colors">Predefined Colors (Light/Dark):</string>
<string name="theme_custom_rgb">Custom RGB (please adapt to the app):</string>
<string name="theme_red">Red</string>
<string name="theme_green">Green</string>
<string name="theme_blue">Blue</string>
<string name="hex_code">Hex Code</string>
</resources> </resources>
+8 -8
View File
@@ -1,16 +1,16 @@
[versions] [versions]
activityKtx = "1.13.0" activityKtx = "1.13.0"
agp = "9.1.1" agp = "9.2.1"
coreKtx = "1.18.0" coreKtx = "1.18.0"
freedroidwarn = "V1.11" freedroidwarn = "V1.13"
gson = "2.13.2" gson = "2.14.0"
kotlin = "2.3.20" kotlin = "2.3.21"
leanback = "1.2.0" leanback = "1.2.0"
material = "1.13.0" material = "1.14.0"
material3 = "1.4.0" material3 = "1.4.0"
media = "1.7.1" media = "1.8.0"
media3 = "1.10.0" media3 = "1.10.1"
navigation = "2.9.7" navigation = "2.9.8"
paletteKtx = "1.0.0" paletteKtx = "1.0.0"
preferenceKtx = "1.2.1" preferenceKtx = "1.2.1"
volley = "1.2.1" volley = "1.2.1"
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+1 -1
View File
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
Vendored
+10 -21
View File
@@ -23,8 +23,8 @@
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables, and ensure extensions are enabled
if "%OS%"=="Windows_NT" setlocal setlocal EnableExtensions
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,7 +65,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:execute :execute
@rem Setup the command line @rem Setup the command line
@@ -73,21 +73,10 @@ goto fail
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end :exitWithErrorLevel
@rem End local scope for the variables with windows NT shell @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
if %ERRORLEVEL% equ 0 goto mainEnd "%COMSPEC%" /c exit %ERRORLEVEL%
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+1
View File
@@ -13,6 +13,7 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
gradlePluginPortal()
maven { url = uri("https://jitpack.io") } maven { url = uri("https://jitpack.io") }
} }
} }