/** * Teapod * * Copyright 2020-2021 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ package org.mosad.teapod.activity.main import android.content.Intent import android.os.Bundle import android.util.Log import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.commit import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.callbacks.onDismiss import com.google.android.material.bottomnavigation.BottomNavigationView import kotlinx.coroutines.joinAll import kotlinx.coroutines.runBlocking import org.mosad.teapod.R import org.mosad.teapod.databinding.ActivityMainBinding import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.activity.player.PlayerActivity import org.mosad.teapod.preferences.EncryptedPreferences import org.mosad.teapod.preferences.Preferences import org.mosad.teapod.ui.components.LoginDialog import org.mosad.teapod.activity.main.fragments.AccountFragment import org.mosad.teapod.activity.main.fragments.HomeFragment import org.mosad.teapod.activity.main.fragments.LibraryFragment import org.mosad.teapod.activity.main.fragments.SearchFragment import org.mosad.teapod.activity.onboarding.OnboardingActivity import org.mosad.teapod.util.DataTypes import org.mosad.teapod.util.StorageController import java.net.SocketTimeoutException import kotlin.system.exitProcess import kotlin.system.measureTimeMillis class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener { private lateinit var binding: ActivityMainBinding private var activeBaseFragment: Fragment = HomeFragment() // the currently active fragment, home at the start companion object { var wasInitialized = false } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (!wasInitialized) { load() } theme.applyStyle(getThemeResource(), true) binding = ActivityMainBinding.inflate(layoutInflater) binding.navView.setOnNavigationItemSelectedListener(this) setContentView(binding.root) supportFragmentManager.commit { replace(R.id.nav_host_fragment, activeBaseFragment, activeBaseFragment.javaClass.simpleName) } } override fun onBackPressed() { if (supportFragmentManager.backStackEntryCount > 0) { supportFragmentManager.popBackStack() } else { if (activeBaseFragment !is HomeFragment) { binding.navView.selectedItemId = R.id.navigation_home } else { super.onBackPressed() } } } override fun onNavigationItemSelected(item: MenuItem): Boolean { if (supportFragmentManager.backStackEntryCount > 0) { supportFragmentManager.popBackStack() } val ret = when (item.itemId) { R.id.navigation_home -> { activeBaseFragment = HomeFragment() true } R.id.navigation_library -> { activeBaseFragment = LibraryFragment() true } R.id.navigation_search -> { activeBaseFragment = SearchFragment() true } R.id.navigation_account -> { activeBaseFragment = AccountFragment() true } else -> false } supportFragmentManager.commit { replace(R.id.nav_host_fragment, activeBaseFragment, activeBaseFragment.javaClass.simpleName) } return ret } private fun getThemeResource(): Int { return when (Preferences.theme) { DataTypes.Theme.LIGHT -> R.style.AppTheme_Light else -> R.style.AppTheme_Dark } } /** * initial loading and login are run in parallel, as initial loading doesn't require * any login cookies */ private fun load() { val time = measureTimeMillis { val loadingJob = AoDParser.initialLoading() // start the initial loading // load all saved stuff here Preferences.load(this) EncryptedPreferences.readCredentials(this) StorageController.load(this) // show onbaording if (EncryptedPreferences.password.isEmpty()) { showOnboarding() } else { try { if (!AoDParser.login()) { showLoginDialog() } } catch (ex: SocketTimeoutException) { Log.w(javaClass.name, "Timeout during login!") // show waring dialog before finishing MaterialDialog(this).show { title(R.string.dialog_timeout_head) message(R.string.dialog_timeout_desc) onDismiss { exitAndRemoveTask() } } } } runBlocking { loadingJob.joinAll() } // wait for initial loading to finish } Log.i(javaClass.name, "loading and login in $time ms") wasInitialized = true } private fun showLoginDialog() { LoginDialog(this, false).positiveButton { EncryptedPreferences.saveCredentials(login, password, context) if (!AoDParser.login()) { showLoginDialog() Log.w(javaClass.name, "Login failed, please try again.") } }.negativeButton { Log.i(javaClass.name, "Login canceled, exiting.") finish() }.show() } /** * start the onboarding activity and finish the main activity */ private fun showOnboarding() { startActivity(Intent(this, OnboardingActivity::class.java)) finish() } /** * Show a fragment on top of the current fragment. * The current fragment is replaced and the new one is added * to the back stack. */ fun showFragment(fragment: Fragment) { supportFragmentManager.commit { replace(R.id.nav_host_fragment, fragment, fragment.javaClass.simpleName) addToBackStack(fragment.javaClass.name) show(fragment) } } /** * start the player as new activity */ fun startPlayer(mediaId: Int, episodeId: Int) { val intent = Intent(this, PlayerActivity::class.java).apply { putExtra(getString(R.string.intent_media_id), mediaId) putExtra(getString(R.string.intent_episode_id), episodeId) } startActivity(intent) } /** * use custom restart instead of recreate(), since it has animations */ fun restart() { val restartIntent = intent restartIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) finish() startActivity(restartIntent) } /** * exit and remove the app from tasks */ fun exitAndRemoveTask() { this.finishAndRemoveTask() exitProcess(0) } }