diff --git a/app/build.gradle b/app/build.gradle index 9a1cae0..d658187 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,8 +29,8 @@ android { applicationId "fr.forum_thalie.tsumugi" minSdkVersion 16 targetSdkVersion 29 - versionCode 100 - versionName "1.0.0" + versionCode 113 + versionName "1.1.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 12e9c38..7e20574 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,9 +31,6 @@ - Any?, val post: (Any?) -> Unit = {}, execute() } catch (e: Exception) { - Log.d(tag,e.toString()) + //[REMOVE LOG CALLS]Log.d(tag,e.toString()) } } @@ -46,14 +46,14 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {}, } - Log.d(tag, "fallback for no network. Store reset : $storeReset") + //[REMOVE LOG CALLS]Log.d(tag, "fallback for no network. Store reset : $storeReset") } override fun doInBackground(vararg params: Any?): Any? { try { return handler(parameters) } catch (e: Exception) { - Log.d(tag,e.toString()) + //[REMOVE LOG CALLS]Log.d(tag,e.toString()) onException(e) } return null @@ -63,7 +63,7 @@ class Async(val handler: (Any?) -> Any?, val post: (Any?) -> Unit = {}, try { post(result) } catch (e: Exception) { - Log.d(tag,e.toString()) + //[REMOVE LOG CALLS]Log.d(tag,e.toString()) onException(e) } } diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/BaseActivity.kt b/app/src/main/java/fr/forum_thalie/tsumugi/BaseActivity.kt index e7b718d..08ea16f 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/BaseActivity.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/BaseActivity.kt @@ -21,7 +21,7 @@ abstract class BaseActivity : AppCompatActivity() { val height = ((rootLayout?.height ?: 0)) val width = ((rootLayout?.width ?: 0)) - Log.d(tag, "$viewWidth, $viewHeight, $width, $height, ${viewHeight.toDouble()/viewWidth.toDouble()}, ${height.toDouble()/width.toDouble()}") + //[REMOVE LOG CALLS]Log.d(tag, "$viewWidth, $viewHeight, $width, $height, ${viewHeight.toDouble()/viewWidth.toDouble()}, ${height.toDouble()/width.toDouble()}") val broadcastManager = LocalBroadcastManager.getInstance(this@BaseActivity) if(height <= viewHeight * 2 / 3 /*height.toDouble()/width.toDouble() < 1.20 */){ @@ -49,14 +49,14 @@ abstract class BaseActivity : AppCompatActivity() { // do things when keyboard is shown val bottomNavigationView = findViewById(R.id.bottom_nav) bottomNavigationView.visibility = View.GONE - Log.d(tag, "bottomNav visibility set to GONE (height $keyboardHeight)") + //[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to GONE (height $keyboardHeight)") } private fun onHideKeyboard() { // do things when keyboard is hidden val bottomNavigationView = findViewById(R.id.bottom_nav) bottomNavigationView.visibility = View.VISIBLE - Log.d(tag, "bottomNav visibility set to VISIBLE") + //[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to VISIBLE") } protected fun attachKeyboardListeners() { diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt b/app/src/main/java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt index 904283f..882233c 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt @@ -7,12 +7,13 @@ import android.os.Build import android.util.Log import androidx.preference.PreferenceManager import fr.forum_thalie.tsumugi.alarm.RadioAlarm +import fr.forum_thalie.tsumugi.planning.Planning import fr.forum_thalie.tsumugi.playerstore.PlayerStore class BootBroadcastReceiver : BroadcastReceiver(){ override fun onReceive(context: Context, arg1: Intent) { - Log.d(tag, "Broadcast Receiver received $arg1") + //[REMOVE LOG CALLS]Log.d(tag, "Broadcast Receiver received $arg1") // define preferenceStore for places of the program that needs to access Preferences without a context preferenceStore = PreferenceManager.getDefaultSharedPreferences(context) @@ -22,7 +23,11 @@ class BootBroadcastReceiver : BroadcastReceiver(){ if (arg1.getStringExtra("action") == "$tag.${Actions.PLAY_OR_FALLBACK.name}" ) { + RadioAlarm.instance.setNextAlarm(context) // schedule next alarm + Planning.instance.parseUrl(context = context) + if (!PlayerStore.instance.isInitialized) + PlayerStore.instance.initApi() if (PlayerStore.instance.streamerName.value.isNullOrBlank()) PlayerStore.instance.initPicture(context) diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/MainActivity.kt b/app/src/main/java/fr/forum_thalie/tsumugi/MainActivity.kt index 511364c..91fd2c7 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/MainActivity.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/MainActivity.kt @@ -15,6 +15,7 @@ import fr.forum_thalie.tsumugi.playerstore.PlayerStore import java.util.Timer import android.view.MenuItem +import com.google.android.material.snackbar.Snackbar import fr.forum_thalie.tsumugi.alarm.RadioAlarm import fr.forum_thalie.tsumugi.planning.Planning @@ -86,6 +87,14 @@ class MainActivity : BaseActivity() { true } */ + R.id.action_refresh -> { + PlayerStore.instance.queue.clear() + //PlayerStore.instance.lp.clear() + PlayerStore.instance.initApi() + val s = Snackbar.make(findViewById(R.id.nav_host_container), getString(R.string.refreshing) as CharSequence, Snackbar.LENGTH_LONG) + s.show() + true + } R.id.action_settings -> { val i = Intent(this, ParametersActivity::class.java) startActivity(i) @@ -129,10 +138,16 @@ class MainActivity : BaseActivity() { colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null)) colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null)) + // fetch program + Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this) + + PlayerStore.instance.initUrl(this) + PlayerStore.instance.initApi() + // Post-UI Launch if (PlayerStore.instance.isInitialized) { - Log.d(tag, "skipped initialization") + //[REMOVE LOG CALLS]Log.d(tag, "skipped initialization") } else { // if the service is not started, start it in STOP mode. // It's not a dummy action : with STOP mode, the player does not buffer audio (and does not use data connection without the user's consent). @@ -160,9 +175,6 @@ class MainActivity : BaseActivity() { isTimerStarted = true } - // fetch program - Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this) - // initialize the UI setTheme(R.style.AppTheme) setContentView(R.layout.activity_main) @@ -193,7 +205,7 @@ class MainActivity : BaseActivity() { val i = Intent(this, RadioService::class.java) i.putExtra("action", a.name) i.putExtra("value", v) - Log.d(tag, "Sending intent ${a.name}") + //[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}") startService(i) } @@ -238,7 +250,7 @@ class MainActivity : BaseActivity() { // File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder") val logDirectory = File("$appDirectory/log") val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt") - Log.d( + //[REMOVE LOG CALLS]Log.d( tag, "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile" ) @@ -246,20 +258,20 @@ class MainActivity : BaseActivity() { // create app folder if (!appDirectory.exists()) { appDirectory.mkdir() - Log.d(tag, "$appDirectory created") + //[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created") } // create log folder if (!logDirectory.exists()) { logDirectory.mkdir() - Log.d(tag, "$logDirectory created") + //[REMOVE LOG CALLS]Log.d(tag, "$logDirectory created") } // clear the previous logcat and then write the new one to the file try { Runtime.getRuntime().exec("logcat -c") Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ") - Log.d(tag, "logcat started") + //[REMOVE LOG CALLS]Log.d(tag, "logcat started") } catch (e: IOException) { e.printStackTrace() } diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ParametersActivity.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ParametersActivity.kt index d8294da..2fda3ea 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ParametersActivity.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ParametersActivity.kt @@ -1,6 +1,7 @@ package fr.forum_thalie.tsumugi import android.os.Bundle +import android.view.MenuItem import fr.forum_thalie.tsumugi.preferences.* @@ -38,4 +39,15 @@ class ParametersActivity : BaseActivity() { .replace(R.id.parameters_host_container, fragmentToLoad) .commit() } + + // Make the Up button function as back instead of always bringing us to the main activity + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + onBackPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } } \ No newline at end of file diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt b/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt index 3b4d57b..3c4a37e 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt @@ -68,14 +68,14 @@ class RadioService : MediaBrowserServiceCompat() { // This *should* work in any case... when (intent.getIntExtra("state", -1)) { 0 -> { - Log.d(tag, radioTag + "Headset is unplugged") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is unplugged") } 1 -> { - Log.d(tag, radioTag + "Headset is plugged") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is plugged") headsetPluggedIn = true } else -> { - Log.d(tag, radioTag + "I have no idea what the headset state is") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "I have no idea what the headset state is") } } /* @@ -91,7 +91,7 @@ class RadioService : MediaBrowserServiceCompat() { } else { - Log.d(tag, radioTag + "Can't get state?") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Can't get state?") } */ @@ -124,10 +124,32 @@ class RadioService : MediaBrowserServiceCompat() { private val titleObserver = Observer { // We're checking if a new song arrives. If so, we put the currentSong in Lp and update the backup. + + if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING) + { + //[REMOVE LOG CALLS]Log.d((tag, radioTag + "SONG CHANGED AND PLAYING") + // we activate latency compensation only if it's been at least 2 songs... + when { + PlayerStore.instance.isStreamDown -> { + // if we reach here, it means that the observer has been called by a new song and that the stream was down previously. + // so the stream is now back to normal. + PlayerStore.instance.isStreamDown = false + PlayerStore.instance.initApi() + } + PlayerStore.instance.currentSong.title.value == noConnectionValue -> { + PlayerStore.instance.isStreamDown = true + } + else -> { + PlayerStore.instance.fetchApi(/* numberOfSongs >= 2 */) + } + } + } + if (PlayerStore.instance.currentSong != PlayerStore.instance.currentSongBackup && it != noConnectionValue) { PlayerStore.instance.updateLp() + PlayerStore.instance.updateQueue() } nowPlayingNotification.update(this) Planning.instance.checkProgramme() @@ -163,6 +185,12 @@ class RadioService : MediaBrowserServiceCompat() { preferenceStore = PreferenceManager.getDefaultSharedPreferences(this) + // start ticker for when the player is stopped + val periodString = PreferenceManager.getDefaultSharedPreferences(this).getString("fetchPeriod", "10") ?: "10" + val period: Long = Integer.parseInt(periodString).toLong() + if (period > 0) + apiTicker.schedule(ApiFetchTick(), 0, period * 1000) + // Define managers telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE) @@ -205,7 +233,7 @@ class RadioService : MediaBrowserServiceCompat() { startForeground(radioServiceId, nowPlayingNotification.notification) PlayerStore.instance.isServiceStarted.value = true - Log.d(tag, radioTag + "created") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "created") } private val handler = Handler() @@ -246,7 +274,7 @@ class RadioService : MediaBrowserServiceCompat() { Actions.CANCEL_FADE_OUT.name -> { handler.removeCallbacks(lowerVolumeRunnable) } Actions.SNOOZE.name -> { RadioAlarm.instance.snooze(this) } } - Log.d(tag, radioTag + "intent received : " + intent.getStringExtra("action")) + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "intent received : " + intent.getStringExtra("action")) super.onStartCommand(intent, flags, startId) // The service must be re-created if it is destroyed by the system. This allows the user to keep actions like Bluetooth and headphones plug available. return START_STICKY @@ -258,7 +286,7 @@ class RadioService : MediaBrowserServiceCompat() { stopSelf() } super.onTaskRemoved(rootIntent) - Log.d(tag, radioTag + "task removed") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "task removed") } override fun onDestroy() { @@ -290,7 +318,7 @@ class RadioService : MediaBrowserServiceCompat() { } apiTicker.cancel() // stops the timer. - Log.d(tag, radioTag + "destroyed") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "destroyed") // if the service is destroyed, the application had become useless. exitProcess(0) } @@ -361,10 +389,10 @@ class RadioService : MediaBrowserServiceCompat() { for (i in 0 until it.length()) { val entry = it.get(i) if (entry is IcyHeaders) { - Log.d(tag, radioTag + "onMetadata: IcyHeaders $entry") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: IcyHeaders $entry") } if (entry is IcyInfo) { - Log.d(tag, radioTag + "onMetadata: Title ----> ${entry.title}") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: Title ----> ${entry.title}") // Note : Kotlin supports UTF-8 by default. numberOfSongs++ val data = entry.title!! @@ -439,7 +467,7 @@ class RadioService : MediaBrowserServiceCompat() { { Thread.sleep(1000) i++ - Log.d(tag, "$i, isAlarmStopped=$isAlarmStopped") + //[REMOVE LOG CALLS]Log.d(tag, "$i, isAlarmStopped=$isAlarmStopped") } } val post: (Any?) -> Unit = { @@ -512,7 +540,7 @@ class RadioService : MediaBrowserServiceCompat() { SystemClock.elapsedRealtime() ) mediaSession.setPlaybackState(playbackStateBuilder.build()) - Log.d(tag, radioTag + "begin playing") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "begin playing") } private fun pausePlaying() @@ -541,7 +569,7 @@ class RadioService : MediaBrowserServiceCompat() { 1.0f, SystemClock.elapsedRealtime() ) - Log.d(tag, radioTag + "stopped") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "stopped") mediaSession.setPlaybackState(playbackStateBuilder.build()) } @@ -629,7 +657,7 @@ class RadioService : MediaBrowserServiceCompat() { Player.STATE_ENDED -> state = "Player.STATE_ENDED" Player.STATE_READY -> state = "Player.STATE_READY" } - Log.d(tag, radioTag + "Player changed state: ${state}. numberOfSongs reset.") + //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Player changed state: ${state}. numberOfSongs reset.") } } diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt b/app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt index 512c132..ec114b6 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt @@ -1,8 +1,18 @@ package fr.forum_thalie.tsumugi +import android.support.v4.media.session.PlaybackStateCompat import fr.forum_thalie.tsumugi.playerstore.PlayerStore import java.util.* +class ApiFetchTick : TimerTask() { + override fun run() { + if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_STOPPED) + { + PlayerStore.instance.fetchApi() + } + } +} + class Tick : TimerTask() { override fun run() { PlayerStore.instance.currentTime.postValue(PlayerStore.instance.currentTime.value!! + 500) diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioAlarm.kt b/app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioAlarm.kt index d8429ad..b64e8c5 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioAlarm.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioAlarm.kt @@ -97,7 +97,7 @@ class RadioAlarm { calendar.isLenient = true calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) + i, hourOfDay, minute) - Log.d(tag, calendar.toString()) + //[REMOVE LOG CALLS]Log.d(tag, calendar.toString()) return calendar.timeInMillis diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioSleeper.kt b/app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioSleeper.kt index 81ee2c6..fc5733c 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioSleeper.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioSleeper.kt @@ -61,7 +61,7 @@ class RadioSleeper { AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, currentMillis + (minutes * 60 * 1000) - (1 * 60 * 1000), fadeOutIntent) sleepAtMillis.value = System.currentTimeMillis() + (minutes * 60 * 1000) - 1 // this -1 allows to round the division for display at the right integer - Log.d(tag, "set sleep to $minutes minutes") + //[REMOVE LOG CALLS]Log.d(tag, "set sleep to $minutes minutes") } } @@ -79,7 +79,7 @@ class RadioSleeper { c.startService(cancelFadeOutIntent) } - Log.d(tag, "cancelled sleep") + //[REMOVE LOG CALLS]Log.d(tag, "cancelled sleep") sleepAtMillis.value = null } } \ No newline at end of file diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/planning/Programme.kt b/app/src/main/java/fr/forum_thalie/tsumugi/planning/Programme.kt index 93b5ab1..20b2ccd 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/planning/Programme.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/planning/Programme.kt @@ -35,7 +35,7 @@ class Programme (val title: String, private val periodicity: Int, private val ho val isSpanningOverNight = (((0b1000000 shr ((currentDay - 1) % 7) and (periodicity)) != 0) && hourEnd < hourBegin) - Log.d(tag, "$title is today: $isToday or spanning $isSpanningOverNight") + //[REMOVE LOG CALLS]Log.d(tag, "$title is today: $isToday or spanning $isSpanningOverNight") // shr = shift-right. It's a binary mask. // if the program started yesterday, and spanned over night, it means that there could be a chance that it's still active. @@ -87,6 +87,6 @@ class Programme (val title: String, private val periodicity: Int, private val ho */ init { - Log.d(tag, this.toString()) + //[REMOVE LOG CALLS]Log.d(tag, this.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt b/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt index 41037c6..e37fde9 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt @@ -7,9 +7,17 @@ import android.support.v4.media.session.PlaybackStateCompat import android.util.Log import androidx.lifecycle.MutableLiveData import fr.forum_thalie.tsumugi.* +import fr.forum_thalie.tsumugi.planning.Planning +import org.json.JSONObject +import java.net.URL +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* +import kotlin.collections.ArrayList class PlayerStore { + private lateinit var urlToScrape: String val isPlaying: MutableLiveData = MutableLiveData() val isServiceStarted: MutableLiveData = MutableLiveData() val volume: MutableLiveData = MutableLiveData() @@ -27,6 +35,7 @@ class PlayerStore { val listenersCount: MutableLiveData = MutableLiveData() var latencyCompensator : Long = 0 var isInitialized: Boolean = false + var isStreamDown: Boolean = false init { playbackState.value = PlaybackStateCompat.STATE_STOPPED @@ -38,29 +47,215 @@ class PlayerStore { isQueueUpdated.value = false isLpUpdated.value = false isMuted.value = false - currentSong.title.value = noConnectionValue - currentSongBackup.title.value = noConnectionValue + currentSong.setTitleArtist(noConnectionValue) + currentSongBackup.setTitleArtist(noConnectionValue) listenersCount.value = 0 } + fun initUrl(c: Context) + { + urlToScrape = c.getString(R.string.API_URL) + } + + private fun getTimestamp(s: String) : Long + { + val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss z", Locale.getDefault()) + try { + val t: Date? = dateFormat.parse("$s ${Planning.instance.timeZone.id}") + //[REMOVE LOG CALLS]Log.d(tag, "date: $s -> $t") + return t!!.time + } catch (e: ParseException) { + e.printStackTrace() + } + return 0 + } + + + // ################################################## + // ################# API FUNCTIONS ################## + // ################################################## + + private fun updateApi(res: JSONObject, isCompensatingLatency : Boolean = false) { + // If we're not in PLAYING state, update title / artist metadata. If we're playing, the ICY will take care of that. + + val resMain = res.getJSONObject("tracks").getJSONObject("current") + val s = extractSong(resMain) + if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty() + || currentSong.title.value == noConnectionValue) + currentSong.setTitleArtist("${s.artist.value} - ${s.title.value}") + + val starts = s.startTime.value + val ends = s.stopTime.value + + if (currentSong.startTime.value != starts) + currentSong.startTime.value = starts + + currentSong.stopTime.value = ends + + val apiTime = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) + // I noticed that the server has a big (3 to 9 seconds !!) offset for current time. + // we can measure it when the player is playing, to compensate it and have our progress bar perfectly timed + // latencyCompensator is set to null when beginPlaying() (we can't measure it at the moment we start playing, since we're in the middle of a song), + // at this moment, we set it to 0. Then, next time the updateApi is called when we're playing, we measure the latency and we set out latencyComparator. + if(isCompensatingLatency) + { + latencyCompensator = apiTime - (currentSong.startTime.value!!) + //[REMOVE LOG CALLS]Log.d(tag, "latency compensator set to ${(latencyCompensator).toFloat() / 1000} s") + } + currentTime.value = apiTime - (latencyCompensator) + + /* + val listeners = resMain.getInt("listeners") + listenersCount.value = listeners + //[REMOVE LOG CALLS]Log.d((tag, playerStoreTag + "store updated") + */ + } + + private val scrape : (Any?) -> String = + { + URL(urlToScrape).readText() + } + + /* initApi is called : + - at startup + - when a streamer changes. + the idea is to fetch the queue when a streamer changes (potentially Hanyuu), and at startup. + The Last Played is only fetched if it's empty (so, only at startup), not when a streamer changes. + */ + fun initApi() + { + val post : (parameter: Any?) -> Unit = { + val result = JSONObject(it as String) + if (result.has("tracks")) + { + updateApi(result) + currentSongBackup.copy(currentSong) + fetchLastRequest() + isQueueUpdated.value = true + + isLpUpdated.value = true + } + isInitialized = true + } + Async(scrape, post) + } + + fun fetchApi(isCompensatingLatency: Boolean = false) { + val post: (parameter: Any?) -> Unit = { + val result = JSONObject(it as String) + if (!result.isNull("tracks")) + { + updateApi(result, isCompensatingLatency) + } + } + Async(scrape, post) + } + + private fun extractSong(songJSON: JSONObject) : Song { + val song = Song() + song.setTitleArtist(songJSON.getString("name")) + song.startTime.value = getTimestamp(songJSON.getString("starts")) + song.stopTime.value = getTimestamp(songJSON.getString("ends")) + song.type.value = 0 // only used for R/a/dio + return song + } + + // ################################################## // ############## QUEUE / LP FUNCTIONS ############## // ################################################## + fun updateQueue() { + //[REMOVE LOG CALLS]Log.d(tag, queue.toString()) + fetchLastRequest() + } + fun updateLp() { // note : lp is empty at initialization. This check was needed when we used the R/a/dio API. //if (lp.isNotEmpty()){ val n = Song() n.copy(currentSongBackup) - if (n.title.value != noConnectionValue && n.title.value != streamDownValue) + if (n != Song(noConnectionValue) && n != Song(streamDownValue)) lp.add(0, n) currentSongBackup.copy(currentSong) isLpUpdated.value = true - Log.d(tag, playerStoreTag + lp.toString()) + //[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + lp.toString()) //} } + private fun fetchLastRequest() + { + isQueueUpdated.value = false + val sleepScrape: (Any?) -> String = { + /* we can maximize our chances to retrieve the last queued song by specifically waiting for the number of seconds we measure between ICY metadata and API change. + we add 2 seconds just to get a higher probability that the API has correctly updated. (the latency compensator can have a jitter of 1 second usually) + If, against all odds, the API hasn't updated yet, we will retry in the same amount of seconds. So we'll have the data anyway. + This way to fetch at the most probable time is a good compromise between fetch speed and fetch frequency + We don't fetch too often, and we start to fetch at the most *probable* time. + If there's no latencyCompensator measured yet, we only wait for 3 seconds. + If the song is the same, it will be called again. 3 seconds is a good compromise between speed and frequency: + it might be called twice, rarely 3 times, and it's only the 2 first songs ; after these, the latencyCompensator is set to fetch at the most probable time. + */ + val sleepTime: Long = if (latencyCompensator > 0) latencyCompensator + 2000 else 3000 + Thread.sleep(sleepTime) // we wait a bit (10s) for the API to get updated on R/a/dio side! + URL(urlToScrape).readText() + } + + lateinit var post: (parameter: Any?) -> Unit + + fun postFun(result: JSONObject) + { + if (result.has("tracks")) { + val resMain = result.getJSONObject("tracks") + /* + if ((resMain.has("isafkstream") && !resMain.getBoolean("isafkstream")) && + queue.isNotEmpty()) + { + queue.clear() //we're not requesting anything anymore. + isQueueUpdated.value = true + } else if (resMain.has("isafkstream") && resMain.getBoolean("isafkstream") && + queue.isEmpty()) + { + initApi() + } else + */ + if (resMain.has("next")) { + val queueJSON = + resMain.getJSONObject("next") + val t = extractSong(queueJSON) + if (queue.isNotEmpty() && (t == queue.last() || t == currentSong) && isQueueUpdated.value == false) + { + //[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + "Song already in there: $t\nQueue:$queue") + Async(sleepScrape, post) + } else { + if (queue.isNotEmpty()) + queue.remove(queue.first()) + queue.add(queue.size, t) + //[REMOVE LOG CALLS]Log.d(tag, playerStoreTag + "added last queue song: $t") + isQueueUpdated.value = true + return // FUUUCK IT WAS CALLING THE ASYNC ONE MORE TIME AFTERWARDS !? + } + } + } + } + + post = { + val result = JSONObject(it as String) + /* The goal is to pass the result to a function that will process it (postFun). + The magic trick is, under circumstances, the last queue song might not have been updated yet when we fetch it. + So if this is detected ==> if (t == queue.last() ) + Then the function re-schedule an Async(sleepScrape, post). + To do that, the "post" must be defined BEFORE the function, but the function must be defined BEFORE the "post" value. + So I declare "post" as lateinit var, define the function, then define the "post" that calls the function. IT SHOULD WORK. + */ + postFun(result) + } + + Async(sleepScrape, post) + } + + // ################################################## // ############## PICTURE FUNCTIONS ################# // ################################################## diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/Song.kt b/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/Song.kt index b0e8526..9b9ba3a 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/Song.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/Song.kt @@ -1,5 +1,6 @@ package fr.forum_thalie.tsumugi.playerstore +import androidx.core.text.HtmlCompat import androidx.lifecycle.MutableLiveData import fr.forum_thalie.tsumugi.noConnectionValue @@ -24,8 +25,9 @@ class Song(artistTitle: String = "", _id : Int = 0) { return "id=$id | ${artist.value} - ${title.value} | type=${type.value} | times ${startTime.value} - ${stopTime.value}\n" } - fun setTitleArtist(data: String) + fun setTitleArtist(dataHtml: String) { + val data = HtmlCompat.fromHtml(dataHtml, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() val hyphenPos = data.indexOf(" - ") try { if (hyphenPos < 0) @@ -46,12 +48,12 @@ class Song(artistTitle: String = "", _id : Int = 0) { override fun equals(other: Any?) : Boolean { val song: Song = other as Song - return this.title.value == song.title.value && this.artist.value == song.artist.value + return this.title.value === song.title.value && this.artist.value === song.artist.value } fun copy(song: Song) { - this.title.value = song.title.value this.artist.value = song.artist.value + this.title. value = song.title.value this.startTime.value = song.startTime.value this.stopTime.value = song.stopTime.value this.type.value = song.type.value diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/preferences/CustomizeFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/preferences/CustomizeFragment.kt index b80bc6c..fe3a2cd 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/preferences/CustomizeFragment.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/preferences/CustomizeFragment.kt @@ -1,9 +1,6 @@ package fr.forum_thalie.tsumugi.preferences -import android.content.Intent -import android.net.Uri import android.os.Bundle -import android.util.Log import androidx.appcompat.app.AlertDialog import androidx.preference.* import fr.forum_thalie.tsumugi.R @@ -40,5 +37,25 @@ class CustomizeFragment : PreferenceFragmentCompat() { true } + + val fetchPeriod = preferenceScreen.findPreference("fetchPeriod") + fetchPeriod?.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance() + fetchPeriod?.setOnPreferenceChangeListener { _, newValue -> + val builder1 = AlertDialog.Builder(context!!) + if (Integer.parseInt(newValue as String) == 0) + builder1.setMessage(R.string.restart_the_app) + else + builder1.setMessage(R.string.restart_the_app) + builder1.setCancelable(true) + + builder1.setPositiveButton("Close" ) { dialog, _ -> + dialog.cancel() + } + + val alert11 = builder1.create() + alert11.show() + true + } + } } \ No newline at end of file diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsAdapter.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsAdapter.kt index f058fd8..1ee36b4 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsAdapter.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsAdapter.kt @@ -40,7 +40,7 @@ class ImageGetterAsyncTask( override fun doInBackground(vararg params: TextView?): Bitmap? { t = params[0] return try { - //Log.d(LOG_CAT, "Downloading the image from: $source") + ////[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(LOG_CAT, "Downloading the image from: $source") var k: InputStream? = null var pic: Bitmap? = null try { diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsFragment.kt index aa86d71..494757f 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsFragment.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsFragment.kt @@ -43,9 +43,9 @@ class NewsFragment : Fragment() { } newsViewModel.isWebViewLoaded = true - Log.d(tag, "webview created") + //[REMOVE LOG CALLS]Log.d(tag, "webview created") } else { - Log.d(tag, "webview already created!?") + //[REMOVE LOG CALLS]Log.d(tag, "webview already created!?") } newsViewModel.root.addOnLayoutChangeListener(orientationLayoutListener) @@ -101,7 +101,7 @@ class NewsFragment : Fragment() { ViewModelProviders.of(this).get(NewsViewModel::class.java) newsViewModel.fetch(c = context!!, isPreloading = true) - Log.d(tag, "news fetched onCreate") + //[REMOVE LOG CALLS]Log.d(tag, "news fetched onCreate") super.onCreate(savedInstanceState) } } \ No newline at end of file diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsViewModel.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsViewModel.kt index ab2ea0d..91c553c 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsViewModel.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsViewModel.kt @@ -43,14 +43,14 @@ class NewsViewModel : ViewModel() { val maxNumberOfArticles = 5 coroutineScope.launch(Dispatchers.Main) { - Log.d(tag, "launching coroutine") + //[REMOVE LOG CALLS]Log.d(tag, "launching coroutine") val parser = Parser() try { val articleList = parser.getArticles(urlToScrape) newsArray.clear() for (i in 0 until min(articleList.size, maxNumberOfArticles)) { val item = articleList[i] - Log.d(tag, "i = $i / ${articleList.size}") + //[REMOVE LOG CALLS]Log.d(tag, "i = $i / ${articleList.size}") val news = News() news.title = item.title ?: "" news.link = item.link ?: urlToScrape @@ -60,7 +60,7 @@ class NewsViewModel : ViewModel() { val formatter6 = SimpleDateFormat(newsDateTimePattern, Locale.ENGLISH) val dateString = item.pubDate.toString() - Log.d(tag, "$news --- $dateString") + //[REMOVE LOG CALLS]Log.d(tag, "$news --- $dateString") news.date = formatter6.parse(dateString) ?: Date(0) diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt index c2ba0ec..1e306ac 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt @@ -6,8 +6,6 @@ import android.content.Context import androidx.lifecycle.ViewModelProviders import android.os.Bundle import android.support.v4.media.session.PlaybackStateCompat -import android.util.Log -import android.util.TypedValue import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View @@ -15,7 +13,6 @@ import android.view.ViewGroup import android.widget.* import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import androidx.core.widget.TextViewCompat import androidx.lifecycle.Observer import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar @@ -24,7 +21,6 @@ import fr.forum_thalie.tsumugi.alarm.RadioSleeper import fr.forum_thalie.tsumugi.planning.Planning import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.forum_thalie.tsumugi.playerstore.Song -import kotlinx.android.synthetic.main.fragment_nowplaying.* class NowPlayingFragment : Fragment() { @@ -49,12 +45,12 @@ class NowPlayingFragment : Fragment() { val volumeText: TextView = root.findViewById(R.id.volume_text) val progressBar: ProgressBar = root.findViewById(R.id.progressBar) val volumeIconImage : ImageView = root.findViewById(R.id.volume_icon) - + val currentProgrammeText: TextView = root.findViewById(R.id.current_programme) val streamerPictureImageView: ImageView = root.findViewById(R.id.streamerPicture) // Note: these values are not used in the generic app, but if you want to, you can use them. val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next) - //val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next) + val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next) /* val streamerNameText : TextView = root.findViewById(R.id.streamerName) @@ -69,15 +65,14 @@ class NowPlayingFragment : Fragment() { listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP) */ - /* // trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change... // so I observe a dedicated Mutable that gets set when the queue is updated. PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer { - val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song("No queue - ") // (it.peekFirst != null ? it.peekFirst : Song() ) + val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song(noConnectionValue) // (it.peekFirst != null ? it.peekFirst : Song() ) songTitleNextText.text = t.title.value songArtistNextText.text = t.artist.value }) - + /* PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer { streamerNameText.text = it }) @@ -92,10 +87,12 @@ class NowPlayingFragment : Fragment() { songTitleText.text = it }) + Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer { - songTitleNextText.text = it + currentProgrammeText.text = "${context!!.getString(R.string.current_programme)} $it" }) + PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer { songArtistText.text = it }) @@ -273,7 +270,7 @@ class NowPlayingFragment : Fragment() { (viewHeight*100)/viewWidth else 100 - Log.d(tag, "orientation set") + //[REMOVE LOG CALLS]Log.d(tag, "orientation set") } override fun onResume() { diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ui/programme/ProgrammeFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/programme/ProgrammeFragment.kt index d6c158b..6c47b65 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ui/programme/ProgrammeFragment.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ui/programme/ProgrammeFragment.kt @@ -37,11 +37,11 @@ class ProgrammeFragment : Fragment() { viewPager.adapter = adapter val todaySundayFirst = Calendar.getInstance(Planning.instance.timeZone).get(Calendar.DAY_OF_WEEK) - 1 - viewPager.currentItem = (todaySundayFirst - 1)%7 + viewPager.currentItem = (todaySundayFirst - 1 + 7)%7 // don't do modulos on negative, seems like it's weird val tabLayout : TabLayout = root.findViewById(R.id.dayTabLayout) tabLayout.setupWithViewPager(viewPager) - Log.d(tag, "SongFragment view created") + //[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created") return root } diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/SongsFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/SongsFragment.kt index 9e99ef7..d571fab 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/SongsFragment.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/SongsFragment.kt @@ -39,7 +39,7 @@ class SongsFragment : Fragment() { val tabLayout : TabLayout = root.findViewById(R.id.tabLayout) tabLayout.setupWithViewPager(viewPager) - Log.d(tag, "SongFragment view created") + //[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created") return root } diff --git a/app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/queuelp/LastPlayedFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/queuelp/LastPlayedFragment.kt index c8f5c81..c97380c 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/queuelp/LastPlayedFragment.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/queuelp/LastPlayedFragment.kt @@ -31,7 +31,7 @@ class LastPlayedFragment : Fragment() { private val queueObserver = Observer { - Log.d(tag, lastPlayedFragmentTag + "queue changed") + //[REMOVE LOG CALLS]Log.d(tag, lastPlayedFragmentTag + "queue changed") viewAdapter.notifyDataSetChanged() } diff --git a/app/src/main/res/layout/fragment_nowplaying.xml b/app/src/main/res/layout/fragment_nowplaying.xml index aff65f3..35f5f80 100644 --- a/app/src/main/res/layout/fragment_nowplaying.xml +++ b/app/src/main/res/layout/fragment_nowplaying.xml @@ -36,7 +36,7 @@ android:contentDescription="dj-image" android:scaleType="fitStart" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/scrollViewMetadataNext" app:layout_constraintStart_toStartOf="@id/imageGuideline" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/logo_roundsquare" @@ -49,7 +49,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_percent="0.68" /> + app:layout_constraintGuide_percent="0.71" /> - - + app:layout_constraintTop_toBottomOf="@id/seek_bar_volume" + > @@ -202,7 +197,7 @@ android:textSize="16sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/upNext" - android:visibility="gone"/> + android:visibility="visible"/> + app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" + android:visibility="visible" /> + + + + + + + + + + + + + android:visibility="visible"/> + android:visibility="visible"/> + android:visibility="visible"/> @@ -346,7 +403,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" - app:layout_constraintGuide_percent="0.58" /> + app:layout_constraintGuide_percent="0.63" /> + app:layout_constraintGuide_percent="0.38" /> - + + + app:showAsAction="never"/> \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 4a7d847..dad40a0 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index d3c7b3b..cb451fd 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 569e804..574f314 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index c659012..66bef29 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 95c49f2..e5ffdf0 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 634960d..cbd03d2 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 1503838..446031c 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index d78562b..cded4d8 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 4ba5edd..c770512 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 7552001..1c1cf4a 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 0b64160..0a3e6a9 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 85a9468..166a3e3 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index ef7264d..c96da67 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index b39bfaf..a0d9df8 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 0240780..b025359 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-fr/arrays.xml b/app/src/main/res/values-fr/arrays.xml new file mode 100644 index 0000000..95848d4 --- /dev/null +++ b/app/src/main/res/values-fr/arrays.xml @@ -0,0 +1,31 @@ + + + + + @string/disable + 1 + 2 + 5 + 10 + 15 + 20 + 25 + 30 + + + + Toutes les 5 secondes (mise à jour rapide) + Toutes les 10 secondes + Toutes les 15 secondes + Toutes les 20 secondes + Toutes les 30 secondes (moins d\'utilisation de batterie) + + + 5 + 10 + 15 + 20 + 30 + + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 7646d70..0a381f0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -10,7 +10,8 @@ Volume : - Émission en cours : + À suivre : + Émission : En cours Erreur du chargement de WebView. Téléchargez Google Chrome sur le Play Store, ou activez le si vous l\'avez désactivé. Paramètres @@ -50,5 +51,8 @@ Minuterie avant fermeture de l\'application Choisir une durée (en minutes) Extinction dans %1$d minutes + Choisir la fréquence de mise à jour quand la radio est stoppée + Actualisation… + Raffraîchir les données diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 22874e6..24a0996 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -13,4 +13,19 @@ 30 + + Every 5 seconds (faster update) + Every 10 seconds + Every 15 seconds + Every 20 seconds + Every 30 seconds (less battery-intensive) + + + 5 + 10 + 15 + 20 + 30 + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4f9137f..6408717 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,6 +7,7 @@ https://github.com/yattoz/Tsumugi-app/issues/ https://tsumugi.forum-thalie.fr/ https://tsumugi.forum-thalie.fr/?feed=rss2 + https://radio.mahoro-net.org/airtime/api/live-info-v2 ADD SOME URL HERE @@ -24,7 +25,8 @@ Volume: - Émission en cours : + Up next : + Current programme : Now streaming Error loading WebView. Try downloading Google Chrome on Google Play, or enabling it if you disabled it. Settings @@ -69,5 +71,8 @@ Sleep - close app after some time Set duration (minutes) Will close in %1$d minutes + Set update period when stopped + Refreshing data… + Refresh data diff --git a/app/src/main/res/xml/customize_preferences.xml b/app/src/main/res/xml/customize_preferences.xml index b25496b..6331d4a 100644 --- a/app/src/main/res/xml/customize_preferences.xml +++ b/app/src/main/res/xml/customize_preferences.xml @@ -29,4 +29,14 @@ app:defaultValue="false" /> + + \ No newline at end of file