removed many R/a/dio specifc code. Hid some UI that could be used later.

remotes/origin/devel
yattoz 2019-12-07 22:10:52 +01:00
parent d1fb81f615
commit 6b1c018318
81 changed files with 163 additions and 1800 deletions

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<!-- seems like the font cannot be changed in KiwiIRC without writing a full theme, so loading the CSS below is useless-->
<!--
<link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /> -->
</head>
<body>
<div style="height: auto">
<iframe src="https://kiwiirc.com/nextclient/?theme=dark#irc://irc.rizon.net:+6697/#r/a/dio"
style="border:0; width:100%; height:100%; position: absolute; top: 0px; left: 0px">
</iframe>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -71,7 +71,7 @@ abstract class BaseNotification(private val notificationChannelId: String,
builder.setSmallIcon(R.drawable.lollipop_logo) builder.setSmallIcon(R.drawable.lollipop_logo)
builder.color = -0xf58b01 // same color as Accent. Can't use c.getColor since it's API23+ builder.color = -0xf58b01 // same color as Accent. Can't use c.getColor since it's API23+
} else { } else {
builder.setSmallIcon(R.drawable.normal_logo) builder.setSmallIcon(R.drawable.logo_roundsquare)
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

View File

@ -8,8 +8,6 @@ import android.util.Log
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.alarm.RadioAlarm import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.playerstore.PlayerStore import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.streamerNotificationService.WorkerStore
import fr.forum_thalie.tsumugi.streamerNotificationService.startStreamerMonitor
class BootBroadcastReceiver : BroadcastReceiver(){ class BootBroadcastReceiver : BroadcastReceiver(){
@ -19,8 +17,6 @@ class BootBroadcastReceiver : BroadcastReceiver(){
preferenceStore = PreferenceManager.getDefaultSharedPreferences(context) preferenceStore = PreferenceManager.getDefaultSharedPreferences(context)
if (arg1.action == Intent.ACTION_BOOT_COMPLETED) { if (arg1.action == Intent.ACTION_BOOT_COMPLETED) {
WorkerStore.instance.init(context)
startStreamerMonitor(context) // will actually start it only if enabled in settings
RadioAlarm.instance.setNextAlarm(context) // schedule next alarm RadioAlarm.instance.setNextAlarm(context) // schedule next alarm
} }
@ -29,8 +25,6 @@ class BootBroadcastReceiver : BroadcastReceiver(){
RadioAlarm.instance.setNextAlarm(context) // schedule next alarm RadioAlarm.instance.setNextAlarm(context) // schedule next alarm
if (PlayerStore.instance.streamerName.value.isNullOrBlank()) if (PlayerStore.instance.streamerName.value.isNullOrBlank())
PlayerStore.instance.initPicture(context) PlayerStore.instance.initPicture(context)
if (!PlayerStore.instance.isInitialized)
PlayerStore.instance.initApi()
val i = Intent(context, RadioService::class.java) val i = Intent(context, RadioService::class.java)
i.putExtra("action", Actions.PLAY_OR_FALLBACK.name) i.putExtra("action", Actions.PLAY_OR_FALLBACK.name)

View File

@ -15,12 +15,8 @@ import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import java.util.Timer import java.util.Timer
import android.view.MenuItem import android.view.MenuItem
import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import fr.forum_thalie.tsumugi.alarm.RadioAlarm import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.streamerNotificationService.WorkerStore
import fr.forum_thalie.tsumugi.streamerNotificationService.startStreamerMonitor
import fr.forum_thalie.tsumugi.ui.songs.request.Requestor
/* Log to file import /* Log to file import
@ -39,11 +35,11 @@ class MainActivity : BaseActivity() {
* Called on first creation and when restoring state. * Called on first creation and when restoring state.
*/ */
private fun setupBottomNavigationBar() { private fun setupBottomNavigationBar() {
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav) val bottomNavigationView : BottomNavigationView = findViewById(R.id.bottom_nav)
//val navGraphIds = listOf(R.navigation.home, R.navigation.list, R.navigation.form) //val navGraphIds = listOf(R.navigation.home, R.navigation.list, R.navigation.form)
val navGraphIds = listOf(R.navigation.navigation_nowplaying, R.navigation.navigation_songs, val navGraphIds = listOf(R.navigation.navigation_nowplaying, R.navigation.navigation_songs,
R.navigation.navigation_news, R.navigation.navigation_chat) R.navigation.navigation_news)
// Setup the bottom navigation view with a list of navigation graphs // Setup the bottom navigation view with a list of navigation graphs
val controller = bottomNavigationView.setupWithNavController( val controller = bottomNavigationView.setupWithNavController(
@ -77,15 +73,15 @@ class MainActivity : BaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle item selection // Handle item selection
return when (item.itemId) { return when (item.itemId) {
/*
R.id.action_refresh -> { R.id.action_refresh -> {
PlayerStore.instance.queue.clear() PlayerStore.instance.queue.clear()
PlayerStore.instance.lp.clear() PlayerStore.instance.lp.clear()
PlayerStore.instance.initApi()
Requestor.instance.initFavorites()
val s = Snackbar.make(findViewById(R.id.nav_host_container), "Refreshing data..." as CharSequence, Snackbar.LENGTH_LONG) val s = Snackbar.make(findViewById(R.id.nav_host_container), "Refreshing data..." as CharSequence, Snackbar.LENGTH_LONG)
s.show() s.show()
true true
} }
*/
R.id.action_settings -> { R.id.action_settings -> {
val i = Intent(this, ParametersActivity::class.java) val i = Intent(this, ParametersActivity::class.java)
startActivity(i) startActivity(i)
@ -118,10 +114,6 @@ class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
WorkerStore.instance.init(this)
startStreamerMonitor(this) // this checks the preferenceStore before actually starting a service, don't worry.
RadioAlarm.instance.cancelAlarm(c = this) RadioAlarm.instance.cancelAlarm(c = this)
RadioAlarm.instance.setNextAlarm(c = this) // this checks the preferenceStore before actually setting an alarm, don't worry. RadioAlarm.instance.setNextAlarm(c = this) // this checks the preferenceStore before actually setting an alarm, don't worry.
@ -132,8 +124,6 @@ class MainActivity : BaseActivity() {
colorRedList = (ResourcesCompat.getColorStateList(resources, R.color.button_red, null)) colorRedList = (ResourcesCompat.getColorStateList(resources, R.color.button_red, null))
colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null)) colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
PlayerStore.instance.initApi() // the service will call the initApi on defining the streamerName Observer too, but it's better to initialize the API as soon as the user opens the activity.
// Post-UI Launch // Post-UI Launch
if (PlayerStore.instance.isInitialized) if (PlayerStore.instance.isInitialized)
{ {
@ -149,9 +139,6 @@ class MainActivity : BaseActivity() {
// initialize some API data // initialize some API data
PlayerStore.instance.initPicture(this) PlayerStore.instance.initPicture(this)
PlayerStore.instance.streamerName.value = "" // initializing the streamer name will trigger an initApi from the observer in the Service. PlayerStore.instance.streamerName.value = "" // initializing the streamer name will trigger an initApi from the observer in the Service.
// initialize the favorites
Requestor.instance.initFavorites()
} }
if (!isTimerStarted) if (!isTimerStarted)

View File

@ -29,7 +29,6 @@ class ParametersActivity : BaseActivity() {
ActionOpenParam.ALARM.name -> AlarmFragment() ActionOpenParam.ALARM.name -> AlarmFragment()
ActionOpenParam.SLEEP.name -> SleepFragment() ActionOpenParam.SLEEP.name -> SleepFragment()
ActionOpenParam.CUSTOMIZE.name -> CustomizeFragment() ActionOpenParam.CUSTOMIZE.name -> CustomizeFragment()
ActionOpenParam.STREAMER_NOTIFICATION_SERVICE.name -> StreamerNotifServiceFragment()
else -> MainPreferenceFragment() else -> MainPreferenceFragment()
} }

View File

@ -107,29 +107,6 @@ class RadioService : MediaBrowserServiceCompat() {
// ################### OBSERVERS #################### // ################### OBSERVERS ####################
// ################################################## // ##################################################
private val titleObserver: Observer<String> = Observer {
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 == getString(R.string.ed) -> {
PlayerStore.instance.isStreamDown = true
}
else -> {
PlayerStore.instance.fetchApi(numberOfSongs >= 2)
}
}
}
nowPlayingNotification.update(this)
}
private val volumeObserver: Observer<Int> = Observer { private val volumeObserver: Observer<Int> = Observer {
setVolume(it) setVolume(it)
} }
@ -145,20 +122,14 @@ class RadioService : MediaBrowserServiceCompat() {
stopPlaying() stopPlaying()
} }
private val startTimeObserver = Observer<Long> { private val titleObserver = Observer<String> {
// We're listening to startTime to determine if we have to update Queue and Lp. // We're checking if a new song arrives. If so, we put the currentSong in Lp and update the backup.
// this is because startTime is set by the API and never by the ICY, so both cases are covered (playing and stopped) if (PlayerStore.instance.currentSong != PlayerStore.instance.currentSongBackup
// should be OK even when a new streamer comes in. && it != noConnectionValue)
if (it != PlayerStore.instance.currentSongBackup.startTime.value) // we have a new song
{ {
PlayerStore.instance.updateLp() PlayerStore.instance.updateLp()
PlayerStore.instance.updateQueue()
} }
} nowPlayingNotification.update(this)
private val streamerObserver = Observer<String> {
PlayerStore.instance.initApi()
nowPlayingNotification.update(this) // should update the streamer icon
} }
private val streamerPictureObserver = Observer<Bitmap> { private val streamerPictureObserver = Observer<Bitmap> {
@ -224,9 +195,7 @@ class RadioService : MediaBrowserServiceCompat() {
nowPlayingNotification.create(this, mediaSession) nowPlayingNotification.create(this, mediaSession)
PlayerStore.instance.streamerName.observeForever(streamerObserver)
PlayerStore.instance.currentSong.title.observeForever(titleObserver) PlayerStore.instance.currentSong.title.observeForever(titleObserver)
PlayerStore.instance.currentSong.startTime.observeForever(startTimeObserver)
PlayerStore.instance.volume.observeForever(volumeObserver) PlayerStore.instance.volume.observeForever(volumeObserver)
PlayerStore.instance.isPlaying.observeForever(isPlayingObserver) PlayerStore.instance.isPlaying.observeForever(isPlayingObserver)
PlayerStore.instance.isMuted.observeForever(isMutedObserver) PlayerStore.instance.isMuted.observeForever(isMutedObserver)
@ -234,12 +203,6 @@ class RadioService : MediaBrowserServiceCompat() {
startForeground(radioServiceId, nowPlayingNotification.notification) startForeground(radioServiceId, nowPlayingNotification.notification)
// 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)
PlayerStore.instance.isServiceStarted.value = true PlayerStore.instance.isServiceStarted.value = true
Log.d(tag, radioTag + "created") Log.d(tag, radioTag + "created")
} }
@ -303,7 +266,6 @@ class RadioService : MediaBrowserServiceCompat() {
player.release() player.release()
unregisterReceiver(receiver) unregisterReceiver(receiver)
PlayerStore.instance.currentSong.title.removeObserver(titleObserver) PlayerStore.instance.currentSong.title.removeObserver(titleObserver)
PlayerStore.instance.currentSong.startTime.removeObserver(startTimeObserver)
PlayerStore.instance.volume.removeObserver(volumeObserver) PlayerStore.instance.volume.removeObserver(volumeObserver)
PlayerStore.instance.isPlaying.removeObserver(isPlayingObserver) PlayerStore.instance.isPlaying.removeObserver(isPlayingObserver)
PlayerStore.instance.isMuted.removeObserver(isMutedObserver) PlayerStore.instance.isMuted.removeObserver(isMutedObserver)

View File

@ -11,11 +11,3 @@ class Tick : TimerTask() {
} }
} }
class ApiFetchTick : TimerTask() {
override fun run() {
if (PlayerStore.instance.playbackState.value == PlaybackStateCompat.STATE_STOPPED)
{
PlayerStore.instance.fetchApi()
}
}
}

View File

@ -31,10 +31,8 @@ class PlayerStore {
val isLpUpdated: MutableLiveData<Boolean> = MutableLiveData() val isLpUpdated: MutableLiveData<Boolean> = MutableLiveData()
val isMuted : MutableLiveData<Boolean> = MutableLiveData() val isMuted : MutableLiveData<Boolean> = MutableLiveData()
val listenersCount: MutableLiveData<Int> = MutableLiveData() val listenersCount: MutableLiveData<Int> = MutableLiveData()
private val urlToScrape = "https://r-a-d.io/api"
var latencyCompensator : Long = 0 var latencyCompensator : Long = 0
var isInitialized: Boolean = false var isInitialized: Boolean = false
var isStreamDown: Boolean = false
init { init {
playbackState.value = PlaybackStateCompat.STATE_STOPPED playbackState.value = PlaybackStateCompat.STATE_STOPPED
@ -47,6 +45,7 @@ class PlayerStore {
isLpUpdated.value = false isLpUpdated.value = false
isMuted.value = false isMuted.value = false
currentSong.title.value = noConnectionValue currentSong.title.value = noConnectionValue
currentSongBackup.title.value = noConnectionValue
listenersCount.value = 0 listenersCount.value = 0
} }
@ -54,6 +53,7 @@ class PlayerStore {
// ################# API FUNCTIONS ################## // ################# API FUNCTIONS ##################
// ################################################## // ##################################################
/*
private fun updateApi(resMain: JSONObject, isCompensatingLatency : Boolean = false) { private fun updateApi(resMain: 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. // If we're not in PLAYING state, update title / artist metadata. If we're playing, the ICY will take care of that.
if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty() if (playbackState.value != PlaybackStateCompat.STATE_PLAYING || currentSong.title.value.isNullOrEmpty()
@ -154,24 +154,26 @@ class PlayerStore {
} }
Async(scrape, post) Async(scrape, post)
} }
*/
// ################################################## // ##################################################
// ############## QUEUE / LP FUNCTIONS ############## // ############## QUEUE / LP FUNCTIONS ##############
// ################################################## // ##################################################
fun updateLp() { fun updateLp() {
// note : lp must never be empty. There should always be some songs "last played". // note : lp is empty at initialization. This check was needed when we used the R/a/dio API.
// if not, then the function has been called before initialization. No need to do anything. //if (lp.isNotEmpty()){
if (lp.isNotEmpty()){
val n = Song() val n = Song()
n.copy(currentSongBackup) n.copy(currentSongBackup)
lp.add(0, n) if (n.title.value != noConnectionValue)
lp.add(0, n)
currentSongBackup.copy(currentSong) currentSongBackup.copy(currentSong)
isLpUpdated.value = true isLpUpdated.value = true
Log.d(tag, playerStoreTag + lp.toString()) Log.d(tag, playerStoreTag + lp.toString())
} //}
} }
/*
fun updateQueue() { fun updateQueue() {
if (queue.isNotEmpty()) { if (queue.isNotEmpty()) {
queue.remove(queue.first()) queue.remove(queue.first())
@ -257,11 +259,13 @@ class PlayerStore {
song.type.value = songJSON.getInt("type") song.type.value = songJSON.getInt("type")
return song return song
} }
*/
// ################################################## // ##################################################
// ############## PICTURE FUNCTIONS ################# // ############## PICTURE FUNCTIONS #################
// ################################################## // ##################################################
/*
private fun fetchPicture(fileUrl: String) private fun fetchPicture(fileUrl: String)
{ {
val scrape: (Any?) -> Bitmap? = { val scrape: (Any?) -> Bitmap? = {
@ -287,10 +291,11 @@ class PlayerStore {
} }
Async(scrape, post) Async(scrape, post)
} }
*/
fun initPicture(c: Context) { fun initPicture(c: Context) {
streamerPicture.value = BitmapFactory.decodeResource(c.resources, streamerPicture.value = BitmapFactory.decodeResource(c.resources,
R.drawable.actionbar_logo R.drawable.logo_roundsquare
) )
} }

View File

@ -8,19 +8,11 @@ import androidx.appcompat.app.AlertDialog
import androidx.preference.* import androidx.preference.*
import fr.forum_thalie.tsumugi.R import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.preferenceStore import fr.forum_thalie.tsumugi.preferenceStore
import fr.forum_thalie.tsumugi.ui.songs.request.Requestor
class CustomizeFragment : PreferenceFragmentCompat() { class CustomizeFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.customize_preferences, rootKey) setPreferencesFromResource(R.xml.customize_preferences, rootKey)
val userNamePref = preferenceScreen.findPreference<EditTextPreference>("userName")
userNamePref?.summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance()
userNamePref?.setOnPreferenceChangeListener { _, newValue ->
val name = newValue as String
Requestor.instance.initFavorites(name) // need to be as parameter cause the callback is called BEFORE PARAMETER SET
true
}
val snackbarPersistent = preferenceScreen.findPreference<SwitchPreferenceCompat>("snackbarPersistent") val snackbarPersistent = preferenceScreen.findPreference<SwitchPreferenceCompat>("snackbarPersistent")
snackbarPersistent!!.summary = if (preferenceStore.getBoolean("snackbarPersistent", true)) snackbarPersistent!!.summary = if (preferenceStore.getBoolean("snackbarPersistent", true))
@ -48,33 +40,5 @@ class CustomizeFragment : PreferenceFragmentCompat() {
true true
} }
val helpFavorites = preferenceScreen.findPreference<Preference>("helpFavorites")
helpFavorites?.setOnPreferenceClickListener { _ ->
val url = getString(R.string.github_url_wiki_irc_for_favorites)
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
startActivity(i)
true
}
val fetchPeriod = preferenceScreen.findPreference<ListPreference>("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.fetch_disabled_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
}
} }
} }

View File

@ -1,63 +0,0 @@
package fr.forum_thalie.tsumugi.preferences
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.preference.*
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.preferenceStore
import fr.forum_thalie.tsumugi.streamerNotificationService.WorkerStore
import fr.forum_thalie.tsumugi.streamerNotificationService.startStreamerMonitor
import fr.forum_thalie.tsumugi.streamerNotificationService.stopStreamerMonitor
class StreamerNotifServiceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.streamer_notif_service_preferences, rootKey)
val streamerPeriod = preferenceScreen.findPreference<Preference>("streamerMonitorPeriodPref")
val streamerNotification = preferenceScreen.findPreference<Preference>("newStreamerNotification")
streamerNotification?.setOnPreferenceChangeListener { _, newValue ->
if ((newValue as Boolean)) {
val builder1 = AlertDialog.Builder(context!!)
builder1.setMessage(R.string.warningStreamerNotif)
builder1.setCancelable(false)
builder1.setPositiveButton(
"Yes"
) { dialog, _ ->
startStreamerMonitor(context!!, force = true) // force enabled because the preference value is not yet set when running this callback.
streamerPeriod?.isEnabled = true
dialog.cancel()
}
builder1.setNegativeButton(
"No"
) { dialog, _ ->
stopStreamerMonitor(context!!)
(streamerNotification as SwitchPreferenceCompat).isChecked = false
dialog.cancel()
}
val alert11 = builder1.create()
alert11.show()
}
else {
stopStreamerMonitor(context!!)
streamerPeriod?.isEnabled = false
WorkerStore.instance.isServiceStarted = false
}
true
}
streamerPeriod?.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
streamerPeriod?.isEnabled = preferenceStore.getBoolean("newStreamerNotification", true)
streamerPeriod?.setOnPreferenceChangeListener { _, newValue ->
WorkerStore.instance.tickerPeriod = (Integer.parseInt(newValue as String)).toLong() * 60
// this should be sufficient, the next alarm schedule should take the new tickerPeriod.
true
}
}
}

View File

@ -1,41 +0,0 @@
package fr.forum_thalie.tsumugi.streamerNotificationService
import android.content.Context
import fr.forum_thalie.tsumugi.BaseNotification
import java.util.*
class ServiceNotification(
notificationChannelId: String,
notificationChannel : Int,
notificationId: Int,
notificationImportance: Int
) : BaseNotification
(
notificationChannelId,
notificationChannel,
notificationId,
notificationImportance
){
override fun create(c: Context)
{
super.create(c)
update()
}
fun update()
{
val date = Date() // given date
val calendar = Calendar.getInstance() // creates a new calendar instance
calendar.time = date // assigns calendar to given date
val hours = calendar.get(Calendar.HOUR_OF_DAY) // gets hour in 24h format
//val hours_american = calendar.get(Calendar.HOUR) // gets hour in 12h format
val minutes = calendar.get(Calendar.MINUTE)
val seconds = calendar.get(Calendar.SECOND)
builder.setContentTitle("Never miss a stream! Current: ${WorkerStore.instance.streamerName.value}")
builder.setContentText("Last update: ${hours}:${minutes}:${seconds}")
notification = builder.build()
}
}

View File

@ -1,114 +0,0 @@
package fr.forum_thalie.tsumugi.streamerNotificationService
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.SystemClock
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.*
import fr.forum_thalie.tsumugi.alarm.RadioAlarm
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import org.json.JSONObject
import java.net.URL
fun startNextAlarmStreamer(c: Context){
// the notification works with an alarm re-scheduled at fixed rate.
// if the service stopped, the alarm is not re-scheduled.
if (WorkerStore.instance.isServiceStarted)
{
val alarmIntent = Intent(c, StreamerMonitorService::class.java).let { intent ->
intent.putExtra("action", Actions.NOTIFY.name)
PendingIntent.getService(c, 0, intent, 0)
}
val alarmMgr = c.getSystemService(Context.ALARM_SERVICE) as AlarmManager
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> alarmMgr.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + WorkerStore.instance.tickerPeriod * 1000,
alarmIntent
)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> alarmMgr.setExact(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + WorkerStore.instance.tickerPeriod * 1000,
alarmIntent
)
else -> alarmMgr.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + WorkerStore.instance.tickerPeriod * 1000,
alarmIntent
)
}
} else {
Log.d(tag, "alarm called while service is dead - skipped.")
}
}
fun stopStreamerMonitor(context: Context)
{
val intent = Intent(context, StreamerMonitorService::class.java)
intent.putExtra("action", Actions.KILL.name)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startService(intent)
} else {
context.startService(intent)
}
Log.i(tag, "Service stopped")
}
fun startStreamerMonitor(context: Context, force: Boolean = false)
{
if (!force)
{
val isNotifyingForNewStreamer = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("newStreamerNotification", false)
if (!isNotifyingForNewStreamer)
return
}
val intent = Intent(context, StreamerMonitorService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
Log.i(tag, "Service started on boot")
}
fun fetchStreamer(applicationContext: Context) {
val urlToScrape = "https://r-a-d.io/api"
val scrape : (Any?) -> String =
{
URL(urlToScrape).readText()
}
val post: (parameter: Any?) -> Unit = {
val result = JSONObject(it as String)
if (!result.isNull("main"))
{
val name = result.getJSONObject("main").getJSONObject("dj").getString("djname")
WorkerStore.instance.streamerName.value = name
}
}
// notify
val t = ServiceNotification(
notificationChannelId = applicationContext.getString(R.string.streamerServiceChannelId),
notificationChannel = R.string.streamerServiceChannel,
notificationId = 2,
notificationImportance = NotificationCompat.PRIORITY_LOW
)
t.create(applicationContext)
t.show()
try{
Async(scrape, post)
Log.d(tag, "enqueue next work in ${WorkerStore.instance.tickerPeriod} seconds")
} catch (e: Exception) {
}
}

View File

@ -1,123 +0,0 @@
package fr.forum_thalie.tsumugi.streamerNotificationService
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.lifecycle.Observer
import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.Actions
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.tag
import java.util.*
class StreamerMonitorService : Service() {
override fun onBind(intent: Intent): IBinder? {
return null // no binding allowed nor needed
}
private val streamerNameObserver: Observer<String> = Observer {
val previousStreamer: String
if (PreferenceManager.getDefaultSharedPreferences(this).contains("streamerName"))
{
previousStreamer = PreferenceManager.getDefaultSharedPreferences(this).getString("streamerName", "") ?: ""
/* 3 conditions:
- the streamer changed from previously
- there is a previous non-empty streamer (at least second time running it)
- the current streamer is non-empty (this can happen at Activity start where init() is called)
*/
if (previousStreamer != it && previousStreamer != "" && it != "")
{
// notify
val newStreamer = StreamerNotification(
notificationChannelId = this.getString(R.string.streamerNotificationChannelId),
notificationChannel = R.string.streamerNotificationChannel,
notificationId = 3,
notificationImportance = NotificationCompat.PRIORITY_DEFAULT
)
newStreamer.create(this)
newStreamer.show()
}
}
with(PreferenceManager.getDefaultSharedPreferences(this).edit()){
putString("streamerName", it)
commit()
}
}
override fun onCreate() {
super.onCreate()
val streamerMonitorNotification = ServiceNotification(
notificationChannelId = this.getString(R.string.streamerServiceChannelId),
notificationChannel = R.string.streamerServiceChannel,
notificationId = 2,
notificationImportance = NotificationCompat.PRIORITY_LOW
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
streamerMonitorNotification.create(this)
streamerMonitorNotification.update()
streamerMonitorNotification.show()
startForeground(2, streamerMonitorNotification.notification)
}
WorkerStore.instance.tickerPeriod = 60 *
(if (PreferenceManager.getDefaultSharedPreferences(this).contains("streamerMonitorPeriodPref"))
Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(this).getString("streamerMonitorPeriodPref", "15")!!).toLong()
else
15)
Log.d(tag, "tickerPeriod = ${WorkerStore.instance.tickerPeriod}")
with(PreferenceManager.getDefaultSharedPreferences(this).edit()){
remove("streamerName")
commit() // I commit on main thread to be sure it's been updated before continuing.
}
WorkerStore.instance.streamerName.observeForever(streamerNameObserver)
WorkerStore.instance.isServiceStarted = true
startNextAlarmStreamer(this)
Log.d(tag, "streamerMonitor created")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val isNotifyingForNewStreamer = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("newStreamerNotification", false)
// it's probably redundant but it shouldn't hurt
if (!isNotifyingForNewStreamer || !WorkerStore.instance.isServiceStarted)
{
stopForeground(true)
stopSelf()
return START_NOT_STICKY
}
when (intent?.getStringExtra("action")) {
Actions.NOTIFY.name -> {
val date = Date() // given date
val calendar = Calendar.getInstance() // creates a new calendar instance
calendar.time = date // assigns calendar to given date
val hours = calendar.get(Calendar.HOUR_OF_DAY) // gets hour in 24h format
//val hours_american = calendar.get(Calendar.HOUR) // gets hour in 12h format
val minutes = calendar.get(Calendar.MINUTE) // gets month number, NOTE this is zero based!
Log.d(tag, "Fetched streamer name at ${hours}:${if (minutes < 10) "0" else ""}${minutes}")
fetchStreamer(this)
startNextAlarmStreamer(this) // schedule next alarm
return START_STICKY
}
Actions.KILL.name -> {
stopForeground(true)
stopSelf()
return START_NOT_STICKY
}
}
return START_STICKY
//super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
WorkerStore.instance.streamerName.removeObserver(streamerNameObserver)
WorkerStore.instance.isServiceStarted = false
super.onDestroy()
}
}

