MainActivity.kt 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package fr.forum_thalie.tsumugi
  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.forum_thalie.tsumugi.playerstore.PlayerStore
  14. import java.util.Timer
  15. import android.view.MenuItem
  16. import fr.forum_thalie.tsumugi.alarm.RadioAlarm
  17. import fr.forum_thalie.tsumugi.planning.Planning
  18. /* Log to file import
  19. import android.os.Environment
  20. import java.io.File
  21. import java.io.IOException
  22. */
  23. class MainActivity : BaseActivity() {
  24. private val clockTicker: Timer = Timer()
  25. private var currentNavController: LiveData<NavController>? = null
  26. private var isTimerStarted = false
  27. /**
  28. * Called on first creation and when restoring state.
  29. */
  30. private fun setupBottomNavigationBar() {
  31. val bottomNavigationView : BottomNavigationView = findViewById(R.id.bottom_nav)
  32. //val navGraphIds = listOf(R.navigation.home, R.navigation.list, R.navigation.form)
  33. val navGraphIds = listOf(
  34. R.navigation.navigation_nowplaying,
  35. R.navigation.navigation_songs,
  36. R.navigation.navigation_news,
  37. R.navigation.navigation_programme)
  38. // Setup the bottom navigation view with a list of navigation graphs
  39. val controller = bottomNavigationView.setupWithNavController(
  40. navGraphIds = navGraphIds,
  41. fragmentManager = supportFragmentManager,
  42. containerId = R.id.nav_host_container,
  43. intent = intent
  44. )
  45. // Whenever the selected controller changes, setup the action bar.
  46. controller.observe(this, Observer { navController ->
  47. setupActionBarWithNavController(navController)
  48. })
  49. currentNavController = controller
  50. }
  51. override fun onSupportNavigateUp(): Boolean {
  52. return currentNavController?.value?.navigateUp() ?: false
  53. }
  54. // #####################################
  55. // ######### LIFECYCLE CALLBACKS #######
  56. // #####################################
  57. override fun onCreateOptionsMenu(menu: Menu): Boolean {
  58. // Inflate the menu, this adds items to the action bar if it is present.
  59. menuInflater.inflate(R.menu.toolbar_menu, menu)
  60. return true
  61. }
  62. override fun onOptionsItemSelected(item: MenuItem): Boolean {
  63. // Handle item selection
  64. return when (item.itemId) {
  65. /*
  66. // You can add more actions. This one is used in R/a/dio app Radio2.
  67. R.id.action_refresh -> {
  68. PlayerStore.instance.queue.clear()
  69. PlayerStore.instance.lp.clear()
  70. val s = Snackbar.make(findViewById(R.id.nav_host_container), "Refreshing data..." as CharSequence, Snackbar.LENGTH_LONG)
  71. s.show()
  72. true
  73. }
  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)
  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. RadioAlarm.instance.cancelAlarm(c = this)
  105. RadioAlarm.instance.setNextAlarm(c = this) // this checks the preferenceStore before actually setting an alarm, don't worry.
  106. // initialize programmatically accessible colors
  107. colorBlue = ResourcesCompat.getColor(resources, R.color.rblue, null)
  108. colorWhited = ResourcesCompat.getColor(resources, R.color.whited, null)
  109. colorGreenList = (ResourcesCompat.getColorStateList(resources, R.color.button_green, null))
  110. colorRedList = (ResourcesCompat.getColorStateList(resources, R.color.button_red, null))
  111. colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
  112. colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null))
  113. // fetch program
  114. Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
  115. PlayerStore.instance.initUrl(this)
  116. PlayerStore.instance.initApi()
  117. // Post-UI Launch
  118. if (PlayerStore.instance.isInitialized)
  119. {
  120. //[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
  121. } else {
  122. // if the service is not started, start it in STOP mode.
  123. // 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).
  124. // this is useful since the service must be started to register bluetooth devices buttons.
  125. // (in case someone opens the app then pushes the PLAY button from their bluetooth device)
  126. if(!PlayerStore.instance.isServiceStarted.value!!)
  127. actionOnService(Actions.STOP)
  128. // initialize some API data
  129. PlayerStore.instance.initPicture(this)
  130. PlayerStore.instance.streamerName.value = "" // initializing the streamer name will trigger an initApi from the observer in the Service.
  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. //[REMOVE LOG CALLS]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. //[REMOVE LOG CALLS]Log.d(
  203. tag,
  204. "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
  205. )
  206. // create app folder
  207. if (!appDirectory.exists()) {
  208. appDirectory.mkdir()
  209. //[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
  210. }
  211. // create log folder
  212. if (!logDirectory.exists()) {
  213. logDirectory.mkdir()
  214. //[REMOVE LOG CALLS]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. //[REMOVE LOG CALLS]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. }