MainActivity.kt 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. package io.r_a_d.radio2
  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 io.r_a_d.radio2.playerstore.PlayerStore
  14. import java.util.Timer
  15. import android.view.MenuItem
  16. import androidx.preference.PreferenceManager
  17. import com.google.android.material.snackbar.Snackbar
  18. import io.r_a_d.radio2.alarm.RadioAlarm
  19. import io.r_a_d.radio2.streamerNotificationService.WorkerStore
  20. import io.r_a_d.radio2.streamerNotificationService.startStreamerMonitor
  21. import io.r_a_d.radio2.ui.songs.request.Requestor
  22. /* Log to file import
  23. import android.os.Environment
  24. import java.io.File
  25. import java.io.IOException
  26. */
  27. class MainActivity : BaseActivity() {
  28. private val clockTicker: Timer = Timer()
  29. private var currentNavController: LiveData<NavController>? = null
  30. private var isTimerStarted = false
  31. /**
  32. * Called on first creation and when restoring state.
  33. */
  34. private fun setupBottomNavigationBar() {
  35. val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_nav)
  36. //val navGraphIds = listOf(R.navigation.home, R.navigation.list, R.navigation.form)
  37. val navGraphIds = listOf(R.navigation.navigation_nowplaying, R.navigation.navigation_songs,
  38. R.navigation.navigation_news, R.navigation.navigation_chat)
  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. R.id.action_refresh -> {
  67. PlayerStore.instance.queue.clear()
  68. PlayerStore.instance.lp.clear()
  69. PlayerStore.instance.initApi()
  70. Requestor.instance.initFavorites()
  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. R.id.action_settings -> {
  76. val i = Intent(this, ParametersActivity::class.java)
  77. startActivity(i)
  78. true
  79. }
  80. R.id.action_sleep -> {
  81. val i = Intent(this, ParametersActivity::class.java)
  82. i.putExtra("action", ActionOpenParam.SLEEP.name) // TODO change value with Actions.something
  83. startActivity(i)
  84. true
  85. }
  86. R.id.action_alarm -> {
  87. val i = Intent(this, ParametersActivity::class.java)
  88. i.putExtra("action", ActionOpenParam.ALARM.name) // TODO change value with Actions.something
  89. startActivity(i)
  90. true
  91. }
  92. else -> super.onOptionsItemSelected(item)
  93. }
  94. }
  95. override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
  96. super.onRestoreInstanceState(savedInstanceState ?: Bundle())
  97. // Now that BottomNavigationBar has restored its instance state
  98. // and its selectedItemId, we can proceed with setting up the
  99. // BottomNavigationBar with Navigation
  100. setupBottomNavigationBar()
  101. }
  102. override fun onCreate(savedInstanceState: Bundle?) {
  103. super.onCreate(savedInstanceState)
  104. WorkerStore.instance.init(this)
  105. startStreamerMonitor(this) // this checks the preferenceStore before actually starting a service, don't worry.
  106. RadioAlarm.instance.cancelAlarm(c = this)
  107. RadioAlarm.instance.setNextAlarm(c = this) // this checks the preferenceStore before actually setting an alarm, don't worry.
  108. // initialize programmatically accessible colors
  109. colorBlue = ResourcesCompat.getColor(resources, R.color.bluereq, null)
  110. colorWhited = ResourcesCompat.getColor(resources, R.color.whited, null)
  111. colorGreenList = (ResourcesCompat.getColorStateList(resources, R.color.button_green, null))
  112. colorRedList = (ResourcesCompat.getColorStateList(resources, R.color.button_red, null))
  113. colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
  114. 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.
  115. // Post-UI Launch
  116. if (PlayerStore.instance.isInitialized)
  117. {
  118. Log.d(tag, "skipped initialization")
  119. } else {
  120. // if the service is not started, start it in STOP mode.
  121. // 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).
  122. // this is useful since the service must be started to register bluetooth devices buttons.
  123. // (in case someone opens the app then pushes the PLAY button from their bluetooth device)
  124. if(!PlayerStore.instance.isServiceStarted.value!!)
  125. actionOnService(Actions.STOP)
  126. // initialize some API data
  127. PlayerStore.instance.initPicture(this)
  128. PlayerStore.instance.streamerName.value = "" // initializing the streamer name will trigger an initApi from the observer in the Service.
  129. // initialize the favorites
  130. Requestor.instance.initFavorites()
  131. }
  132. if (!isTimerStarted)
  133. {
  134. // timers
  135. // the clockTicker is used to update the UI. It's OK if it dies when the app loses focus.
  136. // the timer is declared after access to PlayerStore so that PlayerStore is already initialized:
  137. // Otherwise it makes the PlayerStore call its init{} block from a background thread --> crash
  138. clockTicker.schedule(
  139. Tick(),
  140. 0,
  141. 500
  142. )
  143. isTimerStarted = true
  144. }
  145. // initialize the UI
  146. setTheme(R.style.AppTheme)
  147. setContentView(R.layout.activity_main)
  148. attachKeyboardListeners()
  149. val toolbar : Toolbar = findViewById(R.id.toolbar)
  150. // before setting up the bottom bar, we must declare the top bar that will be used by the bottom bar to display titles.
  151. setSupportActionBar(toolbar)
  152. if (savedInstanceState == null) {
  153. setupBottomNavigationBar()
  154. } // Else, need to wait for onRestoreInstanceState
  155. }
  156. override fun onDestroy() {
  157. clockTicker.cancel()
  158. super.onDestroy()
  159. }
  160. // ####################################
  161. // ####### SERVICE PLAY / PAUSE #######
  162. // ####################################
  163. private fun actionOnService(a: Actions, v: Int = 0)
  164. {
  165. val i = Intent(this, RadioService::class.java)
  166. i.putExtra("action", a.name)
  167. i.putExtra("value", v)
  168. Log.d(tag, "Sending intent ${a.name}")
  169. startService(i)
  170. }
  171. // ####################################
  172. // ###### SERVICE BINDER MANAGER ######
  173. // ####################################
  174. // NO BINDERS, only intents. That's the magic.
  175. // Avoid code duplication, keep a single entry point to modify the service, and manage the service independently
  176. // (no coupling between service and activity, as it should be ! Cause the notification makes changes too.)
  177. /*
  178. // ####################################
  179. // ####### LOGGING TO FILE ############
  180. // ####################################
  181. // Checks if external storage is available for read and write
  182. private val isExternalStorageWritable: Boolean
  183. get() {
  184. val state = Environment.getExternalStorageState()
  185. return Environment.MEDIA_MOUNTED == state
  186. }
  187. // Checks if external storage is available to at least read
  188. private val isExternalStorageReadable: Boolean
  189. get() {
  190. val state = Environment.getExternalStorageState()
  191. return Environment.MEDIA_MOUNTED == state || Environment.MEDIA_MOUNTED_READ_ONLY == state
  192. }
  193. private fun logToFile()
  194. {
  195. // Logging
  196. when {
  197. isExternalStorageWritable -> {
  198. val appDirectory = Environment.getExternalStorageDirectory()
  199. // File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
  200. val logDirectory = File("$appDirectory/log")
  201. val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
  202. Log.d(
  203. tag,
  204. "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
  205. )
  206. // create app folder
  207. if (!appDirectory.exists()) {
  208. appDirectory.mkdir()
  209. Log.d(tag, "$appDirectory created")
  210. }
  211. // create log folder
  212. if (!logDirectory.exists()) {
  213. logDirectory.mkdir()
  214. Log.d(tag, "$logDirectory created")
  215. }
  216. // clear the previous logcat and then write the new one to the file
  217. try {
  218. Runtime.getRuntime().exec("logcat -c")
  219. Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ")
  220. Log.d(tag, "logcat started")
  221. } catch (e: IOException) {
  222. e.printStackTrace()
  223. }
  224. }
  225. isExternalStorageReadable -> {
  226. // only readable
  227. }
  228. else -> {
  229. // not accessible
  230. }
  231. }
  232. }
  233. */
  234. }