View File

@ -1,35 +0,0 @@
package fr.forum_thalie.tsumugi.streamerNotificationService
import android.content.Context
import fr.forum_thalie.tsumugi.BaseNotification
import java.util.*
class StreamerNotification(
notificationChannelId: String,
notificationChannel : Int,
notificationId: Int,
notificationImportance: Int
) : BaseNotification
(
notificationChannelId,
notificationChannel,
notificationId,
notificationImportance
){
override fun create(c: Context) {
super.create(c)
val date = Date() // given date
val calendar = Calendar.getInstance() // creates a new calendar instance
calendar.time = date // assigns calendar to given date
val hours = calendar.get(Calendar.HOUR_OF_DAY) // gets hour in 24h format
//val hours_american = calendar.get(Calendar.HOUR) // gets hour in 12h format
val minutes = calendar.get(Calendar.MINUTE) // gets month number, NOTE this is zero based!
builder.setContentTitle("${WorkerStore.instance.streamerName.value} started streaming!")
builder.setContentText("Started at ${hours}:${if (minutes < 10) "0" else ""}${minutes}")
builder.setAutoCancel(true)
super.show()
}
}

View File

@ -1,46 +0,0 @@
package fr.forum_thalie.tsumugi.streamerNotificationService
import android.content.Context
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.preference.PreferenceManager
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.tag
class WorkerStore {
companion object {
val instance = WorkerStore()
}
val streamerName = MutableLiveData<String>()
var isServiceStarted : Boolean = false
var tickerPeriod : Long = 45 // seconds
init {
tickerPeriod = 45
streamerName.value = ""
isServiceStarted = false
}
fun init(c: Context)
{
tickerPeriod = 45
streamerName.value = ""
val tickerPeriod = 60 *
(if (PreferenceManager.getDefaultSharedPreferences(c).contains("streamerMonitorPeriodPref"))
Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(c).getString("streamerMonitorPeriodPref", "15")!!).toLong()
else
15
)
instance.tickerPeriod = tickerPeriod
Log.d(tag, "tickerPeriod = $tickerPeriod")
with(PreferenceManager.getDefaultSharedPreferences(c).edit()){
remove("streamerName")
commit() // I commit on main thread to be sure it's been updated before continuing.
}
}
}

