ソースを参照

Merge branch 'tsumugi/master'

yattoz 4 年 前
コミット
8374a7888b
共有45 個のファイルを変更した499 個の追加103 個の削除を含む
  1. 2 2
      app/build.gradle
  2. 0 3
      app/src/main/AndroidManifest.xml
  3. BIN
      app/src/main/ic_launcher-web.png
  4. 4 4
      app/src/main/java/fr/forum_thalie/tsumugi/Async.kt
  5. 3 3
      app/src/main/java/fr/forum_thalie/tsumugi/BaseActivity.kt
  6. 6 1
      app/src/main/java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt
  7. 21 9
      app/src/main/java/fr/forum_thalie/tsumugi/MainActivity.kt
  8. 12 0
      app/src/main/java/fr/forum_thalie/tsumugi/ParametersActivity.kt
  9. 42 14
      app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt
  10. 10 0
      app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt
  11. 1 1
      app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioAlarm.kt
  12. 2 2
      app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioSleeper.kt
  13. 2 2
      app/src/main/java/fr/forum_thalie/tsumugi/planning/Programme.kt
  14. 199 4
      app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt
  15. 5 3
      app/src/main/java/fr/forum_thalie/tsumugi/playerstore/Song.kt
  16. 20 3
      app/src/main/java/fr/forum_thalie/tsumugi/preferences/CustomizeFragment.kt
  17. 1 1
      app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsAdapter.kt
  18. 3 3
      app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsFragment.kt
  19. 3 3
      app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsViewModel.kt
  20. 8 11
      app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt
  21. 2 2
      app/src/main/java/fr/forum_thalie/tsumugi/ui/programme/ProgrammeFragment.kt
  22. 1 1
      app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/SongsFragment.kt
  23. 1 1
      app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/queuelp/LastPlayedFragment.kt
  24. 78 21
      app/src/main/res/layout/fragment_nowplaying.xml
  25. 6 7
      app/src/main/res/menu/toolbar_menu.xml
  26. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  27. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
  28. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  29. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  30. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
  31. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  32. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  33. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
  34. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  35. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  36. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
  37. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  38. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  39. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
  40. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  41. 31 0
      app/src/main/res/values-fr/arrays.xml
  42. 5 1
      app/src/main/res/values-fr/strings.xml
  43. 15 0
      app/src/main/res/values/arrays.xml
  44. 6 1
      app/src/main/res/values/strings.xml
  45. 10 0
      app/src/main/res/xml/customize_preferences.xml

+ 2 - 2
app/build.gradle ファイルの表示

29
         applicationId "fr.forum_thalie.tsumugi"
29
         applicationId "fr.forum_thalie.tsumugi"
30
         minSdkVersion 16
30
         minSdkVersion 16
31
         targetSdkVersion 29
31
         targetSdkVersion 29
32
-        versionCode 100
33
-        versionName "1.0.0"
32
+        versionCode 113
33
+        versionName "1.1.3"
34
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
34
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
35
         vectorDrawables.useSupportLibrary = true
35
         vectorDrawables.useSupportLibrary = true
36
     }
36
     }

+ 0 - 3
app/src/main/AndroidManifest.xml ファイルの表示

31
                 <action android:name="android.media.browse.MediaBrowserService" />
31
                 <action android:name="android.media.browse.MediaBrowserService" />
32
             </intent-filter>
32
             </intent-filter>
33
         </service>
33
         </service>
34
-        <service android:name=".streamerNotificationService.StreamerMonitorService"
35
-            android:enabled="true"
36
-            android:exported="true"/>
37
 
34
 
38
         <receiver android:name=".BootBroadcastReceiver"
35
         <receiver android:name=".BootBroadcastReceiver"
39
             android:directBootAware="true"
36
             android:directBootAware="true"

BIN
app/src/main/ic_launcher-web.png ファイルの表示


+ 4 - 4
app/src/main/java/fr/forum_thalie/tsumugi/Async.kt ファイルの表示

13
             execute()
13
             execute()
14
         } catch (e: Exception)
14
         } catch (e: Exception)
15
         {
15
         {
16
-            Log.d(tag,e.toString())
16
+            //[REMOVE LOG CALLS]Log.d(tag,e.toString())
17
         }
17
         }
18
     }
18
     }
19
 
19
 
46
         }
46
         }
47
 
47
 
48
 
48
 
49
-        Log.d(tag, "fallback for no network. Store reset : $storeReset")
49
+        //[REMOVE LOG CALLS]Log.d(tag, "fallback for no network. Store reset : $storeReset")
50
     }
50
     }
51
 
51
 
52
     override fun doInBackground(vararg params: Any?): Any? {
52
     override fun doInBackground(vararg params: Any?): Any? {
53
         try {
53
         try {
54
             return handler(parameters)
54
             return handler(parameters)
55
         } catch (e: Exception) {
55
         } catch (e: Exception) {
56
-            Log.d(tag,e.toString())
56
+            //[REMOVE LOG CALLS]Log.d(tag,e.toString())
57
             onException(e)
57
             onException(e)
58
         }
58
         }
59
         return null
59
         return null
63
         try {
63
         try {
64
             post(result)
64
             post(result)
65
         } catch (e: Exception) {
65
         } catch (e: Exception) {
66
-            Log.d(tag,e.toString())
66
+            //[REMOVE LOG CALLS]Log.d(tag,e.toString())
67
             onException(e)
67
             onException(e)
68
         }
68
         }
69
     }
69
     }

+ 3 - 3
app/src/main/java/fr/forum_thalie/tsumugi/BaseActivity.kt ファイルの表示

21
         val height =  ((rootLayout?.height ?: 0))
21
         val height =  ((rootLayout?.height ?: 0))
22
         val width =  ((rootLayout?.width ?: 0))
22
         val width =  ((rootLayout?.width ?: 0))
23
 
23
 
24
-        Log.d(tag, "$viewWidth, $viewHeight, $width, $height, ${viewHeight.toDouble()/viewWidth.toDouble()}, ${height.toDouble()/width.toDouble()}")
24
+        //[REMOVE LOG CALLS]Log.d(tag, "$viewWidth, $viewHeight, $width, $height, ${viewHeight.toDouble()/viewWidth.toDouble()}, ${height.toDouble()/width.toDouble()}")
25
 
25
 
26
         val broadcastManager = LocalBroadcastManager.getInstance(this@BaseActivity)
26
         val broadcastManager = LocalBroadcastManager.getInstance(this@BaseActivity)
27
         if(height <= viewHeight * 2 / 3 /*height.toDouble()/width.toDouble() < 1.20 */){
27
         if(height <= viewHeight * 2 / 3 /*height.toDouble()/width.toDouble() < 1.20 */){
49
         // do things when keyboard is shown
49
         // do things when keyboard is shown
50
         val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
50
         val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
51
         bottomNavigationView.visibility = View.GONE
51
         bottomNavigationView.visibility = View.GONE
52
-        Log.d(tag, "bottomNav visibility set to GONE (height $keyboardHeight)")
52
+        //[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to GONE (height $keyboardHeight)")
53
     }
53
     }
54
 
54
 
55
      private fun onHideKeyboard() {
55
      private fun onHideKeyboard() {
56
         // do things when keyboard is hidden
56
         // do things when keyboard is hidden
57
         val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
57
         val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
58
         bottomNavigationView.visibility = View.VISIBLE
58
         bottomNavigationView.visibility = View.VISIBLE
59
-        Log.d(tag, "bottomNav visibility set to VISIBLE")
59
+        //[REMOVE LOG CALLS]Log.d(tag, "bottomNav visibility set to VISIBLE")
60
     }
60
     }
61
 
61
 
