mirror of
https://github.com/Michatec/Radio.git
synced 2026-03-31 23:46:28 +02:00
Compare commits
55 Commits
14
...
9140b54a23
| Author | SHA1 | Date | |
|---|---|---|---|
| 9140b54a23 | |||
| 57c4075f19 | |||
| 5334b88f1d | |||
| 5978aab0aa | |||
|
|
c3c2ccfdd9 | ||
|
|
38fb4d162b | ||
|
|
3c3c18104b | ||
|
|
4e1ee7d6a7 | ||
|
|
64ba020eae | ||
|
|
b35d7bd67c | ||
|
|
d3dd098639 | ||
|
|
a8f6c7f946 | ||
|
|
765ccc9250 | ||
|
|
c9d6cf27ec | ||
|
|
7b7579d416 | ||
|
|
d452d7f7ee | ||
|
|
2bb3d22cab | ||
|
|
b4ed3e107c | ||
|
|
ef843b601a | ||
|
|
219d54f4e4 | ||
|
|
c8a39bf2d7 | ||
|
|
81ff920c2c | ||
|
|
f4a5209e14 | ||
|
|
c7b7bdcbed | ||
|
|
eae9176f21 | ||
|
|
b3a833fa44 | ||
|
|
35a8ed46ff | ||
|
|
fd18943878 | ||
|
|
094fb508e2 | ||
|
|
874cf1eb39 | ||
|
|
211cb387ad | ||
|
|
5481a06343 | ||
| fbafcb9773 | |||
| 27c405fd58 | |||
|
|
c2cba26c66 | ||
|
|
9224e45b18 | ||
|
|
90164d7e6a | ||
|
|
45e208e7a9 | ||
|
|
1d040e3edc | ||
|
|
efe17bc82b | ||
|
|
8b06309c64 | ||
| 669fd4683c | |||
| 26b155a721 | |||
|
|
99f7863749 | ||
|
|
008170811a | ||
|
|
c0072fe54f | ||
| c62c61f2f1 | |||
| 5ca2b9b7ef | |||
|
|
9b5c7e3c04 | ||
|
|
4466654e92 | ||
|
|
b2de7bd534 | ||
|
|
d3dfcb98f9 | ||
|
|
0adb906438 | ||
|
|
49e63d3aaa | ||
|
|
f892a137ce |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
35
.github/workflows/gradle-publish.yml
vendored
35
.github/workflows/gradle-publish.yml
vendored
@@ -51,6 +51,10 @@ jobs:
|
||||
- name: Check APK path
|
||||
run: ls -R app/build/outputs/apk
|
||||
|
||||
- name: Zipalign APK
|
||||
run: |
|
||||
/usr/local/lib/android/sdk/build-tools/34.0.0/zipalign -v -p 4 ${{ env.APK_PATH }} app-release-aligned.apk
|
||||
|
||||
- name: Sign APK
|
||||
env:
|
||||
SIGN_CERT: ${{ secrets.SIGN_CERT }}
|
||||
@@ -58,35 +62,12 @@ jobs:
|
||||
run: |
|
||||
echo "$SIGN_CERT" | base64 -d > cert.der
|
||||
echo "$SIGN_KEY" | base64 -d > key.der
|
||||
mv ${{ env.APK_PATH }} app-release.apk
|
||||
${{ env.APKSIGNER }} sign --key key.der --cert cert.der app-release.apk
|
||||
${{ env.APKSIGNER }} sign --key key.der --cert cert.der app-release-aligned.apk
|
||||
rm cert.der key.der
|
||||
|
||||
- name: Zipalign APK
|
||||
run: |
|
||||
/usr/local/lib/android/sdk/build-tools/34.0.0/zipalign -v -p 4 app-release.apk app-release-aligned.apk
|
||||
mv app-release-aligned.apk app-release.apk
|
||||
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: app-release
|
||||
path: app-release.apk
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: app-release
|
||||
path: app-release.apk
|
||||
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "app-release.apk"
|
||||
draft: true
|
||||
path: app-release.apk
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -4,4 +4,6 @@
|
||||
/local.properties
|
||||
/.idea
|
||||
/build
|
||||
/captures
|
||||
/captures
|
||||
/gradle/gradle-daemon-jvm.properties
|
||||
/.kotlin
|
||||
@@ -1,7 +1,7 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright (c) 2025 - Michatec
|
||||
Copyright (c) 2026 - Michatec
|
||||
--------------------------------
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<details>
|
||||
<summary>⚙️ Install Radio</summary>
|
||||
<br>
|
||||
<a href="https://github.com/michatec/Radio/releases/latest"><img src="https://user-images.githubusercontent.com/15986930/229208526-e5a63be5-0d0b-48ab-a222-9f2f2faf0ee4.png" height="80px"></a>
|
||||
<a href="https://github.com/michatec/Radio/releases/latest"><img src="https://user-images.githubusercontent.com/15986930/229208526-e5a63be5-0d0b-48ab-a222-9f2f2faf0ee4.png" alt="Preview" height="80px"></a>
|
||||
</details>
|
||||
|
||||
----------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
alias libs.plugins.android.application
|
||||
id 'kotlin-parcelize'
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ android {
|
||||
applicationId 'com.michatec.radio'
|
||||
minSdk 28
|
||||
targetSdk 36
|
||||
versionCode 140
|
||||
versionName '14'
|
||||
versionCode 143
|
||||
versionName '14.3'
|
||||
resourceConfigurations += ['en', 'de', 'el', 'nl', 'pl', 'ru','uk', 'ja', 'da', 'fr']
|
||||
}
|
||||
|
||||
@@ -55,24 +55,26 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
// Google Stuff //
|
||||
implementation 'com.google.android.material:material:1.13.0'
|
||||
implementation 'com.google.code.gson:gson:2.13.2'
|
||||
implementation libs.material
|
||||
implementation libs.gson
|
||||
|
||||
// AndroidX Stuff //
|
||||
implementation 'androidx.core:core-ktx:1.17.0'
|
||||
implementation 'androidx.activity:activity-ktx:1.12.2'
|
||||
implementation 'androidx.palette:palette-ktx:1.0.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.media:media:1.7.1'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.9.0'
|
||||
implementation 'androidx.media3:media3-exoplayer-hls:1.9.0'
|
||||
implementation 'androidx.media3:media3-session:1.9.0'
|
||||
implementation 'androidx.media3:media3-datasource-okhttp:1.9.0'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.9.6'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.9.6'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.11.0'
|
||||
implementation libs.core.ktx
|
||||
implementation libs.activity.ktx
|
||||
implementation libs.palette.ktx
|
||||
implementation libs.preference.ktx
|
||||
implementation libs.media
|
||||
implementation libs.media3.exoplayer
|
||||
implementation libs.media3.exoplayer.hls
|
||||
implementation libs.media3.session
|
||||
implementation libs.media3.datasource.okhttp
|
||||
implementation libs.navigation.fragment.ktx
|
||||
implementation libs.navigation.ui.ktx
|
||||
implementation libs.work.runtime.ktx
|
||||
|
||||
implementation libs.freedroidwarn
|
||||
|
||||
// Volley HTTP request //
|
||||
implementation 'com.android.volley:volley:1.2.1'
|
||||
implementation 'androidx.compose.material3:material3:1.4.0'
|
||||
implementation libs.volley
|
||||
implementation libs.material3
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="tiramisu">
|
||||
tools:targetApi="33">
|
||||
|
||||
<!-- ANDROID AUTO SUPPORT -->
|
||||
<!-- https://developer.android.com/training/auto/audio/ -->
|
||||
@@ -58,7 +58,8 @@
|
||||
</intent-filter>
|
||||
|
||||
<!-- react to playlist-links based on file extension -->
|
||||
<intent-filter>
|
||||
<!-- This is intended as an App Link for specific extensions -->
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
@@ -66,14 +67,16 @@
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="*" />
|
||||
<data android:host="*"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data android:pathPattern=".*\\.m3u" />
|
||||
<data android:pathPattern=".*\\.m3u8" />
|
||||
<data android:pathPattern=".*\\.pls" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- react to playlist-links based on mimetype -->
|
||||
<intent-filter>
|
||||
<!-- Note: MIME types prevent strict App Link verification, but are kept as requested -->
|
||||
<intent-filter android:autoVerify="false">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
@@ -81,7 +84,8 @@
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="*" />
|
||||
<data android:host="*"
|
||||
tools:ignore="AppLinkUrlError" />
|
||||
<data android:mimeType="audio/x-scpls" />
|
||||
<data android:mimeType="audio/mpegurl" />
|
||||
<data android:mimeType="audio/x-mpegurl" />
|
||||
|
||||
@@ -60,7 +60,6 @@ object Keys {
|
||||
|
||||
// preferences
|
||||
const val PREF_RADIO_BROWSER_API: String = "RADIO_BROWSER_API"
|
||||
const val PREF_ONE_TIME_HOUSEKEEPING_NECESSARY: String = "ONE_TIME_HOUSEKEEPING_NECESSARY_VERSIONCODE_95" // increment to current app version code to trigger housekeeping that runs only once
|
||||
const val PREF_THEME_SELECTION: String = "THEME_SELECTION"
|
||||
const val PREF_LAST_UPDATE_COLLECTION: String = "LAST_UPDATE_COLLECTION"
|
||||
const val PREF_COLLECTION_SIZE: String = "COLLECTION_SIZE"
|
||||
@@ -79,7 +78,6 @@ object Keys {
|
||||
// default const values
|
||||
const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25
|
||||
const val DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY: Int = 127
|
||||
const val DEFAULT_DOWNLOAD_OVER_MOBILE: Boolean = false
|
||||
const val ACTIVE_DOWNLOADS_EMPTY: String = "zero"
|
||||
const val DEFAULT_MAX_RECONNECTION_COUNT: Int = 30
|
||||
const val LARGE_BUFFER_SIZE_MULTIPLIER: Int = 8
|
||||
@@ -138,7 +136,7 @@ object Keys {
|
||||
const val FOLDER_AUDIO: String = "audio"
|
||||
const val FOLDER_IMAGES: String = "images"
|
||||
const val FOLDER_TEMP: String = "temp"
|
||||
const val URLRADIO_LEGACY_FOLDER_COLLECTION: String = "Collection"
|
||||
const val RADIO_LEGACY_FOLDER_COLLECTION: String = "Collection"
|
||||
|
||||
// file names and extensions
|
||||
const val COLLECTION_FILE: String = "collection.json"
|
||||
@@ -150,9 +148,6 @@ object Keys {
|
||||
const val RADIO_BROWSER_API_BASE: String = "all.api.radio-browser.info"
|
||||
const val RADIO_BROWSER_API_DEFAULT: String = "de1.api.radio-browser.info"
|
||||
|
||||
// locations
|
||||
const val LOCATION_DEFAULT_STATION_IMAGE: String = "android.resource://com.michatec.radio/drawable/ic_default_station_image_24dp"
|
||||
|
||||
// sizes (in dp)
|
||||
const val SIZE_STATION_IMAGE_CARD: Int = 72
|
||||
const val SIZE_STATION_IMAGE_MAXIMUM: Int = 640
|
||||
|
||||
@@ -24,9 +24,8 @@ import androidx.navigation.ui.NavigationUI
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import com.michatec.radio.helpers.AppThemeHelper
|
||||
import com.michatec.radio.helpers.FileHelper
|
||||
import com.michatec.radio.helpers.ImportHelper
|
||||
import com.michatec.radio.helpers.PreferencesHelper
|
||||
|
||||
import org.woheller69.freeDroidWarn.FreeDroidWarn
|
||||
|
||||
/*
|
||||
* MainActivity class
|
||||
@@ -40,18 +39,9 @@ class MainActivity : AppCompatActivity() {
|
||||
/* Overrides onCreate from AppCompatActivity */
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// house-keeping: determine if edit stations is enabled by default todo: remove in 2023
|
||||
if (PreferencesHelper.isHouseKeepingNecessary()) {
|
||||
// house-keeping 1: remove hard coded default image
|
||||
ImportHelper.removeDefaultStationImageUris(this)
|
||||
// house-keeping 2: if existing user detected, enable Edit Stations by default
|
||||
if (PreferencesHelper.loadCollectionSize() != -1) {
|
||||
// existing user detected - enable Edit Stations by default
|
||||
PreferencesHelper.saveEditStationsEnabled(true)
|
||||
}
|
||||
PreferencesHelper.saveHouseKeepingNecessaryState()
|
||||
}
|
||||
|
||||
// Free Android
|
||||
FreeDroidWarn.showWarningOnUpgrade(this, BuildConfig.VERSION_CODE)
|
||||
|
||||
// set up views
|
||||
setContentView(R.layout.activity_main)
|
||||
@@ -73,6 +63,16 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
|
||||
/* Overrides onResume from AppCompatActivity */
|
||||
override fun onResume() {
|
||||
try {
|
||||
super.onResume()
|
||||
} catch (_: ClassCastException) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Overrides onSupportNavigateUp from AppCompatActivity */
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
// Taken from: https://developer.android.com/guide/navigation/navigation-ui#action_bar
|
||||
|
||||
@@ -265,7 +265,7 @@ class PlayerFragment : Fragment(),
|
||||
// handle navigation arguments
|
||||
handleNavigationArguments()
|
||||
// // handle start intent - if started via tap on rss link
|
||||
// handleStartIntent()
|
||||
handleStartIntent()
|
||||
// start watching for changes in shared preferences
|
||||
PreferencesHelper.registerPreferenceChangeListener(this as SharedPreferences.OnSharedPreferenceChangeListener)
|
||||
}
|
||||
@@ -636,16 +636,16 @@ class PlayerFragment : Fragment(),
|
||||
collectionViewModel.collectionLiveData.observe(this) {
|
||||
// update collection
|
||||
collection = it
|
||||
//// // updates current station in player views
|
||||
//// playerState = PreferencesHelper.loadPlayerState()
|
||||
// updates current station in player views
|
||||
playerState = PreferencesHelper.loadPlayerState()
|
||||
// // get station
|
||||
// val station: Station = CollectionHelper.getStation(collection, playerState.stationUuid)
|
||||
val station: Station = CollectionHelper.getStation(collection, playerState.stationUuid)
|
||||
// // update player views
|
||||
// layout.updatePlayerViews(activity as Context, station, playerState.isPlaying)
|
||||
//// // handle start intent
|
||||
//// handleStartIntent()
|
||||
//// // handle navigation arguments
|
||||
//// handleNavigationArguments()
|
||||
layout.updatePlayerViews(activity as Context, station, playerState.isPlaying)
|
||||
// handle start intent
|
||||
handleStartIntent()
|
||||
// handle navigation arguments
|
||||
handleNavigationArguments()
|
||||
}
|
||||
collectionViewModel.collectionSizeLiveData.observe(this) {
|
||||
// size of collection changed
|
||||
|
||||
@@ -280,19 +280,8 @@ class PlayerService : MediaLibraryService() {
|
||||
val updatedMediaItems: List<MediaItem> =
|
||||
mediaItems.map { mediaItem ->
|
||||
CollectionHelper.getItem(this@PlayerService, collection, mediaItem.mediaId)
|
||||
// if (mediaItem.requestMetadata.searchQuery != null)
|
||||
// getMediaItemFromSearchQuery(mediaItem.requestMetadata.searchQuery!!)
|
||||
// else MediaItemTree.getItem(mediaItem.mediaId) ?: mediaItem
|
||||
}
|
||||
return Futures.immediateFuture(updatedMediaItems)
|
||||
|
||||
|
||||
// val updatedMediaItems = mediaItems.map { mediaItem ->
|
||||
// mediaItem.buildUpon().apply {
|
||||
// setUri(mediaItem.requestMetadata.mediaUri)
|
||||
// }.build()
|
||||
// }
|
||||
// return Futures.immediateFuture(updatedMediaItems)
|
||||
}
|
||||
|
||||
|
||||
@@ -411,19 +400,19 @@ class PlayerService : MediaLibraryService() {
|
||||
customLayout: ImmutableList<CommandButton>,
|
||||
showPauseButton: Boolean
|
||||
): ImmutableList<CommandButton> {
|
||||
val seekToPreviousCommandButton = CommandButton.Builder()
|
||||
val seekToPreviousCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS)
|
||||
.setIconResId(R.drawable.ic_notification_skip_to_previous_36dp)
|
||||
.setCustomIconResId(R.drawable.ic_notification_skip_to_previous_36dp)
|
||||
.setEnabled(true)
|
||||
.build()
|
||||
val playCommandButton = CommandButton.Builder()
|
||||
val playCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
|
||||
.setIconResId(if (player.isPlaying) R.drawable.ic_notification_stop_36dp else R.drawable.ic_notification_play_36dp)
|
||||
.setCustomIconResId(if (player.isPlaying) R.drawable.ic_notification_stop_36dp else R.drawable.ic_notification_play_36dp)
|
||||
.setEnabled(true)
|
||||
.build()
|
||||
val seekToNextCommandButton = CommandButton.Builder()
|
||||
val seekToNextCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED)
|
||||
.setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT)
|
||||
.setIconResId(R.drawable.ic_notification_skip_to_next_36dp)
|
||||
.setCustomIconResId(R.drawable.ic_notification_skip_to_next_36dp)
|
||||
.setEnabled(true)
|
||||
.build()
|
||||
val commandButtons: MutableList<CommandButton> = mutableListOf(
|
||||
@@ -498,15 +487,10 @@ class PlayerService : MediaLibraryService() {
|
||||
if (!playWhenReady) {
|
||||
when (reason) {
|
||||
Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM -> {
|
||||
// playback reached end: stop / end playback
|
||||
stopSelf()
|
||||
}
|
||||
else -> {
|
||||
// playback has been paused by user or OS: update media session and save state
|
||||
// PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST or
|
||||
// PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS or
|
||||
// PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY or
|
||||
// PLAY_WHEN_READY_CHANGE_REASON_REMOTE
|
||||
// handlePlaybackChange(PlaybackStateCompat.STATE_PAUSED)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -585,7 +569,6 @@ class PlayerService : MediaLibraryService() {
|
||||
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName)
|
||||
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
|
||||
sendBroadcast(intent)
|
||||
// note: remember to broadcast AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, when not needed anymore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,4 @@ class Radio : Application() {
|
||||
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
||||
}
|
||||
|
||||
|
||||
/* Implements onTerminate */
|
||||
override fun onTerminate() {
|
||||
super.onTerminate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.*
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -116,16 +115,21 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
}
|
||||
|
||||
|
||||
// // set up "Update Stations" preference
|
||||
// val preferenceUpdateCollection: Preference = Preference(activity as Context)
|
||||
// preferenceUpdateCollection.title = getString(R.string.pref_update_collection_title)
|
||||
// preferenceUpdateCollection.setIcon(R.drawable.ic_refresh_24dp)
|
||||
// preferenceUpdateCollection.summary = getString(R.string.pref_update_collection_summary)
|
||||
// preferenceUpdateCollection.setOnPreferenceClickListener {
|
||||
// // show dialog
|
||||
// YesNoDialog(this).show(context = activity as Context, type = Keys.DIALOG_UPDATE_COLLECTION, message = R.string.dialog_yes_no_message_update_collection, yesButton = R.string.dialog_yes_no_positive_button_update_collection)
|
||||
// return@setOnPreferenceClickListener true
|
||||
// }
|
||||
// set up "Update Stations" preference
|
||||
val preferenceUpdateCollection = Preference(activity as Context)
|
||||
preferenceUpdateCollection.title = getString(R.string.pref_update_collection_title)
|
||||
preferenceUpdateCollection.setIcon(R.drawable.ic_refresh_24dp)
|
||||
preferenceUpdateCollection.summary = getString(R.string.pref_update_collection_summary)
|
||||
preferenceUpdateCollection.setOnPreferenceClickListener {
|
||||
// show dialog
|
||||
YesNoDialog(this).show(
|
||||
context = activity as Context,
|
||||
type = Keys.DIALOG_UPDATE_COLLECTION,
|
||||
message = R.string.dialog_yes_no_message_update_collection,
|
||||
yesButton = R.string.dialog_yes_no_positive_button_update_collection,
|
||||
)
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
|
||||
// set up "M3U Export" preference
|
||||
@@ -299,7 +303,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
screen.addPreference(preferenceThemeSelection)
|
||||
screen.addPreference(preferenceCategoryMaintenance)
|
||||
screen.addPreference(preferenceUpdateStationImages)
|
||||
// screen.addPreference(preferenceUpdateCollection)
|
||||
screen.addPreference(preferenceUpdateCollection)
|
||||
screen.addPreference(preferenceCategoryImportExport)
|
||||
screen.addPreference(preferenceM3uExport)
|
||||
screen.addPreference(preferencePlsExport)
|
||||
@@ -422,9 +426,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
val sourceUri: Uri? = result.data?.data
|
||||
if (sourceUri != null) {
|
||||
// open and import OPML in player fragment
|
||||
val bundle: Bundle = bundleOf(
|
||||
Keys.ARG_RESTORE_COLLECTION to "$sourceUri"
|
||||
)
|
||||
val bundle = Bundle().apply {
|
||||
putString(Keys.ARG_RESTORE_COLLECTION, "$sourceUri")
|
||||
}
|
||||
this.findNavController().navigate(R.id.player_destination, bundle)
|
||||
}
|
||||
}
|
||||
@@ -440,7 +444,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
// update collection in player screen
|
||||
val bundle: Bundle = bundleOf(Keys.ARG_UPDATE_COLLECTION to true)
|
||||
val bundle = Bundle().apply {
|
||||
putBoolean(Keys.ARG_UPDATE_COLLECTION, true)
|
||||
}
|
||||
this.findNavController().navigate(R.id.player_destination, bundle)
|
||||
} else {
|
||||
ErrorDialog().show(
|
||||
@@ -461,9 +467,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
// update collection in player screen
|
||||
val bundle: Bundle = bundleOf(
|
||||
Keys.ARG_UPDATE_IMAGES to true
|
||||
)
|
||||
val bundle = Bundle().apply {
|
||||
putBoolean(Keys.ARG_UPDATE_IMAGES, true)
|
||||
}
|
||||
this.findNavController().navigate(R.id.player_destination, bundle)
|
||||
} else {
|
||||
ErrorDialog().show(
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
@@ -193,6 +194,8 @@ class CollectionAdapter(
|
||||
setStationImage(stationViewHolder, station)
|
||||
setStationButtons(stationViewHolder, station)
|
||||
setEditViews(stationViewHolder, station)
|
||||
setPlaybackProgress(stationViewHolder, station)
|
||||
setDownloadProgress(stationViewHolder, station)
|
||||
|
||||
// show / hide edit views
|
||||
when (expandedStationPosition) {
|
||||
@@ -248,6 +251,28 @@ class CollectionAdapter(
|
||||
}
|
||||
|
||||
|
||||
/* Sets the playback progress view */
|
||||
private fun setPlaybackProgress(stationViewHolder: StationViewHolder, station: Station) {
|
||||
if (station.bufferingProgress > 0) {
|
||||
stationViewHolder.bufferingProgress.progress = station.bufferingProgress
|
||||
stationViewHolder.bufferingProgress.isVisible = true
|
||||
} else {
|
||||
stationViewHolder.bufferingProgress.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Sets the download progress view */
|
||||
private fun setDownloadProgress(stationViewHolder: StationViewHolder, station: Station) {
|
||||
if (station.downloadProgress > 0) {
|
||||
stationViewHolder.downloadProgress.progress = station.downloadProgress
|
||||
stationViewHolder.downloadProgress.isVisible = true
|
||||
} else {
|
||||
stationViewHolder.downloadProgress.isGone = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Sets the edit views */
|
||||
private fun setEditViews(stationViewHolder: StationViewHolder, station: Station) {
|
||||
stationViewHolder.stationNameEditView.setText(station.name, TextView.BufferType.EDITABLE)
|
||||
@@ -471,24 +496,25 @@ class CollectionAdapter(
|
||||
|
||||
} else if (holder is StationViewHolder) {
|
||||
// get station from position
|
||||
collection.stations[holder.bindingAdapterPosition]
|
||||
val station: Station = collection.stations[holder.bindingAdapterPosition]
|
||||
|
||||
for (data in payloads) {
|
||||
when (data as Int) {
|
||||
Keys.HOLDER_UPDATE_COVER -> {
|
||||
// todo implement
|
||||
setStationImage(holder, station)
|
||||
setStarredIcon(holder, station)
|
||||
}
|
||||
Keys.HOLDER_UPDATE_NAME -> {
|
||||
// todo implement
|
||||
setStationName(holder, station)
|
||||
}
|
||||
Keys.HOLDER_UPDATE_PLAYBACK_STATE -> {
|
||||
// todo implement
|
||||
setStationButtons(holder, station)
|
||||
}
|
||||
Keys.HOLDER_UPDATE_PLAYBACK_PROGRESS -> {
|
||||
// todo implement
|
||||
setPlaybackProgress(holder, station)
|
||||
}
|
||||
Keys.HOLDER_UPDATE_DOWNLOAD_STATE -> {
|
||||
// todo implement
|
||||
setDownloadProgress(holder, station)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -575,21 +601,6 @@ class CollectionAdapter(
|
||||
}
|
||||
|
||||
|
||||
// /* Initiates update of a station's information */ // todo move to CollectionHelper
|
||||
// private fun updateStation(context: Context, station: Station) {
|
||||
// if (station.radioBrowserStationUuid.isNotEmpty()) {
|
||||
// // get updated station from radio browser - results are handled by onRadioBrowserSearchResults
|
||||
// val radioBrowserSearch: RadioBrowserSearch = RadioBrowserSearch(context, this)
|
||||
// radioBrowserSearch.searchStation(context, station.radioBrowserStationUuid, Keys.SEARCH_TYPE_BY_UUID)
|
||||
// } else if (station.remoteStationLocation.isNotEmpty()) {
|
||||
// // download playlist // todo check content type detection is necessary here
|
||||
// DownloadHelper.downloadPlaylists(context, arrayOf(station.remoteStationLocation))
|
||||
// } else {
|
||||
// Log.w(TAG, "Unable to update station: ${station.name}.")
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/* Determines if position is last */
|
||||
private fun isPositionFooter(position: Int): Boolean {
|
||||
return position == collection.stations.size
|
||||
@@ -600,7 +611,7 @@ class CollectionAdapter(
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun updateRecyclerView(oldCollection: Collection, newCollection: Collection) {
|
||||
collection = newCollection
|
||||
if (oldCollection.stations.size == 0 && newCollection.stations.size > 0) {
|
||||
if (oldCollection.stations.isEmpty() && newCollection.stations.isNotEmpty()) {
|
||||
// data set has been initialized - redraw the whole list
|
||||
notifyDataSetChanged()
|
||||
} else {
|
||||
@@ -672,6 +683,8 @@ class CollectionAdapter(
|
||||
val stationImageView: ImageView = stationCardLayout.findViewById(R.id.station_icon)
|
||||
val stationNameView: TextView = stationCardLayout.findViewById(R.id.station_name)
|
||||
val stationStarredView: ImageView = stationCardLayout.findViewById(R.id.starred_icon)
|
||||
val bufferingProgress: ProgressBar = stationCardLayout.findViewById(R.id.buffering_progress)
|
||||
val downloadProgress: ProgressBar = stationCardLayout.findViewById(R.id.download_progress)
|
||||
|
||||
// val menuButtonView: ImageView = stationCardLayout.findViewById(R.id.menu_button)
|
||||
val playButtonView: ImageView = stationCardLayout.findViewById(R.id.playback_button)
|
||||
|
||||
@@ -48,7 +48,9 @@ data class Station(
|
||||
@Expose var radioBrowserStationUuid: String = String(),
|
||||
@Expose var radioBrowserChangeUuid: String = String(),
|
||||
@Expose var bitrate: Int = 0,
|
||||
@Expose var codec: String = String()
|
||||
@Expose var codec: String = String(),
|
||||
@Expose var bufferingProgress: Int = 0,
|
||||
@Expose var downloadProgress: Int = 0
|
||||
) : Parcelable {
|
||||
|
||||
|
||||
@@ -79,6 +81,12 @@ data class Station(
|
||||
}
|
||||
|
||||
|
||||
/* Getter for media type */
|
||||
fun getMediaType(): String {
|
||||
return streamContent
|
||||
}
|
||||
|
||||
|
||||
/* Creates a deep copy of a Station */
|
||||
fun deepCopy(): Station {
|
||||
return Station(
|
||||
@@ -101,7 +109,9 @@ data class Station(
|
||||
radioBrowserStationUuid = radioBrowserStationUuid,
|
||||
radioBrowserChangeUuid = radioBrowserChangeUuid,
|
||||
bitrate = bitrate,
|
||||
codec = codec
|
||||
codec = codec,
|
||||
bufferingProgress = bufferingProgress,
|
||||
downloadProgress = downloadProgress
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.text.method.ScrollingMovementMethod
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -81,8 +82,7 @@ class ErrorDialog {
|
||||
|
||||
// add okay button
|
||||
builder.setPositiveButton(R.string.dialog_generic_button_okay) { _, _ ->
|
||||
// listen for click on okay button
|
||||
// do nothing
|
||||
Toast.makeText(context, R.string.dialog_generic_button_okay, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// display error dialog
|
||||
|
||||
@@ -16,7 +16,6 @@ package com.michatec.radio.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.media3.session.MediaController
|
||||
import androidx.media3.session.SessionCommand
|
||||
import androidx.media3.session.SessionResult
|
||||
@@ -71,8 +70,11 @@ fun MediaController.play(context: Context, station: Station) {
|
||||
|
||||
/* Starts playback with of a stream url */
|
||||
fun MediaController.playStreamDirectly(streamUri: String) {
|
||||
val bundle = Bundle().apply {
|
||||
putString(Keys.KEY_STREAM_URI, streamUri)
|
||||
}
|
||||
sendCustomCommand(
|
||||
SessionCommand(Keys.CMD_PLAY_STREAM, Bundle.EMPTY),
|
||||
bundleOf(Pair(Keys.KEY_STREAM_URI, streamUri))
|
||||
bundle
|
||||
)
|
||||
}
|
||||
@@ -20,6 +20,8 @@ import androidx.media3.common.Metadata
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.extractor.metadata.icy.IcyHeaders
|
||||
import androidx.media3.extractor.metadata.icy.IcyInfo
|
||||
import androidx.media3.extractor.metadata.id3.Id3Frame
|
||||
import androidx.media3.extractor.metadata.id3.TextInformationFrame
|
||||
import com.michatec.radio.Keys
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -37,28 +39,51 @@ object AudioHelper {
|
||||
/* Extract audio stream metadata */
|
||||
@OptIn(UnstableApi::class)
|
||||
fun getMetadataString(metadata: Metadata): String {
|
||||
var metadataString = String()
|
||||
var title = ""
|
||||
var artist = ""
|
||||
var album = ""
|
||||
for (i in 0 until metadata.length()) {
|
||||
// extract IceCast metadata
|
||||
when (val entry = metadata.get(i)) {
|
||||
is IcyInfo -> {
|
||||
metadataString = entry.title.toString()
|
||||
title = entry.title.toString()
|
||||
}
|
||||
|
||||
is IcyHeaders -> {
|
||||
Log.i(TAG, "icyHeaders:" + entry.name + " - " + entry.genre)
|
||||
}
|
||||
|
||||
is Id3Frame -> {
|
||||
when (entry) {
|
||||
is TextInformationFrame -> {
|
||||
when (entry.id) {
|
||||
"TIT2" -> title = entry.values.getOrNull(0) ?: "" // Title
|
||||
"TPE1" -> artist = entry.values.getOrNull(0) ?: "" // Artist
|
||||
"TALB" -> album = entry.values.getOrNull(0) ?: "" // Album
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Unhandled ID3 frame: ${entry.javaClass.simpleName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Unsupported metadata received (type = ${entry.javaClass.simpleName})")
|
||||
}
|
||||
}
|
||||
// TODO implement HLS metadata extraction (Id3Frame / PrivFrame)
|
||||
// https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html
|
||||
}
|
||||
// Build metadata string
|
||||
var metadataString = title
|
||||
if (artist.isNotEmpty() && title.isNotEmpty()) {
|
||||
metadataString = "$artist - $title"
|
||||
}
|
||||
if (album.isNotEmpty() && metadataString.isNotEmpty()) {
|
||||
metadataString += " ($album)"
|
||||
}
|
||||
// ensure a max length of the metadata string
|
||||
if (metadataString.isNotEmpty()) {
|
||||
metadataString = metadataString.substring(0, min(metadataString.length, Keys.DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY))
|
||||
metadataString = metadataString.take(min(metadataString.length, Keys.DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY))
|
||||
}
|
||||
return metadataString
|
||||
}
|
||||
|
||||
@@ -644,51 +644,8 @@ object CollectionHelper {
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(collectionChangedIntent)
|
||||
}
|
||||
|
||||
|
||||
// /* Creates MediaMetadata for a single station - used in media session*/
|
||||
// fun buildStationMediaMetadata(context: Context, station: Station, metadata: String): MediaMetadataCompat {
|
||||
// return MediaMetadataCompat.Builder().apply {
|
||||
// putString(MediaMetadataCompat.METADATA_KEY_ARTIST, station.name)
|
||||
// putString(MediaMetadataCompat.METADATA_KEY_TITLE, metadata)
|
||||
// putString(MediaMetadataCompat.METADATA_KEY_ALBUM, context.getString(R.string.app_name))
|
||||
// putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, station.getStreamUri())
|
||||
// putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN))
|
||||
// //putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, station.image)
|
||||
// }.build()
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /* Creates MediaItem for a station - used by collection provider */
|
||||
// fun buildStationMediaMetaItem(context: Context, station: Station): MediaBrowserCompat.MediaItem {
|
||||
// val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
|
||||
// mediaDescriptionBuilder.setMediaId(station.uuid)
|
||||
// mediaDescriptionBuilder.setTitle(station.name)
|
||||
// mediaDescriptionBuilder.setIconBitmap(ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN))
|
||||
// // mediaDescriptionBuilder.setIconUri(station.image.toUri())
|
||||
// return MediaBrowserCompat.MediaItem(mediaDescriptionBuilder.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||
// }
|
||||
//
|
||||
//
|
||||
// /* Creates description for a station - used in MediaSessionConnector */
|
||||
// fun buildStationMediaDescription(context: Context, station: Station, metadata: String): MediaDescriptionCompat {
|
||||
// val coverBitmap: Bitmap = ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN)
|
||||
// val extras: Bundle = Bundle()
|
||||
// extras.putParcelable(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, coverBitmap)
|
||||
// extras.putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, coverBitmap)
|
||||
// return MediaDescriptionCompat.Builder().apply {
|
||||
// setMediaId(station.uuid)
|
||||
// setIconBitmap(coverBitmap)
|
||||
// setIconUri(station.image.toUri())
|
||||
// setTitle(metadata)
|
||||
// setSubtitle(station.name)
|
||||
// setExtras(extras)
|
||||
// }.build()
|
||||
// }
|
||||
|
||||
|
||||
/* Creates a MediaItem with MediaMetadata for a single radio station - used to prepare player */
|
||||
fun buildMediaItem(context: Context, station: Station): MediaItem {
|
||||
// todo implement HLS MediaItems
|
||||
// put uri in RequestMetadata - credit: https://stackoverflow.com/a/70103460
|
||||
val requestMetadata = MediaItem.RequestMetadata.Builder().apply {
|
||||
setMediaUri(station.getStreamUri().toUri())
|
||||
@@ -713,7 +670,7 @@ object CollectionHelper {
|
||||
setMediaId(station.uuid)
|
||||
setRequestMetadata(requestMetadata)
|
||||
setMediaMetadata(mediaMetadata)
|
||||
//setMimeType(station.getMediaType())
|
||||
setMimeType(station.getMediaType())
|
||||
setUri(station.getStreamUri().toUri())
|
||||
}.build()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import kotlinx.coroutines.Dispatchers.IO
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
/*
|
||||
@@ -246,7 +245,7 @@ object FileHelper {
|
||||
File(activity.getExternalFilesDir(Keys.FOLDER_COLLECTION), Keys.COLLECTION_M3U_FILE)
|
||||
if (!m3uFile.exists()) {
|
||||
m3uFile = File(
|
||||
activity.getExternalFilesDir(Keys.URLRADIO_LEGACY_FOLDER_COLLECTION),
|
||||
activity.getExternalFilesDir(Keys.RADIO_LEGACY_FOLDER_COLLECTION),
|
||||
Keys.COLLECTION_M3U_FILE
|
||||
)
|
||||
}
|
||||
@@ -270,7 +269,7 @@ object FileHelper {
|
||||
File(activity.getExternalFilesDir(Keys.FOLDER_COLLECTION), Keys.COLLECTION_PLS_FILE)
|
||||
if (!plsFile.exists()) {
|
||||
plsFile = File(
|
||||
activity.getExternalFilesDir(Keys.URLRADIO_LEGACY_FOLDER_COLLECTION),
|
||||
activity.getExternalFilesDir(Keys.RADIO_LEGACY_FOLDER_COLLECTION),
|
||||
Keys.COLLECTION_PLS_FILE
|
||||
)
|
||||
}
|
||||
@@ -292,7 +291,7 @@ object FileHelper {
|
||||
collection: Collection,
|
||||
lastUpdate: Date
|
||||
) {
|
||||
return suspendCoroutine { cont ->
|
||||
return suspendCancellableCoroutine { cont ->
|
||||
cont.resume(saveCollection(context, collection, lastUpdate))
|
||||
}
|
||||
}
|
||||
@@ -311,7 +310,7 @@ object FileHelper {
|
||||
originalFileUri: Uri,
|
||||
targetFileUri: Uri
|
||||
): Boolean {
|
||||
return suspendCoroutine { cont ->
|
||||
return suspendCancellableCoroutine { cont ->
|
||||
cont.resume(copyFile(context, originalFileUri, targetFileUri))
|
||||
}
|
||||
}
|
||||
@@ -319,7 +318,7 @@ object FileHelper {
|
||||
|
||||
/* Suspend function: Exports collection of stations as M3U file - local backup copy */
|
||||
suspend fun backupCollectionAsM3uSuspended(context: Context, collection: Collection) {
|
||||
return suspendCoroutine { cont ->
|
||||
return suspendCancellableCoroutine { cont ->
|
||||
Log.v(TAG, "Backing up collection as M3U - Thread: ${Thread.currentThread().name}")
|
||||
// create M3U string
|
||||
val m3uString: String = CollectionHelper.createM3uString(collection)
|
||||
@@ -338,7 +337,7 @@ object FileHelper {
|
||||
|
||||
/* Suspend function: Exports collection of stations as PLS file - local backup copy */
|
||||
suspend fun backupCollectionAsPlsSuspended(context: Context, collection: Collection) {
|
||||
return suspendCoroutine { cont ->
|
||||
return suspendCancellableCoroutine { cont ->
|
||||
Log.v(TAG, "Backing up collection as PLS - Thread: ${Thread.currentThread().name}")
|
||||
// create PLS string
|
||||
val plsString: String = CollectionHelper.createPlsString(collection)
|
||||
@@ -417,16 +416,14 @@ object FileHelper {
|
||||
|
||||
/* Reads InputStream from file uri and returns it as String */
|
||||
private fun readTextFileFromFile(context: Context): String {
|
||||
// todo read https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
|
||||
// https://developer.android.com/training/secure-file-sharing/retrieve-info
|
||||
|
||||
// check if file exists
|
||||
val file = File(context.getExternalFilesDir(Keys.FOLDER_COLLECTION), Keys.COLLECTION_FILE)
|
||||
if (!file.exists() || !file.canRead()) {
|
||||
return String()
|
||||
}
|
||||
// read until last line reached
|
||||
val stream: InputStream = file.inputStream()
|
||||
val uri = Uri.fromFile(file)
|
||||
val stream: InputStream = context.contentResolver.openInputStream(uri) ?: return String()
|
||||
val reader = BufferedReader(InputStreamReader(stream))
|
||||
val builder: StringBuilder = StringBuilder()
|
||||
reader.forEachLine {
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.michatec.radio.R
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import androidx.core.graphics.createBitmap
|
||||
|
||||
|
||||
/*
|
||||
@@ -98,7 +99,7 @@ object ImageHelper {
|
||||
}
|
||||
|
||||
// create empty bitmap and canvas
|
||||
val outputImage: Bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
val outputImage: Bitmap = createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
val imageCanvas = Canvas(outputImage)
|
||||
|
||||
// draw square background
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* ImportHelper.kt
|
||||
* Implements the ImportHelper object
|
||||
* A ImportHelper provides methods for integrating station files from Radio v3
|
||||
*
|
||||
* This file is part of
|
||||
* TRANSISTOR - Radio App for Android
|
||||
*
|
||||
* Copyright (c) 2015-22 - Y20K.org
|
||||
* Licensed under the MIT-License
|
||||
* http://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
|
||||
package com.michatec.radio.helpers
|
||||
|
||||
import android.content.Context
|
||||
import com.michatec.radio.Keys
|
||||
import com.michatec.radio.core.Collection
|
||||
|
||||
|
||||
/*
|
||||
* ImportHelper object
|
||||
*/
|
||||
object ImportHelper {
|
||||
|
||||
|
||||
/* */
|
||||
fun removeDefaultStationImageUris(context: Context) {
|
||||
val collection: Collection = FileHelper.readCollection(context)
|
||||
collection.stations.forEach { station ->
|
||||
if (station.image == Keys.LOCATION_DEFAULT_STATION_IMAGE) {
|
||||
station.image = String()
|
||||
}
|
||||
if (station.smallImage == Keys.LOCATION_DEFAULT_STATION_IMAGE) {
|
||||
station.smallImage = String()
|
||||
}
|
||||
}
|
||||
CollectionHelper.saveCollection(context, collection, async = false)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,13 +19,13 @@ import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.util.Log
|
||||
import com.michatec.radio.Keys
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.InetAddress
|
||||
import java.net.URL
|
||||
import java.net.UnknownHostException
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
/*
|
||||
@@ -105,7 +105,7 @@ object NetworkHelper {
|
||||
|
||||
/* Suspend function: Detects content type (mime type) from given URL string - async using coroutine */
|
||||
suspend fun detectContentTypeSuspended(urlString: String): ContentType {
|
||||
return suspendCoroutine { cont ->
|
||||
return suspendCancellableCoroutine { cont ->
|
||||
cont.resume(detectContentType(urlString))
|
||||
}
|
||||
}
|
||||
@@ -113,14 +113,14 @@ object NetworkHelper {
|
||||
|
||||
/* Suspend function: Gets a random radio-browser.info api address - async using coroutine */
|
||||
suspend fun getRadioBrowserServerSuspended(): String {
|
||||
return suspendCoroutine { cont ->
|
||||
return suspendCancellableCoroutine { cont ->
|
||||
val serverAddress: String = try {
|
||||
// get all available radio browser servers
|
||||
val serverAddressList: Array<InetAddress> =
|
||||
InetAddress.getAllByName(Keys.RADIO_BROWSER_API_BASE)
|
||||
// select a random address
|
||||
serverAddressList[Random().nextInt(serverAddressList.size)].canonicalHostName
|
||||
} catch (e: UnknownHostException) {
|
||||
} catch (_: UnknownHostException) {
|
||||
Keys.RADIO_BROWSER_API_DEFAULT
|
||||
}
|
||||
PreferencesHelper.saveRadioBrowserApiAddress(serverAddress)
|
||||
|
||||
@@ -213,20 +213,6 @@ object PreferencesHelper {
|
||||
}
|
||||
|
||||
|
||||
/* Checks if housekeeping work needs to be done - used usually in DownloadWorker "REQUEST_UPDATE_COLLECTION" */
|
||||
fun isHouseKeepingNecessary(): Boolean {
|
||||
return sharedPreferences.getBoolean(Keys.PREF_ONE_TIME_HOUSEKEEPING_NECESSARY, true)
|
||||
}
|
||||
|
||||
|
||||
/* Saves state of housekeeping */
|
||||
fun saveHouseKeepingNecessaryState(state: Boolean = false) {
|
||||
sharedPreferences.edit {
|
||||
putBoolean(Keys.PREF_ONE_TIME_HOUSEKEEPING_NECESSARY, state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Load currently selected app theme */
|
||||
fun loadThemeSelection(): String {
|
||||
return sharedPreferences.getString(
|
||||
@@ -241,15 +227,6 @@ object PreferencesHelper {
|
||||
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STATIONS, true)
|
||||
}
|
||||
|
||||
|
||||
/* Saves value of the option: Edit Stations (only needed for migration) */
|
||||
fun saveEditStationsEnabled(enabled: Boolean = false) {
|
||||
sharedPreferences.edit {
|
||||
putBoolean(Keys.PREF_EDIT_STATIONS, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Loads value of the option: Edit Station Streams */
|
||||
fun loadEditStreamUrisEnabled(): Boolean {
|
||||
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STREAMS_URIS, true)
|
||||
@@ -276,7 +253,7 @@ object PreferencesHelper {
|
||||
fun downloadOverMobile(): Boolean {
|
||||
return sharedPreferences.getBoolean(
|
||||
Keys.PREF_DOWNLOAD_OVER_MOBILE,
|
||||
Keys.DEFAULT_DOWNLOAD_OVER_MOBILE
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
/*
|
||||
* UiHelper.kt
|
||||
* Implements the UiHelper object
|
||||
* A UiHelper provides helper methods for User Interface related tasks
|
||||
*
|
||||
* This file is part of
|
||||
* TRANSISTOR - Radio App for Android
|
||||
*
|
||||
* Copyright (c) 2015-22 - Y20K.org
|
||||
* Licensed under the MIT-License
|
||||
* http://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
|
||||
package com.michatec.radio.helpers
|
||||
|
||||
import android.content.Context
|
||||
|
||||
@@ -129,7 +129,7 @@ class RadioBrowserSearch(private var radioBrowserSearchListener: RadioBrowserSea
|
||||
|
||||
|
||||
/* Listens for (positive) server responses to search requests */
|
||||
private val responseListener: Response.Listener<JSONArray> = Response.Listener<JSONArray> { response ->
|
||||
private val responseListener: Response.Listener<JSONArray> = Response.Listener { response ->
|
||||
if (response != null) {
|
||||
radioBrowserSearchListener.onRadioBrowserSearchResults(createRadioBrowserResult(response.toString()))
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioFocusRequest
|
||||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -240,7 +239,7 @@ class SearchResultAdapter(
|
||||
/*
|
||||
* Inner class: ViewHolder for a radio station search result
|
||||
*/
|
||||
private inner class SearchResultViewHolder(var searchResultLayout: View) :
|
||||
private class SearchResultViewHolder(var searchResultLayout: View) :
|
||||
RecyclerView.ViewHolder(searchResultLayout) {
|
||||
val nameView: MaterialTextView = searchResultLayout.findViewById(R.id.station_name)
|
||||
val streamView: MaterialTextView = searchResultLayout.findViewById(R.id.station_url)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.michatec.radio.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
@@ -131,6 +132,7 @@ data class LayoutHolder(var rootView: View) {
|
||||
|
||||
|
||||
/* Updates the player views */
|
||||
@SuppressLint("DefaultLocale")
|
||||
fun updatePlayerViews(context: Context, station: Station, isPlaying: Boolean) {
|
||||
|
||||
// set default metadata views, when playback has stopped
|
||||
@@ -164,12 +166,20 @@ data class LayoutHolder(var rootView: View) {
|
||||
// show only the codec when the bitrate is at "0" from radio-browser.info API
|
||||
station.codec
|
||||
} else {
|
||||
val kiloBytesPerSecond = station.bitrate / 8F
|
||||
val dataRateString = if (kiloBytesPerSecond >= 1000) {
|
||||
String.format("%.2f mb/s", kiloBytesPerSecond / 1000F)
|
||||
} else {
|
||||
String.format("%.0f kb/s", kiloBytesPerSecond)
|
||||
}
|
||||
// show the bitrate and codec if the result is available in the radio-browser.info API
|
||||
buildString {
|
||||
append(station.codec)
|
||||
append(" | ")
|
||||
append(station.bitrate)
|
||||
append("kbps")
|
||||
append(" | ")
|
||||
append(dataRateString)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -303,18 +313,6 @@ data class LayoutHolder(var rootView: View) {
|
||||
isBuffering = buffering
|
||||
}
|
||||
|
||||
|
||||
/* Toggles visibility of player depending on playback state - hiding it when playback is stopped (not paused or playing) */
|
||||
// fun togglePlayerVisibility(context: Context, playbackState: Int): Boolean {
|
||||
// when (playbackState) {
|
||||
// PlaybackStateCompat.STATE_STOPPED -> return hidePlayer(context)
|
||||
// PlaybackStateCompat.STATE_NONE -> return hidePlayer(context)
|
||||
// PlaybackStateCompat.STATE_ERROR -> return hidePlayer(context)
|
||||
// else -> return showPlayer(context)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/* Toggles visibility of the download progress indicator */
|
||||
fun toggleDownloadProgressIndicator() {
|
||||
when (PreferencesHelper.loadActiveDownloads()) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<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="m11.999,2.623c-5.54,0 -9.999,4.462 -9.999,10.001 -0,1.878 0.529,3.721 1.528,5.313l8.336,-10.778c0.06,-0.077 0.209,-0.077 0.27,0l3.482,4.502h-2.494l0.054,0.199h2.594l0.736,0.949h-3.062l0.086,0.311h3.217l0.647,0.836h-3.631l0.113,0.402h3.827l0.579,0.745h-4.198l0.137,0.49h4.438l0.508,0.655h-4.764l0.152,0.542h5.031l0.468,0.606h-5.33l0.152,0.542h5.598c0.998,-1.592 1.528,-3.435 1.528,-5.313 0,-5.54 -4.462,-10.001 -10.001,-10.001zM15.044,18.543 L15.196,19.085h4.438c0.145,-0.171 0.294,-0.361 0.428,-0.542zM15.365,19.69 L15.515,20.232h2.969c0.194,-0.166 0.405,-0.357 0.594,-0.542zM15.684,20.838 L15.836,21.377h1.003c0.296,-0.17 0.571,-0.343 0.868,-0.539z" />
|
||||
</vector>
|
||||
17
app/src/main/res/drawable/ic_refresh_24dp.xml
Normal file
17
app/src/main/res/drawable/ic_refresh_24dp.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@color/icon_default"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4
|
||||
7.58,4 4,7.58 4,12h2
|
||||
c0,-3.31 2.69,-6 6,-6
|
||||
1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z
|
||||
M6.35,17.65C7.8,19.1 9.79,20 12,20
|
||||
c4.42,0 8,-3.58 8,-8h-2
|
||||
c0,3.31 -2.69,6 -6,6
|
||||
-1.66,0 -3.14,-0.69 -4.22,-1.78L11,13H4v7l2.35,-2.35z"/>
|
||||
</vector>
|
||||
@@ -31,6 +31,21 @@
|
||||
app:shapeAppearanceOverlay="@style/RoundedCorners"
|
||||
app:srcCompat="@drawable/ic_default_station_image_72dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/download_progress"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:progressTint="@color/player_button_background"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="@+id/station_icon"
|
||||
app:layout_constraintStart_toStartOf="@+id/station_icon"
|
||||
app:layout_constraintTop_toTopOf="@+id/station_icon"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/change_image_view"
|
||||
android:layout_width="0dp"
|
||||
@@ -155,6 +170,21 @@
|
||||
app:layout_constraintEnd_toStartOf="@+id/save_button"
|
||||
app:layout_constraintTop_toTopOf="@+id/save_button" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/buffering_progress"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:progressTint="@color/player_button_background"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/default_edit_views"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -51,6 +51,10 @@
|
||||
<string name="player_sheet_h2_station_metadata">Momentan läuft</string>
|
||||
<string name="player_sheet_h2_stream_url">Streaming-Adresse</string>
|
||||
<!-- Settings -->
|
||||
<string name="pref_update_collection_title">Senderinformationnen aktualisieren</string>
|
||||
<string name="pref_update_collection_summary">Die neueste Version aller Senderinformationen herunterladen.</string>
|
||||
<string name="dialog_yes_no_message_update_collection">Die neueste Version aller Senderinformationen herunterladen?</string>
|
||||
<string name="dialog_yes_no_positive_button_update_collection">Aktualisieren</string>
|
||||
<string name="pref_advanced_title">Erweitert</string>
|
||||
<string name="pref_app_version_summary">Version</string>
|
||||
<string name="pref_app_version_title">App-Version</string>
|
||||
|
||||
@@ -59,6 +59,10 @@
|
||||
<string name="player_sheet_h2_stream_url">Streaming link</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="pref_update_collection_title">Update Stations</string>
|
||||
<string name="pref_update_collection_summary">Download latest version of all station.</string>
|
||||
<string name="dialog_yes_no_message_update_collection">Download latest version of all station?</string>
|
||||
<string name="dialog_yes_no_positive_button_update_collection">Update</string>
|
||||
<string name="pref_advanced_title">Advanced</string>
|
||||
<string name="pref_app_version_summary">Version</string>
|
||||
<string name="pref_app_version_title">App Version</string>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<paths>
|
||||
<external-files-path
|
||||
name="my_images"
|
||||
path="Android/data/com.michatec.urlradio/files/Pictures" />
|
||||
path="Android/data/com.michatec.radio/files/Pictures" />
|
||||
<external-path
|
||||
name="external_files"
|
||||
path="." />
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
android:shortcutShortLabel="@string/shortcut_last_station_short_label">
|
||||
|
||||
<intent
|
||||
android:action="com.jamal2367.urlradio.action.START"
|
||||
android:action="com.michatec.radio.action.START"
|
||||
android:targetClass="com.michatec.radio.MainActivity"
|
||||
android:targetPackage="com.michatec.radio">
|
||||
<extra
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '9.0.0' apply false
|
||||
id 'com.android.library' version '9.0.0' apply false
|
||||
id 'org.jetbrains.kotlin.android' version "2.3.0" apply false
|
||||
alias libs.plugins.android.application apply false
|
||||
alias libs.plugins.android.library apply false
|
||||
alias libs.plugins.jetbrains.kotlin.android apply false
|
||||
}
|
||||
|
||||
tasks.register('clean', Delete) {
|
||||
delete rootProject.buildDir()
|
||||
}
|
||||
}
|
||||
|
||||
42
gradle/libs.versions.toml
Normal file
42
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,42 @@
|
||||
[versions]
|
||||
activityKtx = "1.13.0"
|
||||
agp = "9.1.0"
|
||||
coreKtx = "1.18.0"
|
||||
freedroidwarn = "V1.10"
|
||||
gradleToolchainsFoojayResolverConvention = "1.0.0"
|
||||
gson = "2.13.2"
|
||||
kotlin = "2.3.20"
|
||||
material = "1.13.0"
|
||||
material3 = "1.4.0"
|
||||
media = "1.7.1"
|
||||
media3 = "1.9.3"
|
||||
navigation = "2.9.7"
|
||||
paletteKtx = "1.0.0"
|
||||
preferenceKtx = "1.2.1"
|
||||
volley = "1.2.1"
|
||||
workRuntimeKtx = "2.11.1"
|
||||
|
||||
[libraries]
|
||||
activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
|
||||
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
freedroidwarn = { group = "com.github.woheller69", name = "FreeDroidWarn", version.ref = "freedroidwarn" }
|
||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||
media = { group = "androidx.media", name = "media", version.ref = "media" }
|
||||
media3-datasource-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3" }
|
||||
media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
|
||||
media3-exoplayer-hls = { group = "androidx.media3", name = "media3-exoplayer-hls", version.ref = "media3" }
|
||||
media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
|
||||
navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" }
|
||||
navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" }
|
||||
palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "paletteKtx" }
|
||||
preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferenceKtx" }
|
||||
volley = { group = "com.android.volley", name = "volley", version.ref = "volley" }
|
||||
work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
foojay = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "gradleToolchainsFoojayResolverConvention" }
|
||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
2
gradlew
vendored
2
gradlew
vendored
@@ -57,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
||||
@@ -3,13 +3,19 @@ pluginManagement {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user