View File

@ -1,52 +0,0 @@
package fr.forum_thalie.tsumugi.ui.chat
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.webkit.WebView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import fr.forum_thalie.tsumugi.R
class ChatFragment : Fragment() {
private lateinit var chatViewModel: ChatViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
chatViewModel =
ViewModelProviders.of(this).get(ChatViewModel::class.java)
if (!chatViewModel.isChatLoaded)
{
try {
chatViewModel.root = inflater.inflate(R.layout.fragment_chat, container, false)
chatViewModel.webView = chatViewModel.root.findViewById<WebView>(R.id.chat_webview)
chatViewModel.webViewChat = WebViewChat(chatViewModel.webView as WebView)
chatViewModel.webViewChat!!.start()
} catch (e: Exception) {
chatViewModel.root = inflater.inflate(R.layout.fragment_error_chat, container, false)
}
chatViewModel.isChatLoaded = true
Log.d(tag, "webview created")
} else {
Log.d(tag, "webview already created!?")
}
return chatViewModel.root
}
}

View File

@ -1,12 +0,0 @@
package fr.forum_thalie.tsumugi.ui.chat
import android.view.View
import android.webkit.WebView
import androidx.lifecycle.ViewModel
class ChatViewModel : ViewModel() {
lateinit var root: View
var webView: WebView? = null
var isChatLoaded = false
var webViewChat: WebViewChat? = null
}

View File

@ -1,47 +0,0 @@
package fr.forum_thalie.tsumugi.ui.chat
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.webkit.WebChromeClient
import android.webkit.WebView
class WebViewChat(private val webView: WebView) {
@SuppressLint("SetJavaScriptEnabled")
fun start() {
val webSetting = this.webView.settings
webSetting.javaScriptEnabled = true
webSetting.setSupportZoom(false)
/* TODO: in the future, it could be nice to have a parameters screen where you can:
- Set the text zoom
- Set your username (to not type it every time, would it be possible?)
- Hide the chat?
- do more? */
webSetting.textZoom = 90
webSetting.setSupportMultipleWindows(true)
// needs to open target="_blank" links as KiwiIRC links have this attribute.
// shamelessly ripped off https://stackoverflow.com/questions/18187714/android-open-target-blank-links-in-webview-with-external-browser
this.webView.webChromeClient = object : WebChromeClient() {
override fun onCreateWindow(
view: WebView,
dialog: Boolean,
userGesture: Boolean,
resultMsg: android.os.Message
): Boolean {
val result = view.hitTestResult
val data = result.extra
val context = view.context
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(data))
context.startActivity(browserIntent)
return false
}
}
webView.loadUrl("file:///android_asset/chat.html")
}
}