62
     protected fun attachKeyboardListeners() {
62
     protected fun attachKeyboardListeners() {

+ 6 - 1
app/src/main/java/fr/forum_thalie/tsumugi/BootBroadcastReceiver.kt ファイルの表示

7
 import android.util.Log
7
 import android.util.Log
8
 import androidx.preference.PreferenceManager
8
 import androidx.preference.PreferenceManager
9
 import fr.forum_thalie.tsumugi.alarm.RadioAlarm
9
 import fr.forum_thalie.tsumugi.alarm.RadioAlarm
10
+import fr.forum_thalie.tsumugi.planning.Planning
10
 import fr.forum_thalie.tsumugi.playerstore.PlayerStore
11
 import fr.forum_thalie.tsumugi.playerstore.PlayerStore
11
 
12
 
12
 class BootBroadcastReceiver : BroadcastReceiver(){
13
 class BootBroadcastReceiver : BroadcastReceiver(){
13
 
14
 
14
     override fun onReceive(context: Context, arg1: Intent) {
15
     override fun onReceive(context: Context, arg1: Intent) {
15
-        Log.d(tag, "Broadcast Receiver received $arg1")
16
+        //[REMOVE LOG CALLS]Log.d(tag, "Broadcast Receiver received $arg1")
16
         // define preferenceStore for places of the program that needs to access Preferences without a context
17
         // define preferenceStore for places of the program that needs to access Preferences without a context
17
         preferenceStore = PreferenceManager.getDefaultSharedPreferences(context)
18
         preferenceStore = PreferenceManager.getDefaultSharedPreferences(context)
18
 
19
 
22
 
23
 
23
         if (arg1.getStringExtra("action") == "$tag.${Actions.PLAY_OR_FALLBACK.name}" )
24
         if (arg1.getStringExtra("action") == "$tag.${Actions.PLAY_OR_FALLBACK.name}" )
24
         {
25
         {
26
+
25
             RadioAlarm.instance.setNextAlarm(context) // schedule next alarm
27
             RadioAlarm.instance.setNextAlarm(context) // schedule next alarm
28
+            Planning.instance.parseUrl(context = context)
29
+            if (!PlayerStore.instance.isInitialized)
30
+                PlayerStore.instance.initApi()
26
             if (PlayerStore.instance.streamerName.value.isNullOrBlank())
31
             if (PlayerStore.instance.streamerName.value.isNullOrBlank())
27
                 PlayerStore.instance.initPicture(context)
32
                 PlayerStore.instance.initPicture(context)
28
 
33
 

+ 21 - 9
app/src/main/java/fr/forum_thalie/tsumugi/MainActivity.kt ファイルの表示

15
 
15
 
16
 import java.util.Timer
16
 import java.util.Timer
17
 import android.view.MenuItem
17
 import android.view.MenuItem
18
+import com.google.android.material.snackbar.Snackbar
18
 import fr.forum_thalie.tsumugi.alarm.RadioAlarm
19
 import fr.forum_thalie.tsumugi.alarm.RadioAlarm
19
 import fr.forum_thalie.tsumugi.planning.Planning
20
 import fr.forum_thalie.tsumugi.planning.Planning
20
 
21
 
86
                 true
87
                 true
87
             }
88
             }
88
             */
89
             */
90
+            R.id.action_refresh -> {
91
+                PlayerStore.instance.queue.clear()
92
+                //PlayerStore.instance.lp.clear()
93
+                PlayerStore.instance.initApi()
94
+                val s = Snackbar.make(findViewById(R.id.nav_host_container), getString(R.string.refreshing) as CharSequence, Snackbar.LENGTH_LONG)
95
+                s.show()
96
+                true
97
+            }
89
             R.id.action_settings -> {
98
             R.id.action_settings -> {
90
                 val i = Intent(this, ParametersActivity::class.java)
99
                 val i = Intent(this, ParametersActivity::class.java)
91
                 startActivity(i)
100
                 startActivity(i)
129
         colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
138
         colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
130
         colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null))
139
         colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null))
131
 
140
 
141
+        // fetch program
142
+        Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
143
+
144
+        PlayerStore.instance.initUrl(this)
145
+        PlayerStore.instance.initApi()
146
+
132
         // Post-UI Launch
147
         // Post-UI Launch
133
         if (PlayerStore.instance.isInitialized)
148
         if (PlayerStore.instance.isInitialized)
134
         {
149
         {
135
-            Log.d(tag, "skipped initialization")
150
+            //[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
136
         } else {
151
         } else {
137
             // if the service is not started, start it in STOP mode.
152
             // if the service is not started, start it in STOP mode.
138
             // 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).
153
             // 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
             isTimerStarted = true
175
             isTimerStarted = true
161
         }
176
         }
162
 
177
 
163
-        // fetch program
164
-        Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
165
-
166
         // initialize the UI
178
         // initialize the UI
167
         setTheme(R.style.AppTheme)
179
         setTheme(R.style.AppTheme)
168
         setContentView(R.layout.activity_main)
180
         setContentView(R.layout.activity_main)
193
             val i = Intent(this, RadioService::class.java)
205
             val i = Intent(this, RadioService::class.java)
194
             i.putExtra("action", a.name)
206
             i.putExtra("action", a.name)
195
             i.putExtra("value", v)
207
             i.putExtra("value", v)
196
-            Log.d(tag, "Sending intent ${a.name}")
208
+            //[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}")
197
             startService(i)
209
             startService(i)
198
     }
210
     }
199
 
211
 
238
                 // File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
250
                 // File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
239
                 val logDirectory = File("$appDirectory/log")
251
                 val logDirectory = File("$appDirectory/log")
240
                 val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
252
                 val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
241
-                Log.d(
253
+                //[REMOVE LOG CALLS]Log.d(
242
                     tag,
254
                     tag,
243
                     "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
255
                     "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
244
                 )
256
                 )
246
                 // create app folder
258
                 // create app folder
247
                 if (!appDirectory.exists()) {
259
                 if (!appDirectory.exists()) {
248
                     appDirectory.mkdir()
260
                     appDirectory.mkdir()
249
-                    Log.d(tag, "$appDirectory created")
261
+                    //[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
250
                 }
262
                 }
251
 
263
 
252
                 // create log folder
264
                 // create log folder
253
                 if (!logDirectory.exists()) {
265
                 if (!logDirectory.exists()) {
254
                     logDirectory.mkdir()
266
                     logDirectory.mkdir()
255
-                    Log.d(tag, "$logDirectory created")
267
+                    //[REMOVE LOG CALLS]Log.d(tag, "$logDirectory created")
256
                 }
268
                 }
257
 
269
 
258
                 // clear the previous logcat and then write the new one to the file
270
                 // clear the previous logcat and then write the new one to the file
259
                 try {
271
                 try {
260
                     Runtime.getRuntime().exec("logcat -c")
272
                     Runtime.getRuntime().exec("logcat -c")
261
                     Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ")
273
                     Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ")
262
-                    Log.d(tag, "logcat started")
274
+                    //[REMOVE LOG CALLS]Log.d(tag, "logcat started")
263
                 } catch (e: IOException) {
275
                 } catch (e: IOException) {
264
                     e.printStackTrace()
276
                     e.printStackTrace()
265
                 }
277
                 }

+ 12 - 0
app/src/main/java/fr/forum_thalie/tsumugi/ParametersActivity.kt ファイルの表示

1
 package fr.forum_thalie.tsumugi
1
 package fr.forum_thalie.tsumugi
2
 
2
 
3
 import android.os.Bundle
3
 import android.os.Bundle
4
+import android.view.MenuItem
4
 import fr.forum_thalie.tsumugi.preferences.*
5
 import fr.forum_thalie.tsumugi.preferences.*
5
 
6
 
6
 
7
 
38
             .replace(R.id.parameters_host_container, fragmentToLoad)
39
             .replace(R.id.parameters_host_container, fragmentToLoad)
39
             .commit()
40
             .commit()
40
     }
41
     }
42
+
43
+    // Make the Up button function as back instead of always bringing us to the main activity
44
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
45
+        return when (item.itemId) {
46
+            android.R.id.home -> {
47
+                onBackPressed()
48
+                true
49
+            }
50
+            else -> super.onOptionsItemSelected(item)
51
+        }
52
+    }
41
 }
53
 }

+ 42 - 14
app/src/main/java/fr/forum_thalie/tsumugi/RadioService.kt ファイルの表示

68
                 // This *should* work in any case...
68
                 // This *should* work in any case...
69
                 when (intent.getIntExtra("state", -1)) {
69
                 when (intent.getIntExtra("state", -1)) {
70
                 0 -> {
70
                 0 -> {
71
-                    Log.d(tag, radioTag + "Headset is unplugged")
71
+                    //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is unplugged")
72
                 }
72
                 }
73
                 1 -> {
73
                 1 -> {
74
-                    Log.d(tag, radioTag + "Headset is plugged")
74
+                    //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Headset is plugged")
75
                     headsetPluggedIn = true
75
                     headsetPluggedIn = true
76
                 }
76
                 }
77
                 else -> {
77
                 else -> {
78
-                    Log.d(tag, radioTag + "I have no idea what the headset state is")
78
+                    //[REMOVE LOG CALLS]Log.d(tag, radioTag + "I have no idea what the headset state is")
79
                 }
79
                 }
80
                 }
80
                 }
81
                 /*
81
                 /*
91
                 }
91
                 }
92
                 else
92
                 else
93
                 {
93
                 {
94
-                    Log.d(tag, radioTag + "Can't get state?")
94
+                    //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Can't get state?")
95
                 }
95
                 }
96
 
96
 
97
                  */
97
                  */
124
 
124
 
125
     private val titleObserver = Observer<String> {
125
     private val titleObserver = Observer<String> {
126
         // We're checking if a new song arrives. If so, we put the currentSong in Lp and update the backup.
126
         // We're checking if a new song arrives. If so, we put the currentSong in Lp and update the backup.
127
+
128
+        if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_PLAYING)
129
+        {
130
+            //[REMOVE LOG CALLS]Log.d((tag, radioTag + "SONG CHANGED AND PLAYING")
131
+            // we activate latency compensation only if it's been at least 2 songs...
132
+            when {
133
+                PlayerStore.instance.isStreamDown -> {
134
+                    // if we reach here, it means that the observer has been called by a new song and that the stream was down previously.
135
+                    // so the stream is now back to normal.
136
+                    PlayerStore.instance.isStreamDown = false
137
+                    PlayerStore.instance.initApi()
138
+                }
139
+                PlayerStore.instance.currentSong.title.value == noConnectionValue -> {
140
+                    PlayerStore.instance.isStreamDown = true
141
+                }
142
+                else -> {
143
+                    PlayerStore.instance.fetchApi(/* numberOfSongs >= 2 */)
144
+                }
145
+            }
146
+        }
147
+
127
         if (PlayerStore.instance.currentSong != PlayerStore.instance.currentSongBackup
148
         if (PlayerStore.instance.currentSong != PlayerStore.instance.currentSongBackup
128
             && it != noConnectionValue)
149
             && it != noConnectionValue)
129
         {
150
         {
130
             PlayerStore.instance.updateLp()
151
             PlayerStore.instance.updateLp()
152
+            PlayerStore.instance.updateQueue()
131
         }
153
         }
132
         nowPlayingNotification.update(this)
154
         nowPlayingNotification.update(this)
133
         Planning.instance.checkProgramme()
155
         Planning.instance.checkProgramme()
163
 
185
 
164
         preferenceStore = PreferenceManager.getDefaultSharedPreferences(this)
186
         preferenceStore = PreferenceManager.getDefaultSharedPreferences(this)
165
 
187
 
188
+        // start ticker for when the player is stopped
189
+        val periodString = PreferenceManager.getDefaultSharedPreferences(this).getString("fetchPeriod", "10") ?: "10"
190
+        val period: Long = Integer.parseInt(periodString).toLong()
191
+        if (period > 0)
192
+            apiTicker.schedule(ApiFetchTick(), 0, period * 1000)
193
+
166
         // Define managers
194
         // Define managers
167
         telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
195
         telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
168
         telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
196
         telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
205
         startForeground(radioServiceId, nowPlayingNotification.notification)
233
         startForeground(radioServiceId, nowPlayingNotification.notification)
206
 
234
 
207
         PlayerStore.instance.isServiceStarted.value = true
235
         PlayerStore.instance.isServiceStarted.value = true
208
-        Log.d(tag, radioTag + "created")
236
+        //[REMOVE LOG CALLS]Log.d(tag, radioTag + "created")
209
     }
