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 @@
-
{
+ 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 75e0a31..9d05752 100644
--- a/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt
+++ b/app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt
@@ -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)
+ {
+ 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)
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 d9ece9d..bf170cf 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,10 +51,131 @@ 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")
+ 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
+
+ // 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, "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("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() {
+ 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()){
@@ -61,6 +190,74 @@ class PlayerStore {
}
+ 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/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/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt
index 885e4f8..009bd3b 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,12 +49,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.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.
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 +69,15 @@ 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 +92,12 @@ 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/java/fr/forum_thalie/tsumugi/ui/programme/ProgrammeFragment.kt b/app/src/main/java/fr/forum_thalie/tsumugi/ui/programme/ProgrammeFragment.kt
index 1e08a5a..051bd7c 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 5fa21cd..9bead15 100644
--- a/app/src/main/res/layout/fragment_nowplaying.xml
+++ b/app/src/main/res/layout/fragment_nowplaying.xml
@@ -93,13 +93,6 @@
android:visibility="gone"
/>
-
-
@@ -184,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"
@@ -199,10 +193,10 @@
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="gone"/>
+ android:visibility="visible"/>
+ app:layout_constraintTop_toBottomOf="@id/text_song_artist_next"
+ android:visibility="visible" />
+
+
+
+
@@ -268,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" />
@@ -280,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" />
@@ -305,7 +335,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"/>
@@ -381,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" />
+
+
+
+ - @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..323a71d 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
@@ -50,5 +51,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/colors.xml b/app/src/main/res/values/colors.xml
index 21af4a5..7ea84da 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -11,7 +11,7 @@
#999999
#999999
#7F7F7F
- #4FC3F7
+ #64B5F6
#527a8e
#527a8e
#5cb85c
@@ -20,10 +20,10 @@
#585858
#f58b01
- #4FC3F7
+ #64B5F6
#585858
- #4FC3F7
+ #64B5F6
#f58b01
#FFFFFF
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4f9137f..d2a4652 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,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