View File

@ -5,62 +5,19 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import fr.forum_thalie.tsumugi.R import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.preferenceStore
import fr.forum_thalie.tsumugi.ui.songs.queuelp.LastPlayedFragment import fr.forum_thalie.tsumugi.ui.songs.queuelp.LastPlayedFragment
import fr.forum_thalie.tsumugi.ui.songs.queuelp.QueueFragment
import fr.forum_thalie.tsumugi.ui.songs.request.FavoritesFragment
import fr.forum_thalie.tsumugi.ui.songs.request.RequestFragment
import fr.forum_thalie.tsumugi.ui.songs.request.Requestor
class SongsFragment : Fragment() { class SongsFragment : Fragment() {
private lateinit var adapter : SongsPagerAdapter private lateinit var adapter : SongsPagerAdapter
private lateinit var snackBar : Snackbar
private lateinit var root: View private lateinit var root: View
private lateinit var viewPager: ViewPager private lateinit var viewPager: ViewPager
private val snackBarTextObserver: Observer<String?> = Observer {
if (Requestor.instance.snackBarText.value != "")
{
val snackBarLength = if (preferenceStore.getBoolean("snackbarPersistent", true))
Snackbar.LENGTH_INDEFINITE
else Snackbar.LENGTH_LONG
snackBar = Snackbar.make(viewPager, "", snackBarLength)
if (snackBarLength == Snackbar.LENGTH_INDEFINITE)
snackBar.setAction("OK") {
snackBar.dismiss()
}
snackBar.behavior = BaseTransientBottomBar.Behavior().apply {
setSwipeDirection(BaseTransientBottomBar.Behavior.SWIPE_DIRECTION_ANY)
}
val snackBarView = snackBar.view
val textView =
snackBarView.findViewById(com.google.android.material.R.id.snackbar_text) as TextView
if (Requestor.instance.addRequestMeta != "")
textView.maxLines = 4
else
textView.maxLines = 2
snackBar.setText((it as CharSequence))
snackBar.show()
Requestor.instance.snackBarText.value = "" // resetting afterwards to avoid re-triggering it when we enter again the fragment
Requestor.instance.addRequestMeta = ""
}
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -71,9 +28,8 @@ class SongsFragment : Fragment() {
viewPager = root.findViewById(R.id.tabPager) viewPager = root.findViewById(R.id.tabPager)
adapter = SongsPagerAdapter(childFragmentManager, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) adapter = SongsPagerAdapter(childFragmentManager, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
adapter.addFragment(LastPlayedFragment.newInstance(), "last played") adapter.addFragment(LastPlayedFragment.newInstance(), "last played")
adapter.addFragment(QueueFragment.newInstance(), "queue")
adapter.addFragment(RequestFragment.newInstance(), "request") //adapter.addFragment(QueueFragment.newInstance(), "queue")
adapter.addFragment(FavoritesFragment.newInstance(), "Favorites")
viewPager.adapter = adapter viewPager.adapter = adapter
@ -81,14 +37,8 @@ class SongsFragment : Fragment() {
tabLayout.setupWithViewPager(viewPager) tabLayout.setupWithViewPager(viewPager)
Log.d(tag, "SongFragment view created") Log.d(tag, "SongFragment view created")
Requestor.instance.snackBarText.observeForever(snackBarTextObserver)
return root return root
} }
override fun onDestroyView() {
Requestor.instance.snackBarText.removeObserver(snackBarTextObserver)
super.onDestroyView()
}
} }

View File

@ -1,70 +0,0 @@
package fr.forum_thalie.tsumugi.ui.songs.queuelp
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.R
import fr.forum_thalie.tsumugi.playerstore.PlayerStore
import fr.forum_thalie.tsumugi.playerstore.Song
class QueueFragment : Fragment(){
private val lastPlayedFragmentTag = this::class.java.name
private lateinit var recyclerView: RecyclerView
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var viewManager: RecyclerView.LayoutManager
private val queueObserver = Observer<Boolean> {
Log.d(tag, lastPlayedFragmentTag + "queue changed")
viewAdapter.notifyDataSetChanged()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val root = inflater.inflate(R.layout.fragment_last_played, container, false)
viewManager = LinearLayoutManager(context)
viewAdapter = SongAdaptater(
if (PlayerStore.instance.queue.isEmpty())
ArrayList<Song>(listOf((Song("No queue - "))))
else
PlayerStore.instance.queue
)
recyclerView = root.findViewById<RecyclerView>(R.id.queue_lp_recycler).apply {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
}
PlayerStore.instance.isQueueUpdated.observeForever(queueObserver)
return root
}
override fun onDestroyView() {
PlayerStore.instance.isQueueUpdated.removeObserver(queueObserver)
super.onDestroyView()
}
companion object {
@JvmStatic
fun newInstance() = QueueFragment()
}
}

View File

@ -1,69 +0,0 @@
package fr.forum_thalie.tsumugi.ui.songs.request
import kotlin.math.exp
import kotlin.math.max
/*
//PHP cooldown calculator
function pretty_cooldown($lp, $lr, $rc) {
$delay = delay($rc);
$now = time();
$cd = intval(max($lp + $delay - $now, $lr + $delay - $now));
if ($cd <= 0)
return "Request";
$days = intdiv_1($cd, 86400);
$cd = $cd % 86400;
$hours = intdiv_1($cd, 3600);
$cd = $cd % 3600;
$minutes = intdiv_1($cd, 60);
$seconds = $cd % 60;
if ($days > 0)
return "Requestable in ".$days."d".$hours."h";
else if ($hours > 0)
return "Requestable in ".$hours."h".$minutes."m";
else if ($minutes > 0)
return "Requestable in ".$minutes."m".$seconds."s";
return "Request";
}
function requestable($lastplayed, $requests) {
$delay = delay($requests);
return (time() - $lastplayed) > $delay;
}
function delay($priority) {
// priority is 30 max
if ($priority > 30)
$priority = 30;
// between 0 and 7 return magic
if ($priority >= 0 and $priority <= 7)
$cd = -11057 * $priority * $priority + 172954 * $priority + 81720;
// if above that, return magic crazy numbers
else
$cd = (int) (599955 * exp(0.0372 * $priority) + 0.5);
return $cd / 2;
}
*/
// this function implements the magic delay used on R/a/dio website:
// https://github.com/R-a-dio/site/blob/develop/app/start/global.php#L125
// (Seriously guys, what were you thinking with these crazy magic numbers...)
fun delay(rawPriority: Int) : Int {
val priority = if (rawPriority > 30) 30 else rawPriority
val coolDown : Int =
if (priority in 0..7)
-11057 * priority * priority + 172954 * priority + 81720
else
(599955 * exp(0.0372 * (priority.toDouble()) + 0.5)).toInt()
return coolDown/2
}
// I tweaked this to report in a single point whether the song is requestable or not
fun coolDown(lastPlayed: Int?, lastRequest: Int?, requestsNbr: Int?) : Long {
if (requestsNbr == null || lastPlayed == null || lastRequest == null)
return Long.MAX_VALUE // maximum positive value : the song won't be requestable
val delay = delay(requestsNbr)
val now = (System.currentTimeMillis() / 1000)
return max(lastPlayed, lastRequest) + delay - now
// if coolDown < 0, the song is requestable.
}

View File

@ -1,132 +0,0 @@
package fr.forum_thalie.tsumugi.ui.songs.request
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SearchView
import android.widget.TextView
import androidx.appcompat.widget.AppCompatButton
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import fr.forum_thalie.tsumugi.*
class FavoritesFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var viewManager: RecyclerView.LayoutManager
private lateinit var searchView: SearchView
private lateinit var root: View
private lateinit var recyclerSwipe: SwipeRefreshLayout
private val favoritesSongObserver : Observer<Boolean> = Observer {
viewAdapter.notifyDataSetChanged()
createView(isCallback = true) // force-re-create the view, but do not call again the initFavorites (avoid callback loop)
recyclerSwipe.isRefreshing = false // disable refreshing animation. Needs to be done manually...
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
root = inflater.inflate(R.layout.fragment_request, container, false)
return createView()
}
private fun createView(isCallback: Boolean = false) : View?
{
viewAdapter = RequestSongAdapter(Requestor.instance.favoritesSongArray)
val listener : SearchView.OnQueryTextListener = object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
// do nothing
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
(viewAdapter as RequestSongAdapter).filter(newText ?: "")
return true
}
}
searchView = root.findViewById(R.id.searchBox)
searchView.queryHint = "Search filter..."
searchView.setOnQueryTextListener(listener)
viewManager = LinearLayoutManager(context)
recyclerView = root.findViewById<RecyclerView>(R.id.request_recycler).apply {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
}
val noUserNameText : TextView = root.findViewById(R.id.noUserNameText)
recyclerSwipe = root.findViewById(R.id.recyclerSwipe) as SwipeRefreshLayout
recyclerSwipe.setOnRefreshListener {
val userName1 = preferenceStore.getString("userName", null)
Log.d(tag,"userName = $userName1")
if (userName1 != null && !userName1.isBlank())
{
noUserNameText.visibility = View.GONE
Requestor.instance.initFavorites()
} else {
noUserNameText.visibility = View.VISIBLE
recyclerSwipe.isRefreshing = false
}
}
val userName1 = preferenceStore.getString("userName", null)
Log.d(tag,"userName = $userName1")
if (userName1 != null && !userName1.isBlank())
{
noUserNameText.visibility = View.GONE
if (!isCallback) // avoid callback loop if called from the Observer.
Requestor.instance.initFavorites()
} else {
noUserNameText.visibility = View.VISIBLE
recyclerSwipe.isRefreshing = false
}
val raFButton : AppCompatButton = root.findViewById(R.id.ra_f_button)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) // for API21+ Material Design makes ripples on the button.
raFButton.supportBackgroundTintList = colorGreenList
else // But on API20- no Material Design support, so we add some more color when clicked
raFButton.supportBackgroundTintList = colorGreenListCompat
raFButton.isEnabled = true
raFButton.isClickable = true
raFButton.setOnClickListener {
val s = Requestor.instance.raF()
Requestor.instance.snackBarText.value = ""
Requestor.instance.addRequestMeta = "Request: ${s.artist.value} - ${s.title.value}\n"
Requestor.instance.request(s.id)
}
raFButton.visibility = View.VISIBLE
Requestor.instance.isFavoritesUpdated.observe(viewLifecycleOwner, favoritesSongObserver)
return root
}
companion object {
@JvmStatic
fun newInstance() = FavoritesFragment()
}
}