237
     }
210
 
238
 
211
     private val handler = Handler()
239
     private val handler = Handler()
246
             Actions.CANCEL_FADE_OUT.name -> { handler.removeCallbacks(lowerVolumeRunnable) }
274
             Actions.CANCEL_FADE_OUT.name -> { handler.removeCallbacks(lowerVolumeRunnable) }
247
             Actions.SNOOZE.name -> { RadioAlarm.instance.snooze(this) }
275
             Actions.SNOOZE.name -> { RadioAlarm.instance.snooze(this) }
248
         }
276
         }
249
-        Log.d(tag, radioTag + "intent received : " + intent.getStringExtra("action"))
277
+        //[REMOVE LOG CALLS]Log.d(tag, radioTag + "intent received : " + intent.getStringExtra("action"))
250
         super.onStartCommand(intent, flags, startId)
278
         super.onStartCommand(intent, flags, startId)
251
         // 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.
279
         // 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.
252
         return START_STICKY
280
         return START_STICKY
258
             stopSelf()
286
             stopSelf()
259
         }
287
         }
260
         super.onTaskRemoved(rootIntent)
288
         super.onTaskRemoved(rootIntent)
261
-        Log.d(tag, radioTag + "task removed")
289
+        //[REMOVE LOG CALLS]Log.d(tag, radioTag + "task removed")
262
     }
290
     }
263
 
291
 
264
     override fun onDestroy() {
292
     override fun onDestroy() {
290
         }
318
         }
291
 
319
 
292
         apiTicker.cancel() // stops the timer.
320
         apiTicker.cancel() // stops the timer.
293
-        Log.d(tag, radioTag + "destroyed")
321
+        //[REMOVE LOG CALLS]Log.d(tag, radioTag + "destroyed")
294
         // if the service is destroyed, the application had become useless.
322
         // if the service is destroyed, the application had become useless.
295
         exitProcess(0)
323
         exitProcess(0)
296
     }
324
     }
361
             for (i in 0 until it.length()) {
389
             for (i in 0 until it.length()) {
362
                 val entry  = it.get(i)
390
                 val entry  = it.get(i)
363
                 if (entry is IcyHeaders) {
391
                 if (entry is IcyHeaders) {
364
-                    Log.d(tag, radioTag + "onMetadata: IcyHeaders $entry")
392
+                    //[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: IcyHeaders $entry")
365
                 }
393
                 }
366
                 if (entry is IcyInfo) {
394
                 if (entry is IcyInfo) {
367
-                    Log.d(tag, radioTag + "onMetadata: Title ----> ${entry.title}")
395
+                    //[REMOVE LOG CALLS]Log.d(tag, radioTag + "onMetadata: Title ----> ${entry.title}")
368
                     // Note : Kotlin supports UTF-8 by default.
396
                     // Note : Kotlin supports UTF-8 by default.
369
                     numberOfSongs++
397
                     numberOfSongs++
370
                     val data = entry.title!!
398
                     val data = entry.title!!
439
             {
467
             {
440
                 Thread.sleep(1000)
468
                 Thread.sleep(1000)
441
                 i++
469
                 i++
442
-                Log.d(tag, "$i, isAlarmStopped=$isAlarmStopped")
470
+                //[REMOVE LOG CALLS]Log.d(tag, "$i, isAlarmStopped=$isAlarmStopped")
443
             }
471
             }
444
         }
472
         }
445
         val post: (Any?) -> Unit = {
473
         val post: (Any?) -> Unit = {
512
             SystemClock.elapsedRealtime()
540
             SystemClock.elapsedRealtime()
513
         )
541
         )
514
         mediaSession.setPlaybackState(playbackStateBuilder.build())
542
         mediaSession.setPlaybackState(playbackStateBuilder.build())
515
-        Log.d(tag, radioTag + "begin playing")
543
+        //[REMOVE LOG CALLS]Log.d(tag, radioTag + "begin playing")
516
     }
544
     }
517
 
545
 
518
     private fun pausePlaying()
546
     private fun pausePlaying()
541
             1.0f,
569
             1.0f,
542
             SystemClock.elapsedRealtime()
570
             SystemClock.elapsedRealtime()
543
         )
571
         )
544
-        Log.d(tag, radioTag + "stopped")
572
+        //[REMOVE LOG CALLS]Log.d(tag, radioTag + "stopped")
545
 
573
 
546
         mediaSession.setPlaybackState(playbackStateBuilder.build())
574
         mediaSession.setPlaybackState(playbackStateBuilder.build())
547
     }
575
     }
629
                 Player.STATE_ENDED -> state = "Player.STATE_ENDED"
657
                 Player.STATE_ENDED -> state = "Player.STATE_ENDED"
630
                 Player.STATE_READY -> state = "Player.STATE_READY"
658
                 Player.STATE_READY -> state = "Player.STATE_READY"
631
             }
659
             }
632
-            Log.d(tag, radioTag + "Player changed state: ${state}. numberOfSongs reset.")
660
+            //[REMOVE LOG CALLS]Log.d(tag, radioTag + "Player changed state: ${state}. numberOfSongs reset.")
633
         }
661
         }
634
     }
662
     }
635
 
663
 

+ 10 - 0
app/src/main/java/fr/forum_thalie/tsumugi/Tickers.kt ファイルの表示

1
 package fr.forum_thalie.tsumugi
1
 package fr.forum_thalie.tsumugi
2
 
2
 
3
+import android.support.v4.media.session.PlaybackStateCompat
3
 import fr.forum_thalie.tsumugi.playerstore.PlayerStore
4
 import fr.forum_thalie.tsumugi.playerstore.PlayerStore
4
 import java.util.*
5
 import java.util.*
5
 
6
 
7
+class ApiFetchTick  : TimerTask() {
8
+    override fun run() {
9
+        if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_STOPPED)
10
+        {
11
+            PlayerStore.instance.fetchApi()
12
+        }
13
+    }
14
+}
15
+
6
 class Tick  : TimerTask() {
16
 class Tick  : TimerTask() {
7
     override fun run() {
17
     override fun run() {
8
         PlayerStore.instance.currentTime.postValue(PlayerStore.instance.currentTime.value!! + 500)
18
         PlayerStore.instance.currentTime.postValue(PlayerStore.instance.currentTime.value!! + 500)

+ 1 - 1
app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioAlarm.kt ファイルの表示

97
         calendar.isLenient = true
97
         calendar.isLenient = true
98
         calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) + i, hourOfDay, minute)
98
         calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) + i, hourOfDay, minute)
99
 
99
 
100
-        Log.d(tag, calendar.toString())
100
+        //[REMOVE LOG CALLS]Log.d(tag, calendar.toString())
101
 
101
 
102
 
102
 
103
         return calendar.timeInMillis
103
         return calendar.timeInMillis

+ 2 - 2
app/src/main/java/fr/forum_thalie/tsumugi/alarm/RadioSleeper.kt ファイルの表示

61
 
61
 
62
             AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, currentMillis + (minutes * 60 * 1000) - (1 * 60 * 1000), fadeOutIntent)
62
             AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, currentMillis + (minutes * 60 * 1000) - (1 * 60 * 1000), fadeOutIntent)
63
             sleepAtMillis.value = System.currentTimeMillis() + (minutes * 60 * 1000) - 1 // this -1 allows to round the division for display at the right integer
63
             sleepAtMillis.value = System.currentTimeMillis() + (minutes * 60 * 1000) - 1 // this -1 allows to round the division for display at the right integer
64
-            Log.d(tag, "set sleep to $minutes minutes")
64
+            //[REMOVE LOG CALLS]Log.d(tag, "set sleep to $minutes minutes")
65
         }
65
         }
66
     }
66
     }
67
 
67
 
79
             c.startService(cancelFadeOutIntent)
79
             c.startService(cancelFadeOutIntent)
80
         }
80
         }
81
 
81
 
82
-        Log.d(tag, "cancelled sleep")
82
+        //[REMOVE LOG CALLS]Log.d(tag, "cancelled sleep")
83
         sleepAtMillis.value = null
83
         sleepAtMillis.value = null
84
     }
84
     }
85
 }
85
 }

+ 2 - 2
app/src/main/java/fr/forum_thalie/tsumugi/planning/Programme.kt ファイルの表示

35
         val isSpanningOverNight =
35
         val isSpanningOverNight =
36
             (((0b1000000 shr ((currentDay - 1) % 7) and (periodicity)) != 0) && hourEnd < hourBegin)
36
             (((0b1000000 shr ((currentDay - 1) % 7) and (periodicity)) != 0) && hourEnd < hourBegin)
37
 
37
 
38
-        Log.d(tag, "$title is today: $isToday or spanning $isSpanningOverNight")
38
+        //[REMOVE LOG CALLS]Log.d(tag, "$title is today: $isToday or spanning $isSpanningOverNight")
39
         // shr = shift-right. It's a binary mask.
39
         // shr = shift-right. It's a binary mask.
40
 
40
 
41
         // if the program started yesterday, and spanned over night, it means that there could be a chance that it's still active.
