From c03a00244861e3d76869a312ce0de5f22a205c66 Mon Sep 17 00:00:00 2001 From: yattoz <12408848+yattoz@users.noreply.github.com> Date: Sat, 11 Jan 2020 22:51:50 +0100 Subject: [PATCH 1/6] added Airtime API and progress bar --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 3 - .../tsumugi/BootBroadcastReceiver.kt | 4 + .../fr/forum_thalie/tsumugi/MainActivity.kt | 3 + .../fr/forum_thalie/tsumugi/RadioService.kt | 27 +++++ .../java/fr/forum_thalie/tsumugi/Tickers.kt | 10 ++ .../tsumugi/playerstore/PlayerStore.kt | 114 ++++++++++++++++++ .../main/res/layout/fragment_nowplaying.xml | 6 +- app/src/main/res/values/strings.xml | 1 + 9 files changed, 164 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9a1cae0..3ac518f 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 110 + versionName "1.1.0" 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 @@ - { // 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) + { + 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) { @@ -163,6 +184,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) 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/playerstore/PlayerStore.kt b/app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt index 41037c6..a2e4b5d 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,16 @@ import android.support.v4.media.session.PlaybackStateCompat import android.util.Log import androidx.lifecycle.MutableLiveData import fr.forum_thalie.tsumugi.* +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 +34,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 @@ -43,6 +51,112 @@ class PlayerStore { 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", Locale.getDefault()) + try { + val t: Date? = dateFormat.parse(s) + 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") + if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty() + || currentSong.title.value == noConnectionValue) + currentSong.setTitleArtist(resMain.getString("name")) + + val starts = getTimestamp(resMain.getString("starts")) + val ends = getTimestamp(resMain.getString("ends")) + + if (currentSong.startTime.value != starts) + currentSong.startTime.value = starts + + currentSong.stopTime.value = ends + + // 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 = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (currentSong.startTime.value ?: getTimestamp(res.getJSONObject("station").getString("schedulerTime"))) + Log.d(tag, playerStoreTag + "latency compensator set to ${(latencyCompensator).toFloat()/1000} s") + } + currentTime.value = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (latencyCompensator) + + /* + val listeners = resMain.getInt("listeners") + listenersCount.value = listeners + 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) + + 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("meta")) + song.startTime.value = songJSON.getLong("timestamp") + song.stopTime.value = song.startTime.value + song.type.value = songJSON.getInt("type") + return song + } + + // ################################################## // ############## QUEUE / LP FUNCTIONS ############## // ################################################## diff --git a/app/src/main/res/layout/fragment_nowplaying.xml b/app/src/main/res/layout/fragment_nowplaying.xml index 5fa21cd..f876dd6 100644 --- a/app/src/main/res/layout/fragment_nowplaying.xml +++ b/app/src/main/res/layout/fragment_nowplaying.xml @@ -305,7 +305,7 @@ android:progressDrawable="@drawable/progress_bar_progress" app:layout_constraintBottom_toTopOf="@id/play_pause" tools:layout_editor_absoluteX="0dp" - android:visibility="gone"/> + android:visibility="visible"/> + android:visibility="visible"/> + android:visibility="visible"/> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4f9137f..a132784 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 From 912857c9cdbe9b85f89da2dd9a33d1a8156ed5b1 Mon Sep 17 00:00:00 2001 From: yattoz <12408848+yattoz@users.noreply.github.com> Date: Sat, 11 Jan 2020 23:31:38 +0100 Subject: [PATCH 2/6] added customization preference --- .../tsumugi/ParametersActivity.kt | 12 +++++++ .../tsumugi/preferences/CustomizeFragment.kt | 23 ++++++++++++-- app/src/main/res/values-fr/arrays.xml | 31 +++++++++++++++++++ app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/arrays.xml | 15 +++++++++ app/src/main/res/values/strings.xml | 1 + .../main/res/xml/customize_preferences.xml | 10 ++++++ 7 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/values-fr/arrays.xml 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/preferences/CustomizeFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/preferences/CustomizeFragment.kt index a41b394..ce078ea 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/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..59e9378 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -50,5 +50,6 @@ 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 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 a132784..cc293e3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -70,5 +70,6 @@ Sleep - close app after some time Set duration (minutes) Will close in %1$d minutes + Set update period when stopped 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 From c5d12c17420a6ab3cbe956dd02cd79dcbd32a99c Mon Sep 17 00:00:00 2001 From: yattoz <12408848+yattoz@users.noreply.github.com> Date: Sun, 12 Jan 2020 00:29:20 +0100 Subject: [PATCH 3/6] adding up next --- .../fr/forum_thalie/tsumugi/RadioService.kt | 1 + .../tsumugi/playerstore/PlayerStore.kt | 101 ++++++++++++++++-- .../ui/nowplaying/NowPlayingFragment.kt | 8 +- .../main/res/layout/fragment_nowplaying.xml | 7 +- app/src/main/res/values-fr/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 6 files changed, 106 insertions(+), 17 deletions(-) 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 97c09c7..a046240 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt @@ -149,6 +149,7 @@ class RadioService : MediaBrowserServiceCompat() { && it != noConnectionValue) { PlayerStore.instance.updateLp() + PlayerStore.instance.updateQueue() } nowPlayingNotification.update(this) Planning.instance.checkProgramme() 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 a2e4b5d..7d4dc51 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 @@ -77,12 +77,13 @@ class PlayerStore { // 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(resMain.getString("name")) + currentSong.setTitleArtist("${s.artist.value} - ${s.title.value}") - val starts = getTimestamp(resMain.getString("starts")) - val ends = getTimestamp(resMain.getString("ends")) + val starts = s.startTime.value + val ends = s.stopTime.value if (currentSong.startTime.value != starts) currentSong.startTime.value = starts @@ -96,7 +97,7 @@ class PlayerStore { if(isCompensatingLatency) { latencyCompensator = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (currentSong.startTime.value ?: getTimestamp(res.getJSONObject("station").getString("schedulerTime"))) - Log.d(tag, playerStoreTag + "latency compensator set to ${(latencyCompensator).toFloat()/1000} s") + Log.d(tag, "latency compensator set to ${(latencyCompensator).toFloat()/1000} s") } currentTime.value = getTimestamp(res.getJSONObject("station").getString("schedulerTime")) - (latencyCompensator) @@ -149,10 +150,10 @@ class PlayerStore { private fun extractSong(songJSON: JSONObject) : Song { val song = Song() - song.setTitleArtist(songJSON.getString("meta")) - song.startTime.value = songJSON.getLong("timestamp") - song.stopTime.value = song.startTime.value - song.type.value = songJSON.getInt("type") + 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 } @@ -161,6 +162,20 @@ class PlayerStore { // ############## QUEUE / LP FUNCTIONS ############## // ################################################## + fun updateQueue() { + if (queue.isNotEmpty()) { + queue.remove(queue.first()) + Log.d(tag, queue.toString()) + fetchLastRequest() + isQueueUpdated.value = true + } else if (isInitialized) { + fetchLastRequest() + } else { + Log.d(tag, "queue is empty! fetching anyway !!") + fetchLastRequest() + } + } + fun updateLp() { // note : lp is empty at initialization. This check was needed when we used the R/a/dio API. //if (lp.isNotEmpty()){ @@ -170,11 +185,79 @@ class PlayerStore { lp.add(0, n) currentSongBackup.copy(currentSong) isLpUpdated.value = true - Log.d(tag, playerStoreTag + lp.toString()) + Log.d(tag, lp.toString()) //} } + private fun fetchLastRequest() + { + 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") /*&& queue.isNotEmpty()*/) { + val queueJSON = + resMain.getJSONObject("next") + val t = extractSong(queueJSON) + if (queue.isNotEmpty() && t == queue.last()) + { + Log.d(tag, playerStoreTag + "Song already in there: $t") + Async(sleepScrape, post) + } else { + queue.add(queue.size, t) + Log.d(tag, playerStoreTag + "added last queue song: $t") + isQueueUpdated.value = true + } + } + } + } + + 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/ui/nowplaying/NowPlayingFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt index 73294b2..336158d 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 @@ -54,7 +54,7 @@ class NowPlayingFragment : Fragment() { // 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,7 +69,7 @@ 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 { @@ -77,7 +77,7 @@ class NowPlayingFragment : Fragment() { songTitleNextText.text = t.title.value songArtistNextText.text = t.artist.value }) - + /* PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer { streamerNameText.text = it }) @@ -92,9 +92,11 @@ class NowPlayingFragment : Fragment() { songTitleText.text = it }) + /* Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer { songTitleNextText.text = it }) + */ PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer { songArtistText.text = it diff --git a/app/src/main/res/layout/fragment_nowplaying.xml b/app/src/main/res/layout/fragment_nowplaying.xml index f876dd6..7bfcef3 100644 --- a/app/src/main/res/layout/fragment_nowplaying.xml +++ b/app/src/main/res/layout/fragment_nowplaying.xml @@ -74,7 +74,7 @@ app:layout_constraintVertical_weight="3" app:layout_constraintStart_toStartOf="@id/streamerPicture" app:layout_constraintTop_toBottomOf="@id/streamerPicture" - android:visibility="gone"/> + android:visibility="visible"/> + android:visibility="visible"/> + app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" + android:visibility="visible" /> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 59e9378..052cbe3 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 : + Prochain titre : + Émission en cours : 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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc293e3..bf5ff38 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,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 From 6a992458cc48445cffb5c398acbdd964105b7e15 Mon Sep 17 00:00:00 2001 From: yattoz <12408848+yattoz@users.noreply.github.com> Date: Sun, 12 Jan 2020 01:30:19 +0100 Subject: [PATCH 4/6] tweaked up next, added current programme, seems cool --- .../fr/forum_thalie/tsumugi/MainActivity.kt | 6 +-- .../ui/nowplaying/NowPlayingFragment.kt | 10 ++-- .../main/res/layout/fragment_nowplaying.xml | 47 +++++++++++++++---- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 5 files changed, 48 insertions(+), 19 deletions(-) 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 89d7300..b8cff04 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/MainActivity.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/MainActivity.kt @@ -129,6 +129,9 @@ 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() @@ -163,9 +166,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) 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 336158d..be04f4b 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 @@ -49,7 +49,7 @@ 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.text_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. @@ -73,7 +73,7 @@ class NowPlayingFragment : Fragment() { // 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 }) @@ -92,11 +92,11 @@ class NowPlayingFragment : Fragment() { songTitleText.text = it }) - /* + Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer { - songTitleNextText.text = it + currentProgrammeText.text = it }) - */ + PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer { songArtistText.text = it diff --git a/app/src/main/res/layout/fragment_nowplaying.xml b/app/src/main/res/layout/fragment_nowplaying.xml index 7bfcef3..39bd18b 100644 --- a/app/src/main/res/layout/fragment_nowplaying.xml +++ b/app/src/main/res/layout/fragment_nowplaying.xml @@ -74,7 +74,7 @@ app:layout_constraintVertical_weight="3" app:layout_constraintStart_toStartOf="@id/streamerPicture" app:layout_constraintTop_toBottomOf="@id/streamerPicture" - android:visibility="visible"/> + android:visibility="gone"/> - - @@ -220,7 +214,42 @@ + + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 052cbe3..323a71d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -11,7 +11,7 @@ Volume : Prochain titre : - Émission en cours : + Émission en cours : 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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bf5ff38..d2a4652 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,7 +26,7 @@ Volume: Up next : - Current programme : + Current programme : Now streaming Error loading WebView. Try downloading Google Chrome on Google Play, or enabling it if you disabled it. Settings From 378fe325883959ec79631fb1dc5b00c05e9528c3 Mon Sep 17 00:00:00 2001 From: yattoz <12408848+yattoz@users.noreply.github.com> Date: Sun, 12 Jan 2020 01:53:05 +0100 Subject: [PATCH 5/6] fixed color, fixed layout, removed todo, --- .../java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt | 1 - app/src/main/res/layout/fragment_nowplaying.xml | 2 +- app/src/main/res/values/colors.xml | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) 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 ab366ae..bbf4ff2 100644 --- a/app/src/main/java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt +++ b/app/src/main/java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt @@ -28,7 +28,6 @@ class BootBroadcastReceiver : BroadcastReceiver(){ PlayerStore.instance.initApi() if (PlayerStore.instance.streamerName.value.isNullOrBlank()) PlayerStore.instance.initPicture(context) - // TODO: add initialization for programme. val i = Intent(context, RadioService::class.java) i.putExtra("action", Actions.PLAY_OR_FALLBACK.name) diff --git a/app/src/main/res/layout/fragment_nowplaying.xml b/app/src/main/res/layout/fragment_nowplaying.xml index 39bd18b..bef43e7 100644 --- a/app/src/main/res/layout/fragment_nowplaying.xml +++ b/app/src/main/res/layout/fragment_nowplaying.xml @@ -411,7 +411,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" - app:layout_constraintGuide_percent="0.33" /> + app:layout_constraintGuide_percent="0.38" /> #999999 #999999 #7F7F7F - #4FC3F7 + #64B5F6 #527a8e #527a8e #5cb85c @@ -20,10 +20,10 @@ #585858 #f58b01 - #4FC3F7 + #64B5F6 #585858 - #4FC3F7 + #64B5F6 #f58b01 #FFFFFF From 813dc66aa185efad32029c04c4f6ace1fe68d2df Mon Sep 17 00:00:00 2001 From: yattoz <12408848+yattoz@users.noreply.github.com> Date: Sun, 12 Jan 2020 02:10:51 +0100 Subject: [PATCH 6/6] tweaked font size, should fix Fragment first selection. --- .../tsumugi/ui/programme/ProgrammeFragment.kt | 2 +- app/src/main/res/layout/fragment_nowplaying.xml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) 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..7d9f515 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,7 +37,7 @@ 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) diff --git a/app/src/main/res/layout/fragment_nowplaying.xml b/app/src/main/res/layout/fragment_nowplaying.xml index bef43e7..9bead15 100644 --- a/app/src/main/res/layout/fragment_nowplaying.xml +++ b/app/src/main/res/layout/fragment_nowplaying.xml @@ -178,7 +178,7 @@ android:layout_gravity="top" android:text="@string/up_next" android:textAlignment="center" - android:textColor="@color/whited3" + android:textColor="@color/whited5" android:layout_marginTop="8dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -193,7 +193,7 @@ android:text="" android:textAlignment="center" android:textColor="@color/whited3" - android:textSize="16sp" + android:textSize="14sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/upNext" android:visibility="visible"/> @@ -206,7 +206,7 @@ android:text="" android:textAlignment="center" android:textColor="@color/whited" - android:textSize="16sp" + android:textSize="14sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" android:visibility="visible" /> @@ -298,7 +298,7 @@ android:text="" android:textAlignment="center" android:textColor="@color/whited" - android:textSize="20sp" + android:textSize="18sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> @@ -310,7 +310,7 @@ android:text="" android:textAlignment="center" android:textColor="@color/whited3" - android:textSize="20sp" + android:textSize="18sp" app:layout_constraintBottom_toTopOf="@id/text_song_title" app:layout_constraintStart_toStartOf="parent" />