View File

@ -1,81 +0,0 @@
package fr.forum_thalie.tsumugi.ui.songs.request
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import fr.forum_thalie.tsumugi.R
class RequestFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var viewManager: RecyclerView.LayoutManager
private lateinit var searchView: SearchView
private val listener : SearchView.OnQueryTextListener = object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
if (query == null || query.isEmpty())
Requestor.instance.snackBarText.value = "Field is empty, no search possible."
else
Requestor.instance.search(query)
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
if (newText == "")
{
Requestor.instance.reset()
viewAdapter.notifyDataSetChanged() // this is to remove the "Load more" button
}
return true
}
}
private val requestSongObserver = Observer<Boolean> {
Log.d(tag, "request song list changed")
viewAdapter.notifyDataSetChanged()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val root = inflater.inflate(R.layout.fragment_request, container, false)
val recyclerSwipe = root.findViewById(R.id.recyclerSwipe) as SwipeRefreshLayout
recyclerSwipe.isEnabled = false // don't need to pull-to-refresh for Request
searchView = root.findViewById(R.id.searchBox)
searchView.setOnQueryTextListener(listener)
viewManager = LinearLayoutManager(context)
viewAdapter = RequestSongAdapter(Requestor.instance.requestSongArray)
recyclerView = root.findViewById<RecyclerView>(R.id.request_recycler).apply {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
}
Requestor.instance.isRequestResultUpdated.observe(viewLifecycleOwner, requestSongObserver)
return root
}
companion object {
@JvmStatic
fun newInstance() = RequestFragment()
}
}

View File

@ -1,42 +0,0 @@
package fr.forum_thalie.tsumugi.ui.songs.request
import fr.forum_thalie.tsumugi.playerstore.Song
import org.json.JSONObject
class RequestResponse(jsonResponse: JSONObject) {
//val total: Int = jsonResponse.getInt("total")
//val perPage: Int = jsonResponse.getInt("per_page") // should stay 20 but in any case...
val currentPage: Int = jsonResponse.getInt("current_page")
val lastPage: Int = jsonResponse.getInt("last_page")
//val fromNbr: Int = jsonResponse.getInt("from")
//val toNbr: Int = jsonResponse.getInt("to")
var songs : ArrayList<Song> = ArrayList()
init {
val songList = jsonResponse.getJSONArray("data")
for (i in 0 until songList.length())
{
val title = (songList[i] as JSONObject).getString("title")
val artist = (songList[i] as JSONObject).getString("artist")
val id = (songList[i] as JSONObject).getInt("id")
val s = Song("", id)
s.title.value = title
s.artist.value = artist
s.isRequestable = (songList[i] as JSONObject).getBoolean("requestable")
// TODO add the time before being requestable.
songs.add(s)
}
}
override fun toString(): String {
var s = ""
for (i in 0 until songs.size)
{
s += (songs[i].artist.value + " - " + songs[i].title.value + " | ")
}
return s
}
}

View File

@ -1,148 +0,0 @@
package fr.forum_thalie.tsumugi.ui.songs.request
import android.annotation.SuppressLint
import android.os.Build
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.RecyclerView
import fr.forum_thalie.tsumugi.*
import fr.forum_thalie.tsumugi.playerstore.Song
import kotlinx.android.synthetic.main.request_song_view.view.*
import android.view.View
import kotlinx.android.synthetic.main.button_load_more.view.*
import android.R.attr.name
import android.text.method.TextKeyListener.clear
import android.util.Log
import java.util.*
import kotlin.collections.ArrayList
class RequestSongAdapter(private val dataSet: ArrayList<Song>
/*,
context: Context,
resource: Int,
objects: Array<out Song>*/
) : RecyclerView.Adapter<RequestSongAdapter.MyViewHolder>() /*ArrayAdapter<Song>(context, resource, objects)*/ {
private val viewTypeCell = 1 // normal cell with song and request button
private val viewTypeFooter = 2 // the bottom cell should be the "load more" button whenever needed
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
// Each data item is just a string in this case that is shown in a TextView.
class MyViewHolder(view: ConstraintLayout) : RecyclerView.ViewHolder(view)
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): MyViewHolder {
// create a new view
val view =
if (viewType == viewTypeCell)
LayoutInflater.from(parent.context).inflate(R.layout.request_song_view, parent, false) as ConstraintLayout
else
LayoutInflater.from(parent.context).inflate(R.layout.button_load_more, parent, false) as ConstraintLayout
// set the view's size, margins, paddings and layout parameters
//...
return MyViewHolder(view)
}
// Replace the contents of a view (invoked by the layout manager)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
if (itemCount <= 1)
{
// in any case, if there's nothing, don't display the loadMore button!!
holder.itemView.loadMoreButton.visibility = View.GONE
return
}
if (holder.itemViewType == viewTypeFooter)
{
if (Requestor.instance.isLoadMoreVisible)
holder.itemView.loadMoreButton.visibility = View.VISIBLE
else
holder.itemView.loadMoreButton.visibility = View.GONE
holder.itemView.loadMoreButton.text = "Load more results"
holder.itemView.loadMoreButton.setOnClickListener{
Requestor.instance.loadMore()
}
return
}
val artist = holder.itemView.findViewById<TextView>(R.id.request_song_artist)
val title = holder.itemView.findViewById<TextView>(R.id.request_song_title)
val button = holder.itemView.request_button
if (dataSet[position].isRequestable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) // for API21+ Material Design makes ripples on the button.
button.supportBackgroundTintList = colorGreenList
else // But on API20- no Material Design support, so we add some more color when clicked
button.supportBackgroundTintList = colorGreenListCompat
button.isEnabled = true
button.isClickable = true
button.setOnClickListener {
Requestor.instance.request(dataSet[position].id)
}
} else {
button.supportBackgroundTintList = colorRedList
button.isEnabled = false
button.isClickable = false
}
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
button,2, 24, 2, TypedValue.COMPLEX_UNIT_SP)
artist.text = dataSet[position].artist.value
title.text = dataSet[position].title.value
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = dataSet.size + 1 // add 1 for the "Load more results" button
override fun getItemViewType(position: Int): Int {
return if (position == dataSet.size) viewTypeFooter else viewTypeCell
}
/*
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
// create a new view
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.song_view, parent, false) as ConstraintLayout
}
*/
// a filtering function. As naive as it could be, but it should work.
private val dataSetOrig = ArrayList<Song>()
init {
dataSetOrig.addAll(dataSet)
}
fun filter(entry: String) {
var text = entry
dataSet.clear()
Log.d(tag, "entering filter")
if (text.isEmpty()) {
dataSet.addAll(dataSetOrig)
} else {
text = text.toLowerCase(locale = Locale.ROOT)
for (item in dataSetOrig) {
Log.d(tag, "$text, ${item.artist.value!!.toLowerCase(locale = Locale.ROOT)}, ${item.title.value!!.toLowerCase(locale = Locale.ROOT)}")
if (item.artist.value!!.toLowerCase(locale = Locale.ROOT).contains(text) ||
item.title.value!!.toLowerCase(locale = Locale.ROOT).contains(text)) {
dataSet.add(item)
}
}
}
notifyDataSetChanged()
}
}

View File