41
         // if the program started yesterday, and spanned over night, it means that there could be a chance that it's still active.
87
      */
87
      */
88
 
88
 
89
     init {
89
     init {
90
-        Log.d(tag, this.toString())
90
+        //[REMOVE LOG CALLS]Log.d(tag, this.toString())
91
     }
91
     }
92
 }
92
 }

+ 199 - 4
app/src/main/java/fr/forum_thalie/tsumugi/playerstore/PlayerStore.kt ファイルの表示

7
 import android.util.Log
7
 import android.util.Log
8
 import androidx.lifecycle.MutableLiveData
8
 import androidx.lifecycle.MutableLiveData
9
 import fr.forum_thalie.tsumugi.*
9
 import fr.forum_thalie.tsumugi.*
10
+import fr.forum_thalie.tsumugi.planning.Planning
11
+import org.json.JSONObject
12
+import java.net.URL
13
+import java.text.ParseException
14
+import java.text.SimpleDateFormat
15
+import java.util.*
16
+import kotlin.collections.ArrayList
10
 
17
 
11
 class PlayerStore {
18
 class PlayerStore {
12
 
19
 
20
+    private lateinit var urlToScrape: String
13
     val isPlaying: MutableLiveData<Boolean> = MutableLiveData()
21
     val isPlaying: MutableLiveData<Boolean> = MutableLiveData()
14
     val isServiceStarted: MutableLiveData<Boolean> = MutableLiveData()
22
     val isServiceStarted: MutableLiveData<Boolean> = MutableLiveData()
15
     val volume: MutableLiveData<Int> = MutableLiveData()
23
     val volume: MutableLiveData<Int> = MutableLiveData()
27
     val listenersCount: MutableLiveData<Int> = MutableLiveData()
35
     val listenersCount: MutableLiveData<Int> = MutableLiveData()
28
     var latencyCompensator : Long = 0
36
     var latencyCompensator : Long = 0
29
     var isInitialized: Boolean = false
37
     var isInitialized: Boolean = false
38
+    var isStreamDown: Boolean = false
30
 
39
 
31
     init {
40
     init {
32
         playbackState.value = PlaybackStateCompat.STATE_STOPPED
41
         playbackState.value = PlaybackStateCompat.STATE_STOPPED
38
         isQueueUpdated.value = false
47
         isQueueUpdated.value = false
39
         isLpUpdated.value = false
48
         isLpUpdated.value = false
40
         isMuted.value = false
49
         isMuted.value = false
41
-        currentSong.title.value = noConnectionValue
42
-        currentSongBackup.title.value = noConnectionValue
50
+        currentSong.setTitleArtist(noConnectionValue)
51
+        currentSongBackup.setTitleArtist(noConnectionValue)
43
         listenersCount.value = 0
52
         listenersCount.value = 0
44
     }
53
     }
45
 
54
 
55
+    fun initUrl(c: Context)
56
+    {
57
+        urlToScrape = c.getString(R.string.API_URL)
58
+    }
59
+
60
+    private fun getTimestamp(s: String) : Long
61
+    {
62
+        val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss z", Locale.getDefault())
63
+        try {
64
+            val t: Date? = dateFormat.parse("$s ${Planning.instance.timeZone.id}")
65
+            //[REMOVE LOG CALLS]Log.d(tag, "date: $s -> $t")
66
+            return t!!.time
67
+        } catch (e: ParseException) {
68
+            e.printStackTrace()
69
+        }
70
+        return 0
71
+    }
72
+
73
+
74
+    // ##################################################
75
+    // ################# API FUNCTIONS ##################
76
+    // ##################################################
77
+
78
+    private fun updateApi(res: JSONObject, isCompensatingLatency : Boolean = false) {
79
+        // If we're not in PLAYING state, update title / artist metadata. If we're playing, the ICY will take care of that.
80
+
81
+        val resMain = res.getJSONObject("tracks").getJSONObject("current")
82
+        val s = extractSong(resMain)
83
+        if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty()
84
+            || currentSong.title.value == noConnectionValue)
85
+            currentSong.setTitleArtist("${s.artist.value} - ${s.title.value}")
86
+
87
+        val starts = s.startTime.value
88
+        val ends = s.stopTime.value
89
+
90
+        if (currentSong.startTime.value != starts)
91
+            currentSong.startTime.value = starts
92
+
93
+        currentSong.stopTime.value = ends
94
+
95
+        val apiTime = getTimestamp(res.getJSONObject("station").getString("schedulerTime"))
96
+        // I noticed that the server has a big (3 to 9 seconds !!) offset for current time.
97
+        // we can measure it when the player is playing, to compensate it and have our progress bar perfectly timed
98
+        // 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),
99
+        // 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.
100
+        if(isCompensatingLatency)
101
+        {
102
+            latencyCompensator = apiTime - (currentSong.startTime.value!!)
103
+            //[REMOVE LOG CALLS]Log.d(tag, "latency compensator set to ${(latencyCompensator).toFloat() / 1000} s")
104
+        }
105
+        currentTime.value = apiTime - (latencyCompensator)
106
+
107
+        /*
108
+        val listeners = resMain.getInt("listeners")
109
+        listenersCount.value = listeners
110
+        //[REMOVE LOG CALLS]Log.d((tag, playerStoreTag +  "store updated")
111
+         */
112
+    }
113
+
114
+    private val scrape : (Any?) -> String =
115
+        {
116
+            URL(urlToScrape).readText()
117
+        }
118
+
119
+    /* initApi is called :
120
+        - at startup
121
+        - when a streamer changes.
122
+        the idea is to fetch the queue when a streamer changes (potentially Hanyuu), and at startup.
123
+        The Last Played is only fetched if it's empty (so, only at startup), not when a streamer changes.
124
+     */
125
+    fun initApi()
126
+    {
127
+        val post : (parameter: Any?) -> Unit = {
128
+            val result = JSONObject(it as String)
129
+            if (result.has("tracks"))
130
+            {
131
+                updateApi(result)
132
+                currentSongBackup.copy(currentSong)
133
+                fetchLastRequest()
134
+                isQueueUpdated.value = true
135
+
136
+                isLpUpdated.value = true
137
+            }
138
+            isInitialized = true
139
+        }
140
+        Async(scrape, post)
141
+    }
142
+
143
+    fun fetchApi(isCompensatingLatency: Boolean = false) {
144
+        val post: (parameter: Any?) -> Unit = {
145
+            val result = JSONObject(it as String)
146
+            if (!result.isNull("tracks"))
147
+            {
148
+                updateApi(result, isCompensatingLatency)
149
+            }
150
+        }
151
+        Async(scrape, post)
152
+    }
153
+
154
+    private fun extractSong(songJSON: JSONObject) : Song {
155
+        val song = Song()
156
+        song.setTitleArtist(songJSON.getString("name"))
157
+        song.startTime.value = getTimestamp(songJSON.getString("starts"))
158
+        song.stopTime.value = getTimestamp(songJSON.getString("ends"))
159
+        song.type.value = 0 // only used for R/a/dio
160
+        return song
161
+    }
162
+
163
+
46
     // ##################################################
164
     // ##################################################
47
     // ############## QUEUE / LP FUNCTIONS ##############
165
     // ############## QUEUE / LP FUNCTIONS ##############
48
     // ##################################################
166
     // ##################################################
49
 
167
 
168
+    fun updateQueue() {
169
+        //[REMOVE LOG CALLS]Log.d(tag, queue.toString())
170
+        fetchLastRequest()
171
+    }
172
+
50
     fun updateLp() {
173
     fun updateLp() {
51
         // note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
174
         // note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
52
         //if (lp.isNotEmpty()){
175
         //if (lp.isNotEmpty()){
53
             val n = Song()
176
             val n = Song()
54
             n.copy(currentSongBackup)
177
             n.copy(currentSongBackup)
55
-            if (n.title.value != noConnectionValue && n.title.value != streamDownValue)
178
+            if (n != Song(noConnectionValue) && n != Song(streamDownValue))
56
                 lp.add(0, n)
179
                 lp.add(0, n)
57
             currentSongBackup.copy(currentSong)
180
             currentSongBackup.copy(currentSong)
58
             isLpUpdated.value = true
181
             isLpUpdated.value = true
59
-            Log.d(tag, playerStoreTag +  lp.toString())
182
+            //[REMOVE LOG CALLS]Log.d(tag, playerStoreTag +  lp.toString())
60
         //}
183
         //}
61
     }
184
     }
62
 
185
 
63
 
186
 
187
+    private fun fetchLastRequest()
188
+    {
189
+        isQueueUpdated.value = false
190
+        val sleepScrape: (Any?) -> String = {
191
+            /* 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.
192
+             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)
193
+             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.
194
+            This way to fetch at the most probable time is a good compromise between fetch speed and fetch frequency
195
+            We don't fetch too often, and we start to fetch at the most *probable* time.
196
+            If there's no latencyCompensator measured yet, we only wait for 3 seconds.
197
+            If the song is the same, it will be called again. 3 seconds is a good compromise between speed and frequency:
198
+            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.
199
+             */
200
+            val sleepTime: Long = if (latencyCompensator > 0) latencyCompensator + 2000 else 3000
201
+            Thread.sleep(sleepTime) // we wait a bit (10s) for the API to get updated on R/a/dio side!
202
+            URL(urlToScrape).readText()
203
+        }
204
+
205
+        lateinit var post: (parameter: Any?) -> Unit
206
+
207
+        fun postFun(result: JSONObject)
208
+        {
209
+            if (result.has("tracks")) {
210
+                val resMain = result.getJSONObject("tracks")
211
+                /*
212
+                if ((resMain.has("isafkstream") && !resMain.getBoolean("isafkstream")) &&
213
+                    queue.isNotEmpty())
214
+                {
215
+                    queue.clear() //we're not requesting anything anymore.
216
+                    isQueueUpdated.value = true
217
+                } else if (resMain.has("isafkstream") && resMain.getBoolean("isafkstream") &&
218
+                    queue.isEmpty())
219
+                {
220
+                    initApi()
221
+                } else
222
+                */
223
+                if (resMain.has("next")) {
224
+                    val queueJSON =
225
+                        resMain.getJSONObject("next")
226
+                    val t = extractSong(queueJSON)
227
+                    if (queue.isNotEmpty() && (t == queue.last() || t == currentSong) && isQueueUpdated.value == false)
228
+                    {
229
+                        //[REMOVE LOG CALLS]Log.d(tag, playerStoreTag +  "Song already in there: $t\nQueue:$queue")
230
+                        Async(sleepScrape, post)
231
+                    } else {
232
+                        if (queue.isNotEmpty())
233
+                            queue.remove(queue.first())
234
+                        queue.add(queue.size, t)
235
+                        //[REMOVE LOG CALLS]Log.d(tag, playerStoreTag +  "added last queue song: $t")
236
+                        isQueueUpdated.value = true
237
+                        return // FUUUCK IT WAS CALLING THE ASYNC ONE MORE TIME AFTERWARDS !?
238
+                    }
239
+                }
240
+            }
241
+        }
242
+
243
+        post = {
244
+            val result = JSONObject(it as String)
245
+            /*  The goal is to pass the result to a function that will process it (postFun).
246
+                The magic trick is, under circumstances, the last queue song might not have been updated yet when we fetch it.
247
+                So if this is detected ==> if (t == queue.last() )
248
+                Then the function re-schedule an Async(sleepScrape, post).
249
+                To do that, the "post" must be defined BEFORE the function, but the function must be defined BEFORE the "post" value.
250
+                So I declare "post" as lateinit var, define the function, then define the "post" that calls the function. IT SHOULD WORK.
251
+             */
252
+            postFun(result)
253
+        }
254
+
255
+        Async(sleepScrape, post)
256
+    }
257
+
258
+
64
     // ##################################################
259
     // ##################################################
65
     // ############## PICTURE FUNCTIONS #################
260
     // ############## PICTURE FUNCTIONS #################
66
     // ##################################################
261
     // ##################################################

+ 5 - 3
app/src/main/java/fr/forum_thalie/tsumugi/playerstore/Song.kt ファイルの表示

1
 package fr.forum_thalie.tsumugi.playerstore
1
 package fr.forum_thalie.tsumugi.playerstore
2
 
2
 
3
+import androidx.core.text.HtmlCompat
3
 import androidx.lifecycle.MutableLiveData
4
 import androidx.lifecycle.MutableLiveData
4
 import fr.forum_thalie.tsumugi.noConnectionValue
5
 import fr.forum_thalie.tsumugi.noConnectionValue
5
 
6
 
24
         return "id=$id | ${artist.value} - ${title.value} | type=${type.value} | times ${startTime.value} - ${stopTime.value}\n"
25
         return "id=$id | ${artist.value} - ${title.value} | type=${type.value} | times ${startTime.value} - ${stopTime.value}\n"
25
     }
