MainActivity.kt 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. package fr.riff_app.riff
  2. import android.os.Bundle
  3. import com.google.android.material.bottomnavigation.BottomNavigationView
  4. import androidx.navigation.ui.setupActionBarWithNavController
  5. import android.content.Intent
  6. import android.util.Log
  7. import android.view.Menu
  8. import androidx.appcompat.widget.Toolbar
  9. import androidx.core.content.res.ResourcesCompat
  10. import androidx.lifecycle.LiveData
  11. import androidx.lifecycle.Observer
  12. import androidx.navigation.NavController
  13. import fr.riff_app.riff.playerstore.PlayerStore
  14. import java.util.Timer
  15. import android.view.MenuItem
  16. import com.google.android.material.snackbar.Snackbar
  17. import fr.riff_app.riff.alarm.RadioAlarm
  18. import fr.riff_app.riff.planning.Planning
  19. /* Log to file import
  20. import android.os.Environment
  21. import java.io.File
  22. import java.io.IOException
  23. */
  24. class MainActivity : BaseActivity() {
  25. private val clockTicker: Timer = Timer()
  26. private var currentNavController: LiveData<NavController>? = null
  27. private var isTimerStarted = false
  28. /**
  29. * Called on first creation and when restoring state.
  30. */
  31. private fun setupBottomNavigationBar() {
  32. val bottomNavigationView : BottomNavigationView = findViewById(R.id.bottom_nav)
  33. //val navGraphIds = listOf(R.navigation.home, R.navigation.list, R.navigation.form)
  34. val navGraphIds = listOf(
  35. R.navigation.navigation_nowplaying,
  36. R.navigation.navigation_songs,
  37. R.navigation.navigation_news,
  38. R.navigation.navigation_programme)
  39. // Setup the bottom navigation view with a list of navigation graphs
  40. val controller = bottomNavigationView.setupWithNavController(
  41. navGraphIds = navGraphIds,
  42. fragmentManager = supportFragmentManager,
  43. containerId = R.id.nav_host_container,
  44. intent = intent
  45. )
  46. // Whenever the selected controller changes, setup the action bar.
  47. controller.observe(this, Observer { navController ->
  48. setupActionBarWithNavController(navController)
  49. })
  50. currentNavController = controller
  51. }
  52. override fun onSupportNavigateUp(): Boolean {
  53. return currentNavController?.value?.navigateUp() ?: false
  54. }
  55. // #####################################
  56. // ######### LIFECYCLE CALLBACKS #######
  57. // #####################################
  58. override fun onCreateOptionsMenu(menu: Menu): Boolean {
  59. // Inflate the menu, this adds items to the action bar if it is present.
  60. menuInflater.inflate(R.menu.toolbar_menu, menu)
  61. return true
  62. }
  63. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  64. // Handle item selection
  65. return when (item.itemId) {
  66. /*
  67. // You can add more actions. This one is used in R/a/dio app Radio2.
  68. R.id.action_refresh -> {
  69. PlayerStore.instance.queue.clear()
  70. PlayerStore.instance.lp.clear()
  71. val s = Snackbar.make(findViewById(R.id.nav_host_container), "Refreshing data..." as CharSequence, Snackbar.LENGTH_LONG)
  72. s.show()
  73. true
  74. }
  75. */
  76. R.id.action_refresh -> {
  77. PlayerStore.instance.queue.clear()
  78. //PlayerStore.instance.lp.clear()
  79. PlayerStore.instance.initApi()
  80. val s = Snackbar.make(findViewById(R.id.nav_host_container), getString(R.string.refreshing) as CharSequence, Snackbar.LENGTH_LONG)
  81. s.show()
  82. true
  83. }
  84. R.id.action_settings -> {
  85. val i = Intent(this, ParametersActivity::class.java)
  86. startActivity(i)
  87. true
  88. }
  89. R.id.action_sleep -> {
  90. val i = Intent(this, ParametersActivity::class.java)
  91. i.putExtra("action", ActionOpenParam.SLEEP.name)
  92. startActivity(i)
  93. true
  94. }
  95. R.id.action_alarm -> {
  96. val i = Intent(this, ParametersActivity::class.java)
  97. i.putExtra("action", ActionOpenParam.ALARM.name) // TODO change value with Actions.something
  98. startActivity(i)
  99. true
  100. }
  101. else -> super.onOptionsItemSelected(item)
  102. }
  103. }
  104. override fun onRestoreInstanceState(savedInstanceState: Bundle) {
  105. super.onRestoreInstanceState(savedInstanceState ?: Bundle())
  106. // Now that BottomNavigationBar has restored its instance state
  107. // and its selectedItemId, we can proceed with setting up the
  108. // BottomNavigationBar with Navigation
  109. setupBottomNavigationBar()
  110. }
  111. override fun onCreate(savedInstanceState: Bundle?) {
  112. super.onCreate(savedInstanceState)
  113. RadioAlarm.instance.cancelAlarm(c = this)
  114. RadioAlarm.instance.setNextAlarm(c = this) // this checks the preferenceStore before actually setting an alarm, don't worry.
  115. // initialize programmatically accessible colors
  116. colorBlue = ResourcesCompat.getColor(resources, R.color.rblue, null)
  117. colorWhited = ResourcesCompat.getColor(resources, R.color.whited, null)
  118. colorGreenList = (ResourcesCompat.getColorStateList(resources, R.color.button_green, null))
  119. colorRedList = (ResourcesCompat.getColorStateList(resources, R.color.button_red, null))
  120. colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
  121. colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null))
  122. // fetch program
  123. Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
  124. PlayerStore.instance.initUrl(this)
  125. PlayerStore.instance.initApi()
  126. // Post-UI Launch
  127. if (PlayerStore.instance.isInitialized)
  128. {
  129. //[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
  130. } else {
  131. // if the service is not started, start it in STOP mode.
  132. // It's not a dummy action : with STOP mode, the player does not buffer audio (and does not use data connection without the user's consent).
  133. // this is useful since the service must be started to register bluetooth devices buttons.
  134. // (in case someone opens the app then pushes the PLAY button from their bluetooth device)
  135. if(!PlayerStore.instance.isServiceStarted.value!!)
  136. actionOnService(Actions.STOP)
  137. // initialize some API data
  138. PlayerStore.instance.initPicture(this)
  139. PlayerStore.instance.streamerName.value = "" // initializing the streamer name will trigger an initApi from the observer in the Service.
  140. }
  141. if (!isTimerStarted)
  142. {
  143. // timers
  144. // the clockTicker is used to update the UI. It's OK if it dies when the app loses focus.
  145. // the timer is declared after access to PlayerStore so that PlayerStore is already initialized:
  146. // Otherwise it makes the PlayerStore call its init{} block from a background thread --> crash
  147. clockTicker.schedule(
  148. Tick(),
  149. 0,
  150. 500
  151. )
  152. isTimerStarted = true
  153. }
  154. // initialize the UI
  155. setTheme(R.style.AppTheme)
  156. setContentView(R.layout.activity_main)
  157. attachKeyboardListeners()
  158. val toolbar : Toolbar = findViewById(R.id.toolbar)
  159. // before setting up the bottom bar, we must declare the top bar that will be used by the bottom bar to display titles.
  160. setSupportActionBar(toolbar)
  161. if (savedInstanceState == null) {
  162. setupBottomNavigationBar()
  163. } // Else, need to wait for onRestoreInstanceState
  164. }
  165. override fun onDestroy() {
  166. clockTicker.cancel()
  167. super.onDestroy()
  168. }
  169. // ####################################
  170. // ####### SERVICE PLAY / PAUSE #######
  171. // ####################################
  172. private fun actionOnService(a: Actions, v: Int = 0)
  173. {
  174. val i = Intent(this, RadioService::class.java)
  175. i.putExtra("action", a.name)
  176. i.putExtra("value", v)
  177. //[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}")
  178. startService(i)
  179. }
  180. // ####################################
  181. // ###### SERVICE BINDER MANAGER ######
  182. // ####################################
  183. // NO BINDERS, only intents. That's the magic.
  184. // Avoid code duplication, keep a single entry point to modify the service, and manage the service independently
  185. // (no coupling between service and activity, as it should be ! Cause the notification makes changes too.)
  186. /*
  187. // ####################################
  188. // ####### LOGGING TO FILE ############
  189. // ####################################
  190. // Checks if external storage is available for read and write
  191. private val isExternalStorageWritable: Boolean
  192. get() {
  193. val state = Environment.getExternalStorageState()
  194. return Environment.MEDIA_MOUNTED == state
  195. }
  196. // Checks if external storage is available to at least read
  197. private val isExternalStorageReadable: Boolean
  198. get() {
  199. val state = Environment.getExternalStorageState()
  200. return Environment.MEDIA_MOUNTED == state || Environment.MEDIA_MOUNTED_READ_ONLY == state
  201. }
  202. private fun logToFile()
  203. {
  204. // Logging
  205. when {
  206. isExternalStorageWritable -> {
  207. val appDirectory = Environment.getExternalStorageDirectory()
  208. // File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
  209. val logDirectory = File("$appDirectory/log")
  210. val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
  211. //[REMOVE LOG CALLS]Log.d(
  212. tag,
  213. "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
  214. )
  215. // create app folder
  216. if (!appDirectory.exists()) {
  217. appDirectory.mkdir()
  218. //[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
  219. }
  220. // create log folder
  221. if (!logDirectory.exists()) {
  222. logDirectory.mkdir()
  223. //[REMOVE LOG CALLS]Log.d(tag, "$logDirectory created")
  224. }
  225. // clear the previous logcat and then write the new one to the file
  226. try {
  227. Runtime.getRuntime().exec("logcat -c")
  228. Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ")
  229. //[REMOVE LOG CALLS]Log.d(tag, "logcat started")
  230. } catch (e: IOException) {
  231. e.printStackTrace()
  232. }
  233. }
  234. isExternalStorageReadable -> {
  235. // only readable
  236. }
  237. else -> {
  238. // not accessible
  239. }
  240. }
  241. }
  242. */
  243. }