@ -1,301 +0,0 @@
package fr.forum_thalie.tsumugi.ui.songs.request
import android.util.Log
import androidx.lifecycle.MutableLiveData
import fr.forum_thalie.tsumugi.ActionOnError
import fr.forum_thalie.tsumugi.Async
import fr.forum_thalie.tsumugi.playerstore.Song
import fr.forum_thalie.tsumugi.preferenceStore
import fr.forum_thalie.tsumugi.tag
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.CookieHandler
import java.net.CookieManager
import java.net.MalformedURLException
import java.net.URL
import java.util.*
import java.util.regex.Pattern
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList
import kotlin.random.Random
/**
* Requests a song via the website's API
*
* We scrape the website for a CSRF token and POST it to /request/ endpoint with
* the song id
*
* Created by Kethsar on 1/2/2017.
* Converted to Kotlin and adapted by Yattoz on 05 Nov. 2019
*/
class Requestor {
var addRequestMeta: String = ""
private val cookieManager: CookieManager = CookieManager()
private val requestUrl = "https://r-a-d.io/request/%1\$d"
private val searchUrl = "https://r-a-d.io/api/search/%1s?page=%2\$d"
private val favoritesUrl = "https://r-a-d.io/faves/%1s?dl=true"
private val songThresholdStep = 50
private var songThreshold = songThresholdStep
private var localQuery = ""
private var token: String? = null
val snackBarText : MutableLiveData<String?> = MutableLiveData()
private var responseArray : ArrayList<RequestResponse> = ArrayList()
val requestSongArray : ArrayList<Song> = ArrayList()
val favoritesSongArray : ArrayList<Song> = ArrayList()
val isRequestResultUpdated : MutableLiveData<Boolean> = MutableLiveData()
val isFavoritesUpdated : MutableLiveData<Boolean> = MutableLiveData()
var isLoadMoreVisible: Boolean = false
init {
snackBarText.value = ""
isRequestResultUpdated.value = false
isFavoritesUpdated.value = false
isLoadMoreVisible = false
}
fun initFavorites(userName : String? = preferenceStore.getString("userName", null)){
Log.d(tag, "initializing favorites")
favoritesSongArray.clear()
if (userName == null)
{
// Display is done by default in the XML.
Log.d(tag, "no user name set for favorites")
isFavoritesUpdated.value = true
return
}
val favoritesUserUrl = String.format(Locale.getDefault(), favoritesUrl, userName)
val scrapeFavorites : (Any?) -> JSONArray = {
JSONArray(URL(favoritesUserUrl).readText())
}
val postFavorites : (Any?) -> Unit = {
val res = it as JSONArray
for (i in 0 until (res).length())
{
val item = res.getJSONObject(i)
val artistTitle = item.getString("meta")
val id : Int? = if (item.isNull("tracks_id"))
null
else
item.getInt("tracks_id")
val lastRequested : Int? = if (item.isNull("lastrequested")) null else item.getInt("lastrequested")
val lastPlayed : Int? = if (item.isNull("lastplayed")) null else item.getInt("lastplayed")
val requestCount : Int? = if (item.isNull("requestcount")) null else item.getInt("requestcount")
val isRequestable = (coolDown(lastPlayed, lastRequested, requestCount) < 0)
//Log.d(tag, "val : $id")
favoritesSongArray.add(Song(artistTitle, id ?: 0, isRequestable))
}
Log.d(tag, "favorites : $favoritesSongArray")
isFavoritesUpdated.value = true
}
Async(scrapeFavorites, postFavorites, ActionOnError.NOTIFY)
}
fun search(query: String)
{
responseArray.clear()
requestSongArray.clear()
localQuery = query
searchPage(query, 1) // the searchPage function is recursive to get all pages.
}
private fun searchPage(query: String, pageNumber : Int)
{
val searchURL = String.format(Locale.getDefault(), searchUrl, query, pageNumber)
val scrape : (Any?) -> JSONObject = {
val res = URL(searchURL).readText()
val json = JSONObject(res)
json
}
val post : (Any?) -> Unit = {
val response = RequestResponse(it as JSONObject)
responseArray.add(response)
for (i in 0 until response.songs.size)
{
requestSongArray.add(response.songs[i])
}
isRequestResultUpdated.value = true
if (requestSongArray.size >= songThreshold)
{
isLoadMoreVisible = true
} else {
if (response.currentPage < response.lastPage)
searchPage(query, pageNumber + 1) // recursive call to get the next page
else
finishSearch()
}
}
Async(scrape, post, ActionOnError.NOTIFY)
}
private fun finishSearch()
{
isLoadMoreVisible = false
}
fun reset()
{
requestSongArray.clear()
responseArray.clear()
isRequestResultUpdated.value = false
songThreshold = songThresholdStep
}
fun loadMore()
{
songThreshold += songThresholdStep
searchPage(localQuery, responseArray.last().currentPage + 1)
}
/**
* Scrape the website for the CSRF token required for requesting
* scrapeToken and postToken are the two lambas run by the Async() class.
*/
private val scrapeToken : (Any?) -> Any? = {
val radioSearchUrl = "https://r-a-d.io/search"
var searchURL: URL? = null
var retVal: String? = null
var reader: BufferedReader? = null
CookieHandler.setDefault(cookieManager) // it[0] ??
try {
searchURL = URL(radioSearchUrl)
} catch (e: MalformedURLException) {
e.printStackTrace()
}
try {
reader = BufferedReader(InputStreamReader(searchURL!!.openStream(), "UTF-8"))
var line: String?
line = reader.readLine()
while (line != null)
{
line = line.trim { it <= ' ' }
val p = Pattern.compile("value=\"(\\w+)\"")
val m = p.matcher(line)
if (line.startsWith("<form")) {
if (m.find()) {
retVal = m.group(1)
break
}
}
line = reader.readLine()
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
if (reader != null) try {
reader.close()
} catch (ignored: IOException) {
}
}
retVal
}
private val postToken : (Any?) -> (Unit) = {
token = it as String?
}
/**
* Request the song with the CSRF token that was scraped
*/
private val requestSong: (Any?) -> Any? = {
val reqString = it as String
var response = ""
try {
val reqURL = URL(reqString)
val conn = reqURL.openConnection() as HttpsURLConnection
val tokenObject = JSONObject()
tokenObject.put("_token", token)
val requestBytes = tokenObject.toString().toByteArray()
conn.requestMethod = "POST"
conn.doOutput = true
conn.doInput = true
conn.setChunkedStreamingMode(0)
conn.setRequestProperty("Content-Type", "application/json")
val os = conn.outputStream
os.write(requestBytes)
val responseCode = conn.responseCode
if (responseCode == HttpsURLConnection.HTTP_OK) {
var line: String?
val br = BufferedReader(InputStreamReader(
conn.inputStream))
line = br.readLine()
while (line != null) {
response += line
line = br.readLine()
}
} else {
response += ""
}
} catch (ex: IOException) {
ex.printStackTrace()
} catch (ex: JSONException) {
ex.printStackTrace()
}
response
}
private val postSong : (Any?) -> (Unit) = {
val response = JSONObject(it as String)
val key = response.names()!!.get(0) as String
val value = response.getString(key)
snackBarText.postValue(addRequestMeta + value)
}
fun request(songID: Int?) {
val requestSongUrl = String.format(requestUrl, songID!!)
if (token == null) {
Async(scrapeToken, postToken, ActionOnError.NOTIFY)
}
Async(requestSong, postSong, ActionOnError.NOTIFY, requestSongUrl)
}
fun raF() : Song {
// request a random favorite song. HELL YEAH
val requestableSongArray = ArrayList<Song>()
for (i in 0 until favoritesSongArray.size)
{
if (favoritesSongArray[i].isRequestable && (favoritesSongArray[i].id ?: 0) > 0)
requestableSongArray.add(favoritesSongArray[i])
}
return if (requestableSongArray.isNotEmpty()) {
val songNbr = Random(System.currentTimeMillis()).nextInt(1, requestableSongArray.size)
requestableSongArray[songNbr]
} else {
Song("No song requestable - ")
}
}
companion object {
val instance by lazy {
Requestor()
}
}
}

View File

@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="26.086956"
android:viewportHeight="26.086956"
android:tint="#FFFFFF">
<group android:translateX="1.0434783"
android:translateY="1.0434783">
<path
android:fillColor="#FF000000"
android:pathData="M3.24,6.15C2.51,6.43 2,7.17 2,8v12c0,1.1 0.89,2 2,2h16c1.11,0 2,-0.9 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2L8.3,6l8.26,-3.34L15.88,1 3.24,6.15zM7,20c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM20,12h-2v-2h-2v2L4,12L4,8h16v4z"/>
</group>
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="#FF000000"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z"/>
</vector>

View File

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<path
android:fillColor="#FF000000"
android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h4v-2L5,18L5,8h14v10h-4v2h4c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.89,-2 -2,-2zM12,10l-4,4h3v6h2v-6h3l-4,-4z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 659 B

View File

@ -1,11 +1,11 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque"> android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme --> <!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@color/colorPrimaryDark"/> <item android:drawable="@color/colorAccent"/>
<!-- Your product logo - 144dp color version of your app icon --> <!-- Your product logo - 144dp color version of your app icon -->
<item <item
android:drawable="@drawable/ic_radio_logo_round" android:drawable="@drawable/ic_tsumugi_lineart"
android:gravity="center" android:gravity="center"
android:height="144dp" android:height="144dp"
android:width="144dp" android:width="144dp"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,18 +0,0 @@
<vector xmlns:tools="http://schemas.android.com/tools"
android:height="144dp" android:viewportHeight="130"
android:viewportWidth="130" android:width="144dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="1" android:fillColor="#f5f5f5"
android:pathData="m56.497,129.184c-3.016,-0.459 -7.77,-1.474 -8.229,-1.758 -0.303,-0.187 -0.37,-1.586 -0.37,-7.713 0,-7.406 0.225,-10.317 1.041,-13.463 0.204,-0.786 0.324,-1.476 0.266,-1.534 -0.057,-0.057 -0.956,0.699 -1.996,1.68 -4.188,3.95 -10.293,6.952 -18.619,9.154l-2.904,0.768 -1.065,-0.722C22.693,114.289 15.627,107.008 13.48,104.118 11.15,100.982 8.714,97.085 8.941,96.858c0.081,-0.081 1.319,0.293 2.751,0.832 3.752,1.411 9.219,2.726 12.833,3.088 13.515,1.351 23.624,-5.425 28.35,-19.003 0.84,-2.414 1.934,-6.922 1.725,-7.106 -0.048,-0.043 -0.624,-0.347 -1.279,-0.677 -3.497,-1.761 -8.052,-5.635 -10.005,-8.508 -2.694,-3.963 -4.527,-9.389 -4.971,-14.717l-0.175,-2.103 -1.686,-0.135c-4.393,-0.353 -7.049,-3.319 -7.431,-8.299 -0.346,-4.51 2.339,-8.89 6.315,-10.301 1.915,-0.68 4.271,-0.79 5.647,-0.264 1.357,0.518 3.329,2.354 3.832,3.568 0.185,0.447 0.446,0.813 0.579,0.813 0.133,0 0.744,-0.491 1.357,-1.091 1.998,-1.957 7.303,-5.111 7.786,-4.629 0.173,0.173 1.626,14.106 1.652,15.84 0.009,0.597 -0.104,0.728 -0.631,0.728 -1.316,0 -1.474,0.502 -1.474,4.685l0,3.853l-1.815,1.91c-2.167,2.282 -3.229,4.344 -3.412,6.629 -0.396,4.93 3.516,9.928 8.596,10.982 1.828,0.38 4.104,0.065 6.033,-0.833 1.765,-0.822 4.084,-3.186 5.036,-5.133 0.701,-1.433 0.776,-1.844 0.776,-4.233 0,-2.386 -0.075,-2.8 -0.77,-4.215 -0.451,-0.918 -1.47,-2.268 -2.456,-3.254l-1.685,-1.685 0.031,-4.077c0.017,-2.242 -0.046,-4.201 -0.14,-4.352 -0.094,-0.152 -0.382,-0.276 -0.64,-0.276 -1.013,0 -1.046,0.149 2.346,-10.487l2.206,-6.916 1.745,0.594c6.586,2.243 11.574,6.705 14.448,12.924 0.484,1.048 1.306,3.357 1.827,5.132 1.222,4.166 1.877,5.897 2.68,7.079 0.357,0.527 0.65,1.068 0.65,1.203 0,0.228 -0.637,0.442 -4.101,1.376 -1.002,0.27 -1.323,0.48 -1.323,0.863 0,0.697 2.512,1.989 3.867,1.989 0.962,0 1.028,0.05 1.028,0.78 0,0.429 -0.298,1.34 -0.661,2.024 -0.535,1.007 -0.661,1.621 -0.661,3.217 0,1.718 -0.085,2.074 -0.661,2.759 -0.674,0.802 -0.874,1.906 -0.397,2.201 0.146,0.09 0.265,0.507 0.265,0.926 0,0.419 -0.119,0.836 -0.265,0.926 -0.153,0.095 -0.265,1.544 -0.265,3.449 0,3.198 -0.017,3.297 -0.651,3.712 -0.688,0.451 -0.912,0.434 -4.153,-0.316 -1.321,-0.306 -1.897,-0.335 -2.304,-0.117 -0.563,0.301 -1.09,1.742 -1.093,2.987 -0.003,1.693 -2.224,6.44 -2.678,5.724 -0.124,-0.196 -0.228,-1.214 -0.23,-2.262 -0.002,-1.048 -0.13,-2.493 -0.284,-3.212 -0.292,-1.361 -0.875,-2.415 -1.172,-2.118 -0.513,0.513 -1.992,10.728 -1.72,11.88 0.12,0.508 0.466,0.691 2.034,1.077 4.671,1.148 7.063,3.567 7.805,7.892 0.539,3.142 -0.341,10.284 -1.758,14.267 -0.226,0.637 -0.41,1.351 -0.408,1.587 0.002,0.236 1.487,2.567 3.301,5.179l3.298,4.749 -0.854,0.364c-1.188,0.507 -7.225,2.031 -10.246,2.587 -3.3,0.608 -15.425,0.783 -18.785,0.272z"
android:strokeAlpha="1" android:strokeColor="#00000000"
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="1.33333325"
tools:ignore="VectorPath" />
<path android:fillAlpha="1" android:fillColor="#f5f5f5"
android:pathData="m91.686,122.295c0,-1.147 -0.159,-1.729 -0.707,-2.598 -2.015,-3.188 -4.277,-9.369 -4.617,-12.616 -0.276,-2.633 -0.587,-3.682 -2.237,-7.553 -0.811,-1.902 -1.037,-2.794 -1.121,-4.432 -0.082,-1.603 -0.027,-2.049 0.254,-2.045 0.619,0.01 1.489,0.881 1.91,1.912l0.406,0.992l2.115,0c1.163,0 2.19,-0.122 2.282,-0.271 0.186,-0.3 -1.685,-3.435 -2.701,-4.525 -0.356,-0.382 -1.591,-1.332 -2.744,-2.111 -2.079,-1.404 -2.894,-2.431 -2.894,-3.65 0,-0.305 0.045,-0.555 0.099,-0.555 0.055,0 0.772,0.298 1.595,0.661 2.106,0.931 2.274,0.892 2.274,-0.529 0,-0.655 0.109,-1.191 0.241,-1.191 0.249,0 3.206,3.628 4.851,5.953 2.423,3.424 6.277,12.163 7.702,17.462 0.886,3.297 2.382,11.144 2.21,11.593 -0.208,0.542 -6.74,4.305 -8.323,4.795 -0.568,0.176 -0.595,0.117 -0.595,-1.294zM90.315,99.671c0.098,-1.376 -0.346,-1.862 -1.703,-1.862 -1.129,0 -1.739,0.618 -1.521,1.54 0.289,1.22 0.876,1.669 2.068,1.581 1.067,-0.078 1.072,-0.085 1.156,-1.26z"
android:strokeAlpha="1" android:strokeColor="#00000000"
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="1.33333325"
tools:ignore="VectorPath" />
<path android:fillAlpha="1" android:fillColor="#f5f5f5"
android:pathData="m57.486,69.12c-5.937,-1.578 -6.761,-9.516 -1.285,-12.376 2.399,-1.253 5.309,-0.938 7.213,0.782 1.654,1.494 2.199,2.757 2.199,5.094 0,2.319 -0.56,3.636 -2.136,5.019 -1.739,1.526 -3.854,2.05 -5.991,1.481z"
android:strokeAlpha="1" android:strokeColor="#00000000"
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="1.33333325"/>
</vector>

View File

@ -1,18 +0,0 @@
<vector xmlns:tools="http://schemas.android.com/tools"
android:height="144dp" android:viewportHeight="130"
android:viewportWidth="130" android:width="144dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="1" android:fillColor="#f5f5f5"
android:pathData="m48.088,128.692c-0.323,-2.725 -0.477,-9.598 -0.297,-13.223 0.227,-4.572 0.798,-8.774 1.407,-10.35 0.223,-0.577 -0.398,-0.371 -1.022,0.34 -4.345,4.949 -13.03,8.963 -24.242,11.205 -6.35,1.27 -12.392,1.943 -20.346,2.268l-3.597,0.194l0,-8.227c0,-4.525 -0.016,-10.434 -0.007,-13.234L-0,92.494 3.744,94.173c7.594,3.405 14.078,5.826 20.089,6.514 2.987,0.342 7.839,0.132 10.296,-0.446 6.157,-1.449 11.139,-4.971 14.864,-10.51 2.054,-3.054 4.272,-8.386 5.16,-12.402 0.571,-2.581 0.552,-2.656 -0.799,-3.297 -3.588,-1.702 -8.251,-5.744 -10.333,-8.955 -1.72,-2.654 -3.083,-5.859 -3.914,-9.209 -0.397,-1.6 -0.862,-4.916 -0.862,-6.151l0,-1.052l-0.889,-0.003c-1.226,-0.004 -3.218,-0.45 -4.161,-0.932 -2.634,-1.344 -4.159,-4.346 -4.181,-8.233 -0.028,-5.06 3.09,-9.007 7.921,-10.028 0.832,-0.176 1.845,-0.258 2.489,-0.202 2.09,0.181 4.191,1.661 5.402,3.806 0.31,0.549 0.609,0.999 0.664,0.999 0.055,0 0.727,-0.595 1.493,-1.323 1.566,-1.488 3.611,-2.887 5.566,-3.809 2.064,-0.973 2.12,-0.979 2.24,-0.256 0.092,0.556 1.352,13.901 1.463,15.489 0.044,0.637 0.025,0.656 -0.753,0.761 -0.439,0.06 -0.902,0.213 -1.029,0.341 -0.171,0.173 -0.216,1.242 -0.175,4.174l0.056,3.941 -1.404,1.294c-1.729,1.593 -3.081,3.576 -3.655,5.361 -0.369,1.149 -0.42,1.612 -0.338,3.078 0.191,3.417 1.991,6.404 5.05,8.381 3.174,2.051 7.386,2.06 10.539,0.023 2.898,-1.873 4.94,-5.486 4.938,-8.737 -0.002,-2.817 -1.027,-5.057 -3.418,-7.465l-1.653,-1.665 0.05,-4.11c0.047,-3.847 0.028,-4.126 -0.304,-4.369 -0.195,-0.142 -0.548,-0.259 -0.786,-0.259 -0.327,0 -0.432,-0.105 -0.432,-0.433 0,-0.747 5.15,-16.966 5.387,-16.966 0.465,0 3.837,1.283 5.198,1.977 4.423,2.257 8.254,6.142 10.53,10.679 1.084,2.161 1.265,2.651 2.288,6.208 1.002,3.48 2.078,6.197 2.862,7.226 0.268,0.351 0.487,0.685 0.487,0.74 0,0.218 -1.128,0.665 -2.632,1.044 -3.138,0.791 -2.98,0.726 -2.98,1.216 0,0.707 2.113,1.815 3.728,1.954l1.23,0.106 0.06,0.626c0.042,0.438 -0.155,1.057 -0.655,2.058 -0.699,1.4 -0.714,1.473 -0.681,3.336l0.034,1.905 -0.642,0.681c-0.5,0.53 -0.642,0.839 -0.642,1.393 0,0.405 0.121,0.813 0.281,0.945 0.359,0.298 0.375,1.607 0.021,1.743 -0.204,0.078 -0.274,0.822 -0.33,3.5 -0.069,3.301 -0.083,3.411 -0.51,3.756 -0.586,0.474 -1.312,0.449 -3.878,-0.136 -3.071,-0.7 -3.444,-0.461 -3.826,2.456 -0.121,0.926 -0.388,2.135 -0.594,2.686 -0.432,1.161 -1.79,3.582 -2.009,3.582 -0.263,0 -0.375,-0.796 -0.377,-2.686 -0.003,-2.42 -0.666,-5.172 -1.246,-5.172 -0.329,0 -0.538,0.553 -0.705,1.871 -0.072,0.566 -0.246,1.45 -0.387,1.964 -0.141,0.514 -0.311,1.609 -0.377,2.432 -0.067,0.823 -0.235,2.33 -0.373,3.347 -0.368,2.701 -0.306,2.793 2.244,3.371 2.834,0.642 5.471,2.475 6.509,4.523 0.952,1.878 1.16,2.95 1.145,5.877 -0.022,4.194 -0.502,7.048 -2.157,12.816l-0.215,0.748 4.492,6.482c2.47,3.565 4.557,7.11 4.628,7.224 0.093,0.151 -5.43,0.123 -20.266,0.081l-20.332,-0.059z"
android:strokeAlpha="1" android:strokeColor="#00000000"
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="0.94280899"
tools:ignore="VectorPath" />
<path android:fillAlpha="1" android:fillColor="#f5f5f5"
android:pathData="m57.115,69.085c-5.74,-1.898 -6.229,-9.968 -0.759,-12.531 0.921,-0.432 1.308,-0.5 2.846,-0.5 1.636,0 1.882,0.05 3.035,0.617 1.391,0.684 2.307,1.647 2.987,3.139 0.344,0.754 0.42,1.259 0.425,2.795 0.005,1.747 -0.037,1.959 -0.612,3.129 -1.395,2.834 -4.943,4.335 -7.921,3.35z"
android:strokeAlpha="1" android:strokeColor="#00000000"
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="0.94280899"/>
<path android:fillAlpha="1" android:fillColor="#f5f5f5"
android:pathData="m92.411,130.001c-0.259,-0.42 -0.681,-3.216 -0.775,-5.939l-0.106,-3.087 -1.564,-3.181c-0.86,-1.749 -1.997,-4.401 -2.527,-5.893 -0.836,-2.354 -0.986,-2.997 -1.135,-4.864 -0.203,-2.548 -0.481,-3.596 -1.593,-6.023 -1.397,-3.05 -1.751,-4.298 -1.751,-6.176 0,-1.327 0.058,-1.679 0.291,-1.769 0.442,-0.17 1.605,0.877 1.89,1.702 0.418,1.207 0.464,1.228 2.711,1.228 2.313,0 2.522,-0.13 1.87,-1.166 -0.211,-0.336 -0.719,-1.158 -1.127,-1.827 -0.925,-1.516 -1.892,-2.446 -4.058,-3.905 -2.056,-1.385 -2.39,-1.732 -2.775,-2.883 -0.572,-1.71 -0.609,-1.699 1.995,-0.588 0.902,0.385 1.695,0.644 1.763,0.576 0.068,-0.068 0.108,-0.565 0.09,-1.105 -0.052,-1.516 0.156,-1.626 1.087,-0.574 0.439,0.496 1.718,2.123 2.841,3.615 1.719,2.284 2.361,3.349 4.06,6.735 3.051,6.08 4.741,10.916 5.82,16.651 0.319,1.698 0.7,3.634 0.845,4.303 0.502,2.316 0.669,4.231 0.784,9.027l0.116,5.144 -4.401,0.004c-2.427,0.002 -4.303,0.073 -4.351,-0.004zM90.081,100.856c0.287,-0.154 0.361,-0.394 0.359,-1.169 -0.002,-0.537 -0.065,-1.068 -0.14,-1.181 -0.413,-0.618 -1.845,-0.83 -2.768,-0.41 -0.458,0.209 -0.49,0.296 -0.41,1.124 0.093,0.967 0.501,1.575 1.143,1.7 0.776,0.152 1.459,0.128 1.817,-0.064z"
android:strokeAlpha="1" android:strokeColor="#00000000"
android:strokeLineCap="butt" android:strokeLineJoin="miter" android:strokeWidth="0.94280899"
tools:ignore="VectorPath" />
</vector>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- shamelessly ripped off https://stackoverflow.com/questions/32212259/android-how-to-change-default-seekbar-thickness -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners
android:radius="4dp"
/>
<solid
android:color="@color/seek_bar_background"
/>
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners
android:radius="4dp"
/>
<solid
android:color="@color/seek_bar_secondary_progress"
/>
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners
android:radius="4dp"
/>
<solid
android:color="@color/seek_bar_progress"
/>
</shape>
</clip>
</item>
</layer-list>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- shamelessly ripped off https://stackoverflow.com/questions/32212259/android-how-to-change-default-seekbar-thickness -->
<!--
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
android:innerRadius="@dimen/seek_bar_thumb_size">
<size
android:height="@dimen/seek_bar_thumb_size"
android:width="@dimen/seek_bar_thumb_size"
/>
<solid android:color="@color/seek_bar_progress"/>
</shape>
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<corners
android:radius="2dp"
/>
<size
android:height="@dimen/seek_bar_thumb_height"
android:width="@dimen/seek_bar_thumb_size"
/>
<solid android:color="@color/whited2"/>
</shape>

View File

@ -37,15 +37,17 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
> >
<!-- REMOVE VISIBILITY GONE TO ADD AN ICON IN THE TOP BAR -->
<ImageView <ImageView
android:id="@+id/logo_persistent" android:id="@+id/logo_persistent"
android:layout_width="?attr/actionBarSize" android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:srcCompat="@drawable/actionbar_logo" app:srcCompat="@drawable/logo_roundsquare"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:padding="12dp" android:padding="12dp"
android:contentDescription="@string/r_a_dio_icon" /> android:contentDescription="@string/r_a_dio_icon"
android:visibility="gone"/>
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
@ -54,7 +56,7 @@
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar" android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:menu="@menu/toolbar_menu" app:menu="@menu/toolbar_menu"
app:layout_constraintStart_toEndOf="@id/logo_persistent" app:layout_constraintStart_toEndOf="@+id/logo_persistent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="-4dp" android:layout_marginLeft="-4dp"

View File

@ -40,7 +40,7 @@
app:layout_constraintStart_toStartOf="@id/imageGuideline" app:layout_constraintStart_toStartOf="@id/imageGuideline"
app:layout_constraintTop_toTopOf="@id/seek_bar_volume" app:layout_constraintTop_toTopOf="@id/seek_bar_volume"
app:srcCompat="@drawable/actionbar_logo" app:srcCompat="@drawable/logo_roundsquare"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<androidx.constraintlayout.widget.Guideline <androidx.constraintlayout.widget.Guideline
@ -72,7 +72,8 @@
app:layout_constraintHorizontal_bias="1.0" app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintVertical_weight="3" app:layout_constraintVertical_weight="3"
app:layout_constraintStart_toStartOf="@id/streamerPicture" app:layout_constraintStart_toStartOf="@id/streamerPicture"
app:layout_constraintTop_toBottomOf="@id/streamerPicture" /> app:layout_constraintTop_toBottomOf="@id/streamerPicture"
android:visibility="gone"/>
<TextView <TextView
android:id="@+id/listenersCount" android:id="@+id/listenersCount"
android:layout_width="0dp" android:layout_width="0dp"
@ -87,7 +88,9 @@
app:layout_constraintBottom_toTopOf="@id/topInfoGuideline" app:layout_constraintBottom_toTopOf="@id/topInfoGuideline"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/streamerPicture" app:layout_constraintStart_toStartOf="@id/streamerPicture"
app:layout_constraintTop_toBottomOf="@id/streamerName" /> app:layout_constraintTop_toBottomOf="@id/streamerName"
android:visibility="gone"
/>
<androidx.constraintlayout.widget.Guideline <androidx.constraintlayout.widget.Guideline
android:id="@+id/topInfoGuideline" android:id="@+id/topInfoGuideline"
@ -170,6 +173,7 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:visibility="gone"
> >
<TextView <TextView
@ -287,6 +291,7 @@
<!-- android:progressDrawable="@drawable/progress_bar_progress" --> <!-- android:progressDrawable="@drawable/progress_bar_progress" -->
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" style="@style/Widget.AppCompat.ProgressBar.Horizontal"
@ -297,8 +302,10 @@
android:progress="70" android:progress="70"
android:progressDrawable="@drawable/progress_bar_progress" android:progressDrawable="@drawable/progress_bar_progress"
app:layout_constraintBottom_toTopOf="@id/play_pause" app:layout_constraintBottom_toTopOf="@id/play_pause"
tools:layout_editor_absoluteX="0dp" /> tools:layout_editor_absoluteX="0dp"
android:visibility="gone"/>
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
<TextView <TextView
android:id="@+id/endTime" android:id="@+id/endTime"
android:layout_width="44sp" android:layout_width="44sp"
@ -308,8 +315,10 @@
android:text="" android:text=""
android:textAlignment="textEnd" android:textAlignment="textEnd"
app:layout_constraintEnd_toEndOf="@id/progressBar" app:layout_constraintEnd_toEndOf="@id/progressBar"
app:layout_constraintTop_toBottomOf="@id/progressBar" /> app:layout_constraintTop_toBottomOf="@id/progressBar"
android:visibility="gone"/>
<!-- REMOVE VISIBILITY GONE IF YOU HAVE TIME VALUES TO DISPLAY THE PROGRESS BAR -->
<TextView <TextView
android:id="@+id/currentTime" android:id="@+id/currentTime"
android:layout_width="44sp" android:layout_width="44sp"
@ -319,7 +328,8 @@
android:text="" android:text=""
android:textAlignment="textStart" android:textAlignment="textStart"
app:layout_constraintStart_toStartOf="@id/progressBar" app:layout_constraintStart_toStartOf="@id/progressBar"
app:layout_constraintTop_toBottomOf="@id/progressBar" /> app:layout_constraintTop_toBottomOf="@id/progressBar"
android:visibility="gone"/>

View File

@ -16,9 +16,5 @@
android:icon="@drawable/ic_newspaper" android:icon="@drawable/ic_newspaper"
android:title="@string/title_news"/> android:title="@string/title_news"/>
<item
android:id="@+id/navigation_chat"
android:icon="@drawable/ic_chat_processing"
android:title="@string/title_chat"/>
</menu> </menu>

View File

@ -14,11 +14,12 @@
android:icon="@drawable/ic_alarm" android:icon="@drawable/ic_alarm"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<!--
<item <item
android:id="@+id/action_refresh" android:id="@+id/action_refresh"
android:title="@string/action_refresh" android:title="@string/action_refresh"
app:showAsAction="never"/> app:showAsAction="never"/>
-->
<!-- <!--
<item <item

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navigation_chat"
app:startDestination="@id/fragment_chat"
tools:ignore="UnusedNavigation">
<fragment
android:id="@+id/fragment_chat"
android:name="fr.forum_thalie.tsumugi.ui.chat.ChatFragment"
android:label="@string/title_chat"
tools:layout="@layout/fragment_chat" />
</navigation>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#F58B01</color>
</resources>

View File

@ -29,7 +29,7 @@
<string name="action_settings">Settings</string> <string name="action_settings">Settings</string>
<string name="action_refresh">Force data refresh</string> <string name="action_refresh">Force data refresh</string>
<string name="action_bug_submit">Submit a bug (GitHub)</string> <string name="action_bug_submit">Submit a bug (GitHub)</string>
<string name="github_url_new_issue">https://github.com/yattoz/Radio2/issues/</string> <string name="github_url_new_issue">https://github.com/yattoz/Tsumugi-app/issues/</string>
<string name="no_user_name">To display your favorite, set you IRC user name in Settings → Customize app behavior.</string> <string name="no_user_name">To display your favorite, set you IRC user name in Settings → Customize app behavior.</string>
<string name="ra_f">.ra f</string> <string name="ra_f">.ra f</string>

View File

@ -6,13 +6,13 @@
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="icon">@drawable/actionbar_logo</item> <item name="icon">@drawable/logo_roundsquare</item>
<item name="android:textColorHighlight">@color/rblue</item> <item name="android:textColorHighlight">@color/rblue</item>
<item name="android:colorBackground">@color/colorPrimaryDark</item> <item name="android:colorBackground">@color/colorPrimaryDark</item>
</style> </style>
<style name="AppTheme.Parameters" parent="AppTheme"> <style name="AppTheme.Parameters" parent="AppTheme">
<item name="icon" /> <item name="icon"/>
</style> </style>
<style name="AppTheme.BottomBar" parent="AppTheme"> <style name="AppTheme.BottomBar" parent="AppTheme">

View File

@ -2,14 +2,6 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
app:iconSpaceReserved="false"
app:key="userName"
app:title="User name for favorite songs"
/>
<SeekBarPreference <SeekBarPreference
app:key="volume" app:key="volume"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
@ -34,24 +26,7 @@
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:title="Make Snackbars persistent" app:title="Make Snackbars persistent"
app:singleLineTitle="false" app:singleLineTitle="false"
app:defaultValue="true" app:defaultValue="false"
/>
<ListPreference
app:key="fetchPeriod"
app:iconSpaceReserved="false"
android:title="Set update period when stopped"
app:singleLineTitle="false"
android:entries="@array/fetchPeriodString"
android:entryValues="@array/fetchPeriodValues"
android:defaultValue="10"
/>
<Preference
app:key="helpFavorites"
app:title="Help to use favorites with IRC"
app:summary="Opens the wiki in your web browser"
app:icon="@drawable/ic_open_in_browser"
/> />
</PreferenceScreen> </PreferenceScreen>

View File

@ -25,13 +25,6 @@
app:fragment="fr.forum_thalie.tsumugi.preferences.CustomizeFragment" app:fragment="fr.forum_thalie.tsumugi.preferences.CustomizeFragment"
/> />
<Preference
app:icon="@drawable/ic_notification"
app:key="streamerNotifServiceFragment"
app:title="Streamer Notification Service"
app:fragment="fr.forum_thalie.tsumugi.preferences.StreamerNotifServiceFragment"
/>
<Preference <Preference
app:key="submitBug" app:key="submitBug"
app:title="Submit a bug (opens GitHub in a web browser)" app:title="Submit a bug (opens GitHub in a web browser)"

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:key="newStreamerNotification"
app:iconSpaceReserved="false"
app:title="New streamer notification (drains battery!)"
app:singleLineTitle="false"
app:defaultValue="false"
/>
<ListPreference
app:iconSpaceReserved="false"
app:key="streamerMonitorPeriodPref"
app:title="Streamer monitor frequency"
android:entries="@array/streamerFetchPeriodString"
android:entryValues="@array/streamerFetchPeriod"
android:defaultValue="10"
/>
</PreferenceScreen>