26
     }
26
 
27
 
27
-    fun setTitleArtist(data: String)
28
+    fun setTitleArtist(dataHtml: String)
28
     {
29
     {
30
+        val data = HtmlCompat.fromHtml(dataHtml, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
29
         val hyphenPos = data.indexOf(" - ")
31
         val hyphenPos = data.indexOf(" - ")
30
         try {
32
         try {
31
             if (hyphenPos < 0)
33
             if (hyphenPos < 0)
46
     override fun equals(other: Any?) : Boolean
48
     override fun equals(other: Any?) : Boolean
47
     {
49
     {
48
         val song: Song = other as Song
50
         val song: Song = other as Song
49
-        return this.title.value == song.title.value && this.artist.value == song.artist.value
51
+        return this.title.value === song.title.value && this.artist.value === song.artist.value
50
     }
52
     }
51
 
53
 
52
     fun copy(song: Song) {
54
     fun copy(song: Song) {
53
-        this.title.value = song.title.value
54
         this.artist.value = song.artist.value
55
         this.artist.value = song.artist.value
56
+        this.title. value = song.title.value
55
         this.startTime.value = song.startTime.value
57
         this.startTime.value = song.startTime.value
56
         this.stopTime.value = song.stopTime.value
58
         this.stopTime.value = song.stopTime.value
57
         this.type.value = song.type.value
59
         this.type.value = song.type.value

+ 20 - 3
app/src/main/java/fr/forum_thalie/tsumugi/preferences/CustomizeFragment.kt ファイルの表示

1
 package fr.forum_thalie.tsumugi.preferences
1
 package fr.forum_thalie.tsumugi.preferences
2
 
2
 
3
-import android.content.Intent
4
-import android.net.Uri
5
 import android.os.Bundle
3
 import android.os.Bundle
6
-import android.util.Log
7
 import androidx.appcompat.app.AlertDialog
4
 import androidx.appcompat.app.AlertDialog
8
 import androidx.preference.*
5
 import androidx.preference.*
9
 import fr.forum_thalie.tsumugi.R
6
 import fr.forum_thalie.tsumugi.R
40
             true
37
             true
41
         }
38
         }
42
 
39
 
40
+
41
+        val fetchPeriod = preferenceScreen.findPreference<ListPreference>("fetchPeriod")
42
+        fetchPeriod?.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
43
+        fetchPeriod?.setOnPreferenceChangeListener { _, newValue ->
44
+            val builder1 = AlertDialog.Builder(context!!)
45
+            if (Integer.parseInt(newValue as String) == 0)
46
+                builder1.setMessage(R.string.restart_the_app)
47
+            else
48
+                builder1.setMessage(R.string.restart_the_app)
49
+            builder1.setCancelable(true)
50
+
51
+            builder1.setPositiveButton("Close" ) { dialog, _ ->
52
+                dialog.cancel()
53
+            }
54
+
55
+            val alert11 = builder1.create()
56
+            alert11.show()
57
+            true
58
+        }
59
+
43
     }
60
     }
44
 }
61
 }

+ 1 - 1
app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsAdapter.kt ファイルの表示

40
     override fun doInBackground(vararg params: TextView?): Bitmap? {
40
     override fun doInBackground(vararg params: TextView?): Bitmap? {
41
         t = params[0]
41
         t = params[0]
42
         return try {
42
         return try {
43
-            //Log.d(LOG_CAT, "Downloading the image from: $source")
43
+            ////[REMOVE LOG CALLS]//[REMOVE LOG CALLS]Log.d(LOG_CAT, "Downloading the image from: $source")
44
             var k: InputStream? = null
44
             var k: InputStream? = null
45
             var pic: Bitmap? = null
45
             var pic: Bitmap? = null
46
             try {
46
             try {

+ 3 - 3
app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsFragment.kt ファイルの表示

43
                 }
43
                 }
44
 
44
 
45
                 newsViewModel.isWebViewLoaded = true
45
                 newsViewModel.isWebViewLoaded = true
46
-                Log.d(tag, "webview created")
46
+                //[REMOVE LOG CALLS]Log.d(tag, "webview created")
47
             } else {
47
             } else {
48
-                Log.d(tag, "webview already created!?")
48
+                //[REMOVE LOG CALLS]Log.d(tag, "webview already created!?")
49
             }
49
             }
50
 
50
 
51
             newsViewModel.root.addOnLayoutChangeListener(orientationLayoutListener)
51
             newsViewModel.root.addOnLayoutChangeListener(orientationLayoutListener)
101
             ViewModelProviders.of(this).get(NewsViewModel::class.java)
101
             ViewModelProviders.of(this).get(NewsViewModel::class.java)
102
 
102
 
103
         newsViewModel.fetch(c = context!!, isPreloading = true)
103
         newsViewModel.fetch(c = context!!, isPreloading = true)
104
-        Log.d(tag, "news fetched onCreate")
104
+        //[REMOVE LOG CALLS]Log.d(tag, "news fetched onCreate")
105
         super.onCreate(savedInstanceState)
105
         super.onCreate(savedInstanceState)
106
     }
106
     }
107
 }
107
 }

+ 3 - 3
app/src/main/java/fr/forum_thalie/tsumugi/ui/news/NewsViewModel.kt ファイルの表示

43
 
43
 
44
         val maxNumberOfArticles = 5
44
         val maxNumberOfArticles = 5
