MainActivity.kt 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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.bluereq, 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. // Post-UI Launch
  113. if (PlayerStore.instance.isInitialized)
  114. {
  115. Log.d(tag, "skipped initialization")
  116. } else {
  117. // if the service is not started, start it in STOP mode.
  118. // 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).
  119. // this is useful since the service must be started to register bluetooth devices buttons.
  120. // (in case someone opens the app then pushes the PLAY button from their bluetooth device)
  121. if(!PlayerStore.instance.isServiceStarted.value!!)
  122. actionOnService(Actions.STOP)
  123. // initialize some API data
  124. PlayerStore.instance.initPicture(this)
  125. PlayerStore.instance.streamerName.value = "" // initializing the streamer name will trigger an initApi from the observer in the Service.
  126. }
  127. if (!isTimerStarted)
  128. {
  129. // timers
  130. // the clockTicker is used to update the UI. It's OK if it dies when the app loses focus.
  131. // the timer is declared after access to PlayerStore so that PlayerStore is already initialized:
  132. // Otherwise it makes the PlayerStore call its init{} block from a background thread --> crash
  133. clockTicker.schedule(
  134. Tick(),
  135. 0,
  136. 500
  137. )
  138. isTimerStarted = true
  139. }
  140. // fetch program
  141. Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
  142. // initialize the UI
  143. setTheme(R.style.AppTheme)
  144. setContentView(R.layout.activity_main)
  145. attachKeyboardListeners()
  146. val toolbar : Toolbar = findViewById(R.id.toolbar)
  147. // before setting up the bottom bar, we must declare the top bar that will be used by the bottom bar to display titles.
  148. setSupportActionBar(toolbar)
  149. if (savedInstanceState == null) {
  150. setupBottomNavigationBar()
  151. } // Else, need to wait for onRestoreInstanceState
  152. }
  153. override fun onDestroy() {
  154. clockTicker.cancel()
  155. super.onDestroy()
  156. }
  157. // ####################################
  158. // ####### SERVICE PLAY / PAUSE #######
  159. // ####################################
  160. private fun actionOnService(a: Actions, v: Int = 0)
  161. {
  162. val i = Intent(this, RadioService::class.java)
  163. i.putExtra("action", a.name)
  164. i.putExtra("value", v)
  165. Log.d(tag, "Sending intent ${a.name}")
  166. startService(i)
  167. }
  168. // ####################################
  169. // ###### SERVICE BINDER MANAGER ######
  170. // ####################################
  171. // NO BINDERS, only intents. That's the magic.
  172. // Avoid code duplication, keep a single entry point to modify the service, and manage the service independently
  173. // (no coupling between service and activity, as it should be ! Cause the notification makes changes too.)
  174. /*
  175. // ####################################
  176. // ####### LOGGING TO FILE ############
  177. // ####################################
  178. // Checks if external storage is available for read and write
  179. private val isExternalStorageWritable: Boolean
  180. get() {
  181. val state = Environment.getExternalStorageState()
  182. return Environment.MEDIA_MOUNTED == state
  183. }
  184. // Checks if external storage is available to at least read
  185. private val isExternalStorageReadable: Boolean
  186. get() {
  187. val state = Environment.getExternalStorageState()
  188. return Environment.MEDIA_MOUNTED == state || Environment.MEDIA_MOUNTED_READ_ONLY == state
  189. }
  190. private fun logToFile()
  191. {
  192. // Logging
  193. when {
  194. isExternalStorageWritable -> {
  195. val appDirectory = Environment.getExternalStorageDirectory()
  196. // File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
  197. val logDirectory = File("$appDirectory/log")
  198. val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
  199. Log.d(
  200. tag,
  201. "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
  202. )
  203. // create app folder
  204. if (!appDirectory.exists()) {
  205. appDirectory.mkdir()
  206. Log.d(tag, "$appDirectory created")
  207. }
  208. // create log folder
  209. if (!logDirectory.exists()) {
  210. logDirectory.mkdir()
  211. Log.d(tag, "$logDirectory created")
  212. }
  213. // clear the previous logcat and then write the new one to the file
  214. try {
  215. Runtime.getRuntime().exec("logcat -c")
  216. Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ")
  217. Log.d(tag, "logcat started")
  218. } catch (e: IOException) {
  219. e.printStackTrace()
  220. }
  221. }
  222. isExternalStorageReadable -> {
  223. // only readable
  224. }
  225. else -> {
  226. // not accessible
  227. }
  228. }
  229. }
  230. */
  231. }