123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- package fr.forum_thalie.tsumugi
-
- import android.os.Bundle
- import com.google.android.material.bottomnavigation.BottomNavigationView
- import androidx.navigation.ui.setupActionBarWithNavController
- import android.content.Intent
- import android.util.Log
- import android.view.Menu
- import androidx.appcompat.widget.Toolbar
- import androidx.core.content.res.ResourcesCompat
- import androidx.lifecycle.LiveData
- import androidx.lifecycle.Observer
- import androidx.navigation.NavController
- import fr.forum_thalie.tsumugi.playerstore.PlayerStore
-
- import java.util.Timer
- import android.view.MenuItem
- import fr.forum_thalie.tsumugi.alarm.RadioAlarm
- import fr.forum_thalie.tsumugi.planning.Planning
-
-
- /* Log to file import
- import android.os.Environment
- import java.io.File
- import java.io.IOException
- */
-
- class MainActivity : BaseActivity() {
-
- private val clockTicker: Timer = Timer()
- private var currentNavController: LiveData<NavController>? = null
- private var isTimerStarted = false
-
- /**
- * Called on first creation and when restoring state.
- */
- private fun setupBottomNavigationBar() {
- 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.navigation_nowplaying,
- R.navigation.navigation_songs,
- R.navigation.navigation_news,
- R.navigation.navigation_programme)
-
- // Setup the bottom navigation view with a list of navigation graphs
- val controller = bottomNavigationView.setupWithNavController(
- navGraphIds = navGraphIds,
- fragmentManager = supportFragmentManager,
- containerId = R.id.nav_host_container,
- intent = intent
- )
-
- // Whenever the selected controller changes, setup the action bar.
- controller.observe(this, Observer { navController ->
- setupActionBarWithNavController(navController)
- })
- currentNavController = controller
- }
-
- override fun onSupportNavigateUp(): Boolean {
- return currentNavController?.value?.navigateUp() ?: false
- }
-
- // #####################################
- // ######### LIFECYCLE CALLBACKS #######
- // #####################################
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- // Inflate the menu, this adds items to the action bar if it is present.
- menuInflater.inflate(R.menu.toolbar_menu, menu)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- // Handle item selection
- return when (item.itemId) {
- /*
- // You can add more actions. This one is used in R/a/dio app Radio2.
- R.id.action_refresh -> {
- PlayerStore.instance.queue.clear()
- PlayerStore.instance.lp.clear()
- val s = Snackbar.make(findViewById(R.id.nav_host_container), "Refreshing data..." as CharSequence, Snackbar.LENGTH_LONG)
- s.show()
- true
- }
- */
- R.id.action_settings -> {
- val i = Intent(this, ParametersActivity::class.java)
- startActivity(i)
- true
- }
- R.id.action_sleep -> {
- val i = Intent(this, ParametersActivity::class.java)
- i.putExtra("action", ActionOpenParam.SLEEP.name)
- startActivity(i)
- true
- }
- R.id.action_alarm -> {
- val i = Intent(this, ParametersActivity::class.java)
- i.putExtra("action", ActionOpenParam.ALARM.name) // TODO change value with Actions.something
- startActivity(i)
- true
- }
-
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
- super.onRestoreInstanceState(savedInstanceState ?: Bundle())
- // Now that BottomNavigationBar has restored its instance state
- // and its selectedItemId, we can proceed with setting up the
- // BottomNavigationBar with Navigation
- setupBottomNavigationBar()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- RadioAlarm.instance.cancelAlarm(c = this)
- RadioAlarm.instance.setNextAlarm(c = this) // this checks the preferenceStore before actually setting an alarm, don't worry.
-
- // initialize programmatically accessible colors
- colorBlue = ResourcesCompat.getColor(resources, R.color.rblue, null)
- colorWhited = ResourcesCompat.getColor(resources, R.color.whited, null)
- colorGreenList = (ResourcesCompat.getColorStateList(resources, R.color.button_green, null))
- colorRedList = (ResourcesCompat.getColorStateList(resources, R.color.button_red, null))
- colorGreenListCompat = (ResourcesCompat.getColorStateList(resources, R.color.button_green_compat, null))
- colorAccent = (ResourcesCompat.getColor(resources, R.color.colorAccent, null))
-
- // fetch program
- Planning.instance.parseUrl(/* getString(R.string.planning_url) */ context = this)
-
- PlayerStore.instance.initUrl(this)
- PlayerStore.instance.initApi()
-
- // Post-UI Launch
- if (PlayerStore.instance.isInitialized)
- {
- //[REMOVE LOG CALLS]Log.d(tag, "skipped initialization")
- } else {
- // if the service is not started, start it in STOP mode.
- // 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).
- // this is useful since the service must be started to register bluetooth devices buttons.
- // (in case someone opens the app then pushes the PLAY button from their bluetooth device)
- if(!PlayerStore.instance.isServiceStarted.value!!)
- actionOnService(Actions.STOP)
-
- // initialize some API data
- PlayerStore.instance.initPicture(this)
- PlayerStore.instance.streamerName.value = "" // initializing the streamer name will trigger an initApi from the observer in the Service.
- }
-
- if (!isTimerStarted)
- {
- // timers
- // the clockTicker is used to update the UI. It's OK if it dies when the app loses focus.
- // the timer is declared after access to PlayerStore so that PlayerStore is already initialized:
- // Otherwise it makes the PlayerStore call its init{} block from a background thread --> crash
- clockTicker.schedule(
- Tick(),
- 0,
- 500
- )
- isTimerStarted = true
- }
-
- // initialize the UI
- setTheme(R.style.AppTheme)
- setContentView(R.layout.activity_main)
- attachKeyboardListeners()
-
- val toolbar : Toolbar = findViewById(R.id.toolbar)
-
- // before setting up the bottom bar, we must declare the top bar that will be used by the bottom bar to display titles.
- setSupportActionBar(toolbar)
-
- if (savedInstanceState == null) {
- setupBottomNavigationBar()
- } // Else, need to wait for onRestoreInstanceState
- }
-
- override fun onDestroy() {
- clockTicker.cancel()
- super.onDestroy()
- }
-
- // ####################################
- // ####### SERVICE PLAY / PAUSE #######
- // ####################################
-
-
- private fun actionOnService(a: Actions, v: Int = 0)
- {
- val i = Intent(this, RadioService::class.java)
- i.putExtra("action", a.name)
- i.putExtra("value", v)
- //[REMOVE LOG CALLS]Log.d(tag, "Sending intent ${a.name}")
- startService(i)
- }
-
-
- // ####################################
- // ###### SERVICE BINDER MANAGER ######
- // ####################################
-
- // NO BINDERS, only intents. That's the magic.
- // Avoid code duplication, keep a single entry point to modify the service, and manage the service independently
- // (no coupling between service and activity, as it should be ! Cause the notification makes changes too.)
-
-
- /*
- // ####################################
- // ####### LOGGING TO FILE ############
- // ####################################
-
- // Checks if external storage is available for read and write
- private val isExternalStorageWritable: Boolean
- get() {
- val state = Environment.getExternalStorageState()
- return Environment.MEDIA_MOUNTED == state
- }
-
- // Checks if external storage is available to at least read
- private val isExternalStorageReadable: Boolean
- get() {
- val state = Environment.getExternalStorageState()
- return Environment.MEDIA_MOUNTED == state || Environment.MEDIA_MOUNTED_READ_ONLY == state
- }
-
-
- private fun logToFile()
- {
-
- // Logging
- when {
- isExternalStorageWritable -> {
-
- val appDirectory = Environment.getExternalStorageDirectory()
- // File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/MyPersonalAppFolder")
- val logDirectory = File("$appDirectory/log")
- val logFile = File(logDirectory, "logcat" + System.currentTimeMillis() + ".txt")
- //[REMOVE LOG CALLS]Log.d(
- tag,
- "appDirectory : $appDirectory, logDirectory : $logDirectory, logFile : $logFile"
- )
-
- // create app folder
- if (!appDirectory.exists()) {
- appDirectory.mkdir()
- //[REMOVE LOG CALLS]Log.d(tag, "$appDirectory created")
- }
-
- // create log folder
- if (!logDirectory.exists()) {
- logDirectory.mkdir()
- //[REMOVE LOG CALLS]Log.d(tag, "$logDirectory created")
- }
-
- // clear the previous logcat and then write the new one to the file
- try {
- Runtime.getRuntime().exec("logcat -c")
- Runtime.getRuntime().exec("logcat -v time -f $logFile *:E $tag:V ")
- //[REMOVE LOG CALLS]Log.d(tag, "logcat started")
- } catch (e: IOException) {
- e.printStackTrace()
- }
-
- }
- isExternalStorageReadable -> {
- // only readable
- }
- else -> {
- // not accessible
- }
- }
- }
-
- */
-
- }
|