45
         coroutineScope.launch(Dispatchers.Main) {
45
         coroutineScope.launch(Dispatchers.Main) {
46
-            Log.d(tag, "launching coroutine")
46
+            //[REMOVE LOG CALLS]Log.d(tag, "launching coroutine")
47
                 val parser = Parser()
47
                 val parser = Parser()
48
             try {
48
             try {
49
                 val articleList = parser.getArticles(urlToScrape)
49
                 val articleList = parser.getArticles(urlToScrape)
50
                 newsArray.clear()
50
                 newsArray.clear()
51
                 for (i in 0 until min(articleList.size, maxNumberOfArticles)) {
51
                 for (i in 0 until min(articleList.size, maxNumberOfArticles)) {
52
                     val item = articleList[i]
52
                     val item = articleList[i]
53
-                    Log.d(tag, "i = $i / ${articleList.size}")
53
+                    //[REMOVE LOG CALLS]Log.d(tag, "i = $i / ${articleList.size}")
54
                     val news = News()
54
                     val news = News()
55
                     news.title = item.title ?: ""
55
                     news.title = item.title ?: ""
56
                     news.link = item.link ?: urlToScrape
56
                     news.link = item.link ?: urlToScrape
60
 
60
 
61
                     val formatter6 = SimpleDateFormat(newsDateTimePattern, Locale.ENGLISH)
61
                     val formatter6 = SimpleDateFormat(newsDateTimePattern, Locale.ENGLISH)
62
                     val dateString = item.pubDate.toString()
62
                     val dateString = item.pubDate.toString()
63
-                    Log.d(tag, "$news --- $dateString")
63
+                    //[REMOVE LOG CALLS]Log.d(tag, "$news --- $dateString")
64
 
64
 
65
                     news.date = formatter6.parse(dateString) ?: Date(0)
65
                     news.date = formatter6.parse(dateString) ?: Date(0)
66
 
66
 

+ 8 - 11
app/src/main/java/fr/forum_thalie/tsumugi/ui/nowplaying/NowPlayingFragment.kt ファイルの表示

6
 import androidx.lifecycle.ViewModelProviders
6
 import androidx.lifecycle.ViewModelProviders
7
 import android.os.Bundle
7
 import android.os.Bundle
8
 import android.support.v4.media.session.PlaybackStateCompat
8
 import android.support.v4.media.session.PlaybackStateCompat
9
-import android.util.Log
10
-import android.util.TypedValue
11
 import androidx.fragment.app.Fragment
9
 import androidx.fragment.app.Fragment
12
 import android.view.LayoutInflater
10
 import android.view.LayoutInflater
13
 import android.view.View
11
 import android.view.View
15
 import android.widget.*
13
 import android.widget.*
16
 import androidx.constraintlayout.widget.ConstraintLayout
14
 import androidx.constraintlayout.widget.ConstraintLayout
17
 import androidx.constraintlayout.widget.ConstraintSet
15
 import androidx.constraintlayout.widget.ConstraintSet
18
-import androidx.core.widget.TextViewCompat
19
 import androidx.lifecycle.Observer
16
 import androidx.lifecycle.Observer
20
 import com.google.android.material.snackbar.BaseTransientBottomBar
17
 import com.google.android.material.snackbar.BaseTransientBottomBar
21
 import com.google.android.material.snackbar.Snackbar
18
 import com.google.android.material.snackbar.Snackbar
24
 import fr.forum_thalie.tsumugi.planning.Planning
21
 import fr.forum_thalie.tsumugi.planning.Planning
25
 import fr.forum_thalie.tsumugi.playerstore.PlayerStore
22
 import fr.forum_thalie.tsumugi.playerstore.PlayerStore
26
 import fr.forum_thalie.tsumugi.playerstore.Song
23
 import fr.forum_thalie.tsumugi.playerstore.Song
27
-import kotlinx.android.synthetic.main.fragment_nowplaying.*
28
 
24
 
29
 
25
 
30
 class NowPlayingFragment : Fragment() {
26
 class NowPlayingFragment : Fragment() {
49
         val volumeText: TextView = root.findViewById(R.id.volume_text)
45
         val volumeText: TextView = root.findViewById(R.id.volume_text)
50
         val progressBar: ProgressBar = root.findViewById(R.id.progressBar)
46
         val progressBar: ProgressBar = root.findViewById(R.id.progressBar)
51
         val volumeIconImage : ImageView = root.findViewById(R.id.volume_icon)
47
         val volumeIconImage : ImageView = root.findViewById(R.id.volume_icon)
52
-
48
+        val currentProgrammeText: TextView  = root.findViewById(R.id.current_programme)
53
         val streamerPictureImageView: ImageView = root.findViewById(R.id.streamerPicture)
49
         val streamerPictureImageView: ImageView = root.findViewById(R.id.streamerPicture)
54
 
50
 
55
         // Note: these values are not used in the generic app, but if you want to, you can use them.
51
         // Note: these values are not used in the generic app, but if you want to, you can use them.
56
         val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next)
52
         val songTitleNextText: TextView = root.findViewById(R.id.text_song_title_next)
57
-        //val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next)
53
+        val songArtistNextText: TextView = root.findViewById(R.id.text_song_artist_next)
58
 
54
 
59
         /*
55
         /*
60
         val streamerNameText : TextView = root.findViewById(R.id.streamerName)
56
         val streamerNameText : TextView = root.findViewById(R.id.streamerName)
69
             listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP)
65
             listenersText,8, 16, 2, TypedValue.COMPLEX_UNIT_SP)
70
          */
66
          */
71
 
67
 
72
-        /*
73
         // trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change...
68
         // trick : I can't observe the queue because it's an ArrayDeque that doesn't trigger any change...
74
         // so I observe a dedicated Mutable that gets set when the queue is updated.
69
         // so I observe a dedicated Mutable that gets set when the queue is updated.
75
         PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer {
70
         PlayerStore.instance.isQueueUpdated.observe(viewLifecycleOwner, Observer {
76
-            val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song("No queue - ") // (it.peekFirst != null ? it.peekFirst : Song() )
71
+            val t = if (PlayerStore.instance.queue.size > 0) PlayerStore.instance.queue[0] else Song(noConnectionValue) // (it.peekFirst != null ? it.peekFirst : Song() )
77
             songTitleNextText.text = t.title.value
72
             songTitleNextText.text = t.title.value
78
             songArtistNextText.text = t.artist.value
73
             songArtistNextText.text = t.artist.value
79
         })
74
         })
80
-
75
+        /*
81
         PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
76
         PlayerStore.instance.streamerName.observe(viewLifecycleOwner, Observer {
82
             streamerNameText.text = it
77
             streamerNameText.text = it
83
         })
78
         })
92
             songTitleText.text = it
87
             songTitleText.text = it
93
         })
88
         })
94
 
89
 
90
+
95
         Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer {
91
         Planning.instance.currentProgramme.observe(viewLifecycleOwner, Observer {
96
-            songTitleNextText.text = it
92
+            currentProgrammeText.text = "${context!!.getString(R.string.current_programme)} $it"
97
         })
93
         })
98
 
94
 
95
+
99
         PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer {
96
         PlayerStore.instance.currentSong.artist.observe(viewLifecycleOwner, Observer {
100
             songArtistText.text = it
97
             songArtistText.text = it
101
         })
98
         })
273
                 (viewHeight*100)/viewWidth
270
                 (viewHeight*100)/viewWidth
274
         else
271
         else
275
             100
272
             100
276
-        Log.d(tag, "orientation set")
273
+        //[REMOVE LOG CALLS]Log.d(tag, "orientation set")
277
     }
274
     }
278
 
275
 
279
     override fun onResume() {
276
     override fun onResume() {

+ 2 - 2
app/src/main/java/fr/forum_thalie/tsumugi/ui/programme/ProgrammeFragment.kt ファイルの表示

37
 
37
 
38
         viewPager.adapter = adapter
38
         viewPager.adapter = adapter
39
         val todaySundayFirst = Calendar.getInstance(Planning.instance.timeZone).get(Calendar.DAY_OF_WEEK) - 1
39
         val todaySundayFirst = Calendar.getInstance(Planning.instance.timeZone).get(Calendar.DAY_OF_WEEK) - 1
40
-        viewPager.currentItem = (todaySundayFirst - 1)%7
40
+        viewPager.currentItem = (todaySundayFirst - 1 + 7)%7 // don't do modulos on negative, seems like it's weird
41
 
41
 
42
         val tabLayout : TabLayout = root.findViewById(R.id.dayTabLayout)
42
         val tabLayout : TabLayout = root.findViewById(R.id.dayTabLayout)
43
         tabLayout.setupWithViewPager(viewPager)
43
         tabLayout.setupWithViewPager(viewPager)
44
-        Log.d(tag, "SongFragment view created")
44
+        //[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created")
45
 
45
 
46
         return root
46
         return root
47
     }
47
     }

+ 1 - 1
app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/SongsFragment.kt ファイルの表示

39
 
39
 
40
         val tabLayout : TabLayout = root.findViewById(R.id.tabLayout)
40
         val tabLayout : TabLayout = root.findViewById(R.id.tabLayout)
41
         tabLayout.setupWithViewPager(viewPager)
41
         tabLayout.setupWithViewPager(viewPager)
42
-        Log.d(tag, "SongFragment view created")
42
+        //[REMOVE LOG CALLS]Log.d(tag, "SongFragment view created")
43
 
43
 
44
         return root
44
         return root
45
     }
45
     }

+ 1 - 1
app/src/main/java/fr/forum_thalie/tsumugi/ui/songs/queuelp/LastPlayedFragment.kt ファイルの表示

31
 
31
 
32
 
32
 
33
     private val queueObserver = Observer<Boolean> {
33
     private val queueObserver = Observer<Boolean> {
34
-        Log.d(tag, lastPlayedFragmentTag + "queue changed")
34
+        //[REMOVE LOG CALLS]Log.d(tag, lastPlayedFragmentTag + "queue changed")
35
         viewAdapter.notifyDataSetChanged()
35
         viewAdapter.notifyDataSetChanged()
36
     }
36
     }
37
 
37
 

+ 78 - 21
app/src/main/res/layout/fragment_nowplaying.xml ファイルの表示

36
             android:contentDescription="dj-image"
36
             android:contentDescription="dj-image"
37
             android:scaleType="fitStart"
37
             android:scaleType="fitStart"
38
             app:layout_constraintEnd_toEndOf="parent"
38
             app:layout_constraintEnd_toEndOf="parent"
39
-            app:layout_constraintBottom_toBottomOf="parent"
39
+            app:layout_constraintBottom_toBottomOf="@id/scrollViewMetadataNext"
40
             app:layout_constraintStart_toStartOf="@id/imageGuideline"
40
             app:layout_constraintStart_toStartOf="@id/imageGuideline"
41
             app:layout_constraintTop_toTopOf="parent"
41
             app:layout_constraintTop_toTopOf="parent"
42
             app:srcCompat="@drawable/logo_roundsquare"
42
             app:srcCompat="@drawable/logo_roundsquare"
49
             android:layout_width="wrap_content"
49
             android:layout_width="wrap_content"
50
             android:layout_height="wrap_content"
50
             android:layout_height="wrap_content"
51
             android:orientation="vertical"
51
             android:orientation="vertical"
52
-            app:layout_constraintGuide_percent="0.68" />
52
+            app:layout_constraintGuide_percent="0.71" />
53
 
53
 
54
         <androidx.constraintlayout.widget.Guideline
54
         <androidx.constraintlayout.widget.Guideline
55
             android:id="@+id/imageLeftGuideline"
55
             android:id="@+id/imageLeftGuideline"
93
             android:visibility="gone"
93
             android:visibility="gone"
94
             />
94
             />
95
 
95
 
96
-        <androidx.constraintlayout.widget.Guideline
97
-            android:id="@+id/topInfoGuideline"
98
-            android:layout_width="wrap_content"
99
-            android:layout_height="wrap_content"
100
-            android:orientation="horizontal"
101
-            app:layout_constraintGuide_percent="0.95" />
102
-
103
         <TextView
96
         <TextView
104
             android:id="@+id/sleepInfo"
97
             android:id="@+id/sleepInfo"
105
             android:layout_width="0dp"
98
             android:layout_width="0dp"
147
         <ImageView
140
         <ImageView
148
             android:id="@+id/volume_icon"
141
             android:id="@+id/volume_icon"
149
             android:layout_width="wrap_content"
142
             android:layout_width="wrap_content"
150
-            android:layout_height="24dp"
143
+            android:layout_height="20dp"
151
             android:contentDescription="@string/volume"
144
             android:contentDescription="@string/volume"
152
             android:src="@drawable/ic_volume_high"
145
             android:src="@drawable/ic_volume_high"
153
             android:textSize="12sp"
146
             android:textSize="12sp"
162
             android:layout_width="0dp"
155
             android:layout_width="0dp"
163
             android:layout_height="0dp"
156
             android:layout_height="0dp"
164
             android:fillViewport="true"
157
             android:fillViewport="true"
165
-            app:layout_constraintBottom_toTopOf="@id/topInfoGuideline"
158
+            android:layout_marginBottom="4dp"
159
+            app:layout_constraintBottom_toTopOf="@id/scrollProgramme"
166
             app:layout_constraintEnd_toStartOf="@id/streamerPicture"
160
             app:layout_constraintEnd_toStartOf="@id/streamerPicture"
167
             app:layout_constraintStart_toStartOf="parent"
161
             app:layout_constraintStart_toStartOf="parent"
168
-            app:layout_constraintTop_toBottomOf="@id/seek_bar_volume">
162
+            app:layout_constraintTop_toBottomOf="@id/seek_bar_volume"
163
+            >
169
 
164
 
170
 
165
 
171
             <androidx.constraintlayout.widget.ConstraintLayout
166
             <androidx.constraintlayout.widget.ConstraintLayout
184
                     android:layout_gravity="top"
179
                     android:layout_gravity="top"
185
                     android:text="@string/up_next"
180
                     android:text="@string/up_next"
186
                     android:textAlignment="center"
181
                     android:textAlignment="center"
187
-                    android:textColor="@color/whited3"
188
-                    android:layout_marginTop="8dp"
182
+                    android:textColor="@color/whited5"
183
+                    android:layout_marginTop="0dp"
189
                     app:layout_constraintStart_toStartOf="parent"
184
                     app:layout_constraintStart_toStartOf="parent"
190
                     app:layout_constraintEnd_toEndOf="parent"
185
                     app:layout_constraintEnd_toEndOf="parent"
191
                     app:layout_constraintTop_toTopOf="parent" />
186
                     app:layout_constraintTop_toTopOf="parent" />
202
                     android:textSize="16sp"
197
                     android:textSize="16sp"
203
                     app:layout_constraintStart_toStartOf="parent"
198
                     app:layout_constraintStart_toStartOf="parent"
204
                     app:layout_constraintTop_toBottomOf="@id/upNext"
199
                     app:layout_constraintTop_toBottomOf="@id/upNext"
205
-                    android:visibility="gone"/>
200
+                    android:visibility="visible"/>
206
 
201
 
207
                 <TextView
202
                 <TextView
208
                     android:id="@+id/text_song_title_next"
203
                     android:id="@+id/text_song_title_next"
214
                     android:textColor="@color/whited"
209
                     android:textColor="@color/whited"
215
                     android:textSize="16sp"
210
                     android:textSize="16sp"
216
                     app:layout_constraintStart_toStartOf="parent"
211
                     app:layout_constraintStart_toStartOf="parent"
217
-                    app:layout_constraintTop_toBottomOf="@id/text_song_artist_next" />
212
+                    app:layout_constraintTop_toBottomOf="@id/text_song_artist_next"
213
+                    android:visibility="visible" />
218
 
214
 
219
             </androidx.constraintlayout.widget.ConstraintLayout>
215
             </androidx.constraintlayout.widget.ConstraintLayout>
220
         </ScrollView>
216
         </ScrollView>
221
 
217
 
218
+        <ScrollView
219
+            android:id="@+id/scrollProgramme"
220
+            android:layout_width="0dp"
221
+            android:layout_height="wrap_content"
222
+            android:fillViewport="true"
223
+            android:layout_marginBottom="0dp"
224
+            app:layout_constraintEnd_toEndOf="parent"
225
+            app:layout_constraintStart_toStartOf="parent"
226
+            app:layout_constraintTop_toBottomOf="@id/scrollViewMetadataNext"
227
+            app:layout_constraintBottom_toBottomOf="@id/topInfoGuideline"
228
+            >
222
 
229
 
223
 
230
 
231
+        <androidx.constraintlayout.widget.ConstraintLayout
232
+            android:layout_width="match_parent"
233
+            android:layout_height="wrap_content"
234
+            android:orientation="vertical"
235
+            android:layout_marginEnd="8dp"
236
+            android:layout_marginRight="8dp"
237
+            android:visibility="visible"
238
+            >
239
+
240
+            <TextView
241
+                android:id="@+id/current_programme"
242
+                android:layout_width="match_parent"
243
+                android:layout_height="wrap_content"
244
+                android:gravity="top|center_horizontal"
245
+                android:text="@string/current_programme"
246
+                android:textAlignment="center"
247
+                android:textColor="@color/whited3"
248
+                android:textSize="16sp"
249
+                app:layout_constraintStart_toStartOf="parent"
250
+                app:layout_constraintEnd_toEndOf="parent"
251
+                app:layout_constraintTop_toTopOf="parent"
252
+                android:visibility="visible" />
253
+
254
+            <TextView
255
+                android:id="@+id/text_current_programme"
256
+                android:layout_width="0dp"
257
+                android:layout_height="wrap_content"
258
+                android:text=""
259
+                android:layout_marginStart="8sp"
260
+                android:layout_marginLeft="8sp"
261
+                android:gravity="start|center_horizontal"
262
+                android:textAlignment="textStart"
263
+                android:textColor="@color/whited"
264
+                android:textSize="16sp"
265
+                app:layout_constraintStart_toEndOf="@id/current_programme"
266
+                app:layout_constraintEnd_toEndOf="parent"
267
+                app:layout_constraintTop_toTopOf="parent"
268
+                android:visibility="gone"
269
+                />
270
+
271
+        </androidx.constraintlayout.widget.ConstraintLayout>
272
+        </ScrollView>
273
+
274
+        <androidx.constraintlayout.widget.Guideline
275
+            android:id="@+id/topInfoGuideline"
276
+            android:layout_width="wrap_content"
277
+            android:layout_height="wrap_content"
278
+            android:orientation="horizontal"
279
+            app:layout_constraintGuide_percent="0.97" />
280
+
224
     </androidx.constraintlayout.widget.ConstraintLayout>
281
     </androidx.constraintlayout.widget.ConstraintLayout>
225
 
282
 
226
     <androidx.constraintlayout.widget.ConstraintLayout
283
     <androidx.constraintlayout.widget.ConstraintLayout
305
             android:progressDrawable="@drawable/progress_bar_progress"
362
             android:progressDrawable="@drawable/progress_bar_progress"
306
             app:layout_constraintBottom_toTopOf="@id/play_pause"
363
             app:layout_constraintBottom_toTopOf="@id/play_pause"
307
             tools:layout_editor_absoluteX="0dp"
364
             tools:layout_editor_absoluteX="0dp"
308
-            android:visibility="gone"/>
365
+            android:visibility="visible"/>
309
 
366
 
310
         <!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
367
         <!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
311
         <TextView
368
         <TextView
318
             android:textAlignment="textEnd"
375
             android:textAlignment="textEnd"
319
             app:layout_constraintEnd_toEndOf="@id/progressBar"
376
             app:layout_constraintEnd_toEndOf="@id/progressBar"
320
             app:layout_constraintTop_toBottomOf="@id/progressBar"
377
             app:layout_constraintTop_toBottomOf="@id/progressBar"
321
-            android:visibility="gone"/>
378
+            android:visibility="visible"/>
322
 
379
 
323
         <!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
380
         <!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
324
         <TextView
381
         <TextView
331
             android:textAlignment="textStart"
388
             android:textAlignment="textStart"
332
             app:layout_constraintStart_toStartOf="@id/progressBar"
389
             app:layout_constraintStart_toStartOf="@id/progressBar"
333
             app:layout_constraintTop_toBottomOf="@id/progressBar"
390
             app:layout_constraintTop_toBottomOf="@id/progressBar"
334
-            android:visibility="gone"/>
391
+            android:visibility="visible"/>
335
 
392
 
336
 
393
 
337
 
394
 
346
             android:layout_width="wrap_content"
403
             android:layout_width="wrap_content"
347
             android:layout_height="wrap_content"
404
             android:layout_height="wrap_content"
348
             android:orientation="horizontal"
405
             android:orientation="horizontal"
349
-            app:layout_constraintGuide_percent="0.58" />
406
+            app:layout_constraintGuide_percent="0.63" />
350
 
407
 
351
 
408
 
352
         <ImageButton
409
         <ImageButton
381
         android:layout_width="wrap_content"
438
         android:layout_width="wrap_content"
382
         android:layout_height="wrap_content"
439
         android:layout_height="wrap_content"
383
         android:orientation="horizontal"
440
         android:orientation="horizontal"
384
-        app:layout_constraintGuide_percent="0.33" />
441
+        app:layout_constraintGuide_percent="0.38" />
385
 
442
 
386
     <androidx.constraintlayout.widget.Guideline
443
     <androidx.constraintlayout.widget.Guideline
387
     android:id="@+id/splitHorizontalLayout"
444
     android:id="@+id/splitHorizontalLayout"

+ 6 - 7
app/src/main/res/menu/toolbar_menu.xml ファイルの表示

15
         app:showAsAction="ifRoom"/>
15
         app:showAsAction="ifRoom"/>
16
 
16
 
17
     <!--
17
     <!--
18
-    <item
19
-        android:id="@+id/action_refresh"
20
-        android:title="@string/action_refresh"
21
-        app:showAsAction="never"/>
22
-    -->
23
 
18
 
24
-    <!--
25
     <item
19
     <item
26
         android:id="@+id/action_bug_submit"
20
         android:id="@+id/action_bug_submit"
27
         android:title="@string/action_bug_submit"
21
         android:title="@string/action_bug_submit"
28
         app:showAsAction="never"/>
22
         app:showAsAction="never"/>
29
     -->
23
     -->
30
 
24
 
25
+    <item
26
+        android:id="@+id/action_refresh"
27
+        android:title="@string/action_refresh"
28
+        app:showAsAction="never"/>
29
+
31
     <item android:id="@+id/action_settings"
30
     <item android:id="@+id/action_settings"
32
         android:title="@string/action_settings"
31
         android:title="@string/action_settings"
33
         android:icon="@drawable/ic_settings"
32
         android:icon="@drawable/ic_settings"
34
-        app:showAsAction="ifRoom"/>
33
+        app:showAsAction="never"/>
35
 
34
 
36
 
35
 
37
 </menu>
36
 </menu>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png ファイルの表示


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png ファイルの表示


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png ファイルの表示


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png ファイルの表示


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png ファイルの表示


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png ファイルの表示


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png ファイルの表示


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png ファイルの表示


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png ファイルの表示


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png ファイルの表示


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png ファイルの表示


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png ファイルの表示


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png ファイルの表示


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png ファイルの表示


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png ファイルの表示


+ 31 - 0
app/src/main/res/values-fr/arrays.xml ファイルの表示

1
+<?xml version="1.0" encoding="utf-8"?>
2
+<resources>
3
+
4
+    <string-array name="snoozeValues">
5
+        <item name="0">@string/disable</item>
6
+        <item name="1">1</item>
7
+        <item name="2">2</item>
8
+        <item name="5">5</item>
9
+        <item name="10">10</item>
10
+        <item name="15">15</item>
11
+        <item name="20">20</item>
12
+        <item name="25">25</item>
13
+        <item name="30">30</item>
14
+    </string-array>
15
+
16
+    <string-array name="fetchPeriodString">
17
+        <item name="5">Toutes les 5 secondes (mise à jour rapide)</item>
18
+        <item name="10">Toutes les 10 secondes</item>
19
+        <item name="15">Toutes les 15 secondes</item>
20
+        <item name="20">Toutes les 20 secondes</item>
21
+        <item name="30">Toutes les 30 secondes (moins d\'utilisation de batterie)</item>
22
+    </string-array>
23
+    <string-array name="fetchPeriodValues">
24
+        <item name="5">5</item>
25
+        <item name="10">10</item>
26
+        <item name="15">15</item>
27
+        <item name="20">20</item>
28
+        <item name="30">30</item>
29
+    </string-array>
30
+
31
+</resources>

+ 5 - 1
app/src/main/res/values-fr/strings.xml ファイルの表示

10
 
10
 
11
     <string name="volume">Volume : </string>
11
     <string name="volume">Volume : </string>
12
 
12
 
13
-    <string name="up_next">Émission en cours :</string>
13
+    <string name="up_next">À suivre :</string>
14
+    <string name="current_programme">Émission : </string>
14
     <string name="now_streaming">En cours</string>
15
     <string name="now_streaming">En cours</string>
15
     <string name="error_webView">Erreur du chargement de WebView. Téléchargez Google Chrome sur le Play Store, ou activez le si vous l\'avez désactivé.</string>
16
     <string name="error_webView">Erreur du chargement de WebView. Téléchargez Google Chrome sur le Play Store, ou activez le si vous l\'avez désactivé.</string>
16
     <string name="action_settings">Paramètres</string>
17
     <string name="action_settings">Paramètres</string>
50
     <string name="sleepClosesApp">Minuterie avant fermeture de l\'application</string>
51
     <string name="sleepClosesApp">Minuterie avant fermeture de l\'application</string>
51
     <string name="setSleepDuration">Choisir une durée (en minutes)</string>
52
     <string name="setSleepDuration">Choisir une durée (en minutes)</string>
52
     <string name="willCloseIn">Extinction dans %1$d minutes</string>
53
     <string name="willCloseIn">Extinction dans %1$d minutes</string>
54
+    <string name="fetchPeriod">Choisir la fréquence de mise à jour quand la radio est stoppée</string>
55
+    <string name="refreshing">Actualisation…</string>
56
+    <string name="action_refresh">Raffraîchir les données</string>
53
 
57
 
54
 </resources>
58
 </resources>

+ 15 - 0
app/src/main/res/values/arrays.xml ファイルの表示

13
         <item name="30">30</item>
13
         <item name="30">30</item>
14
     </string-array>
14
     </string-array>
15
 
15
 
16
+    <string-array name="fetchPeriodString">
17
+        <item name="5">Every 5 seconds (faster update)</item>
18
+        <item name="10">Every 10 seconds</item>
19
+        <item name="15">Every 15 seconds</item>
20
+        <item name="20">Every 20 seconds</item>
21
+        <item name="30">Every 30 seconds (less battery-intensive)</item>
22
+    </string-array>
23
+    <string-array name="fetchPeriodValues">
24
+        <item name="5">5</item>
25
+        <item name="10">10</item>
26
+        <item name="15">15</item>
27
+        <item name="20">20</item>
28
+        <item name="30">30</item>
29
+    </string-array>
30
+
16
 </resources>
31
 </resources>

+ 6 - 1
app/src/main/res/values/strings.xml ファイルの表示

7
     <string name="github_url_new_issue">https://github.com/yattoz/Tsumugi-app/issues/</string>
7
     <string name="github_url_new_issue">https://github.com/yattoz/Tsumugi-app/issues/</string>
8
     <string name="website_url">https://tsumugi.forum-thalie.fr/</string>
8
     <string name="website_url">https://tsumugi.forum-thalie.fr/</string>
9
     <string name="rss_url">https://tsumugi.forum-thalie.fr/?feed=rss2</string>
9
     <string name="rss_url">https://tsumugi.forum-thalie.fr/?feed=rss2</string>
10
+    <string name="API_URL">https://radio.mahoro-net.org/airtime/api/live-info-v2</string>
10
     <string name="planning_url">ADD SOME URL HERE</string>
11
     <string name="planning_url">ADD SOME URL HERE</string>
11
 
12
 
12
 
13
 
24
 
25
 
25
     <string name="volume">Volume: </string>
26
     <string name="volume">Volume: </string>
26
 
27
 
27
-    <string name="up_next">Émission en cours :</string>
28
+    <string name="up_next">Up next :</string>
29
+    <string name="current_programme">Current programme : </string>
28
     <string name="now_streaming">Now streaming</string>
30
     <string name="now_streaming">Now streaming</string>
29
     <string name="error_webView">Error loading WebView. Try downloading Google Chrome on Google Play, or enabling it if you disabled it.</string>
31
     <string name="error_webView">Error loading WebView. Try downloading Google Chrome on Google Play, or enabling it if you disabled it.</string>
30
     <string name="action_settings">Settings</string>
32
     <string name="action_settings">Settings</string>
69
     <string name="sleepClosesApp">Sleep - close app after some time</string>
71
     <string name="sleepClosesApp">Sleep - close app after some time</string>
70
     <string name="setSleepDuration">Set duration (minutes)</string>
72
     <string name="setSleepDuration">Set duration (minutes)</string>
71
     <string name="willCloseIn">Will close in %1$d minutes</string>
73
     <string name="willCloseIn">Will close in %1$d minutes</string>
74
+    <string name="fetchPeriod">Set update period when stopped</string>
75
+    <string name="refreshing">Refreshing data…</string>
76
+    <string name="action_refresh">Refresh data</string>
72
 
77
 
73
 </resources>
78
 </resources>

+ 10 - 0
app/src/main/res/xml/customize_preferences.xml ファイルの表示

29
         app:defaultValue="false"
29
         app:defaultValue="false"
30
         />
30
         />
31
 
31
 
32
+    <ListPreference
33
+        app:key="fetchPeriod"
34
+        app:iconSpaceReserved="false"
35
+        android:title="@string/fetchPeriod"
36
+        app:singleLineTitle="false"
37
+        android:entries="@array/fetchPeriodString"
38
+        android:entryValues="@array/fetchPeriodValues"
39
+        android:defaultValue="10"
40
+        />
41
+
32
 </PreferenceScreen>
42
 </PreferenceScreen>