From e5037cf9ac83836a77bd212a2304f289d75a26b4 Mon Sep 17 00:00:00 2001 From: Jannik Date: Wed, 6 Jan 2021 15:07:31 +0100 Subject: [PATCH 01/28] add pip support to player activity --- app/src/main/AndroidManifest.xml | 13 ++-- .../org/mosad/teapod/player/PlayerActivity.kt | 69 +++++++++++++++++-- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1a62b38..ab96abf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,16 +21,19 @@ - + + \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt b/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt index d5b76e9..5c328d2 100644 --- a/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt +++ b/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt @@ -3,11 +3,16 @@ package org.mosad.teapod.player import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.annotation.SuppressLint +import android.app.PictureInPictureParams +import android.content.pm.PackageManager +import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.util.Log +import android.util.Rational import android.view.* import androidx.activity.viewModels +import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.view.GestureDetectorCompat import androidx.core.view.isVisible @@ -73,6 +78,11 @@ class PlayerActivity : AppCompatActivity() { initActions() } + /** + * once minimum is android 7.0 this can be simplified + * only onStart and onStop should be needed then + * see: https://developer.android.com/guide/topics/ui/picture-in-picture#continuing_playback + */ override fun onStart() { super.onStart() if (Util.SDK_INT > 23) { @@ -83,6 +93,8 @@ class PlayerActivity : AppCompatActivity() { override fun onResume() { super.onResume() + if (isInPiPMode()) { return } + if (Util.SDK_INT <= 23) { initPlayer() video_view?.onResume() @@ -91,6 +103,8 @@ class PlayerActivity : AppCompatActivity() { override fun onPause() { super.onPause() + if (isInPiPMode()) { return } + if (Util.SDK_INT <= 23) { video_view?.onPause() releasePlayer() @@ -112,6 +126,38 @@ class PlayerActivity : AppCompatActivity() { super.onSaveInstanceState(outState) } + /** + * previous to android n, don't override + */ + @RequiresApi(Build.VERSION_CODES.N) + override fun onUserLeaveHint() { + super.onUserLeaveHint() + + // start pip mode, if supported + if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + @Suppress("deprecation") + enterPictureInPictureMode() + } else { + val width = model.player.videoFormat?.width ?: 0 + val height = model.player.videoFormat?.height ?: 0 + val params = PictureInPictureParams.Builder() + .setAspectRatio(Rational(width, height)) + .build() + enterPictureInPictureMode(params) + } + } + } + + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) + + // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode. + if (isInPictureInPictureMode) { + controller.hideImmediately() + } + } + private fun initPlayer() { if (model.media.id < 0) { Log.e(javaClass.name, "No media was set.") @@ -178,7 +224,9 @@ class PlayerActivity : AppCompatActivity() { } private fun initActions() { - exo_close_player.setOnClickListener { this.finish() } + exo_close_player.setOnClickListener { + this.finish() + } rwd_10.setOnButtonClickListener { rewind() } ffwd_10.setOnButtonClickListener { fastForward() } button_next_ep.setOnClickListener { playNextEpisode() } @@ -213,8 +261,8 @@ class PlayerActivity : AppCompatActivity() { } if (remainingTime in 1..20000) { - // if the next ep button is not visible, make it visible - if (!btnNextEpIsVisible && model.nextEpisode != null && Preferences.autoplay) { + // if the next ep button is not visible, make it visible. Don't show in pip mode + if (!btnNextEpIsVisible && model.nextEpisode != null && Preferences.autoplay && !isInPiPMode()) { withContext(Dispatchers.Main) { showButtonNextEp() } } } else if (btnNextEpIsVisible) { @@ -229,7 +277,7 @@ class PlayerActivity : AppCompatActivity() { } } - private fun releasePlayer(){ + private fun releasePlayer() { playbackPosition = model.player.currentPosition currentWindow = model.player.currentWindowIndex playWhenReady = model.player.playWhenReady @@ -378,6 +426,14 @@ class PlayerActivity : AppCompatActivity() { pauseAndHideControls() } + private fun isInPiPMode(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + isInPictureInPictureMode + } else { + false // pip mode not supported + } + } + /** * pause playback and hide controls */ @@ -393,7 +449,10 @@ class PlayerActivity : AppCompatActivity() { * on single tap hide or show the controls */ override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { - if (controller.isVisible) controller.hide() else controller.show() + if (!isInPiPMode()) { + if (controller.isVisible) controller.hide() else controller.show() + } + return true } -- 2.44.0 From 86e07ba2cf9c3bf900659da676cad0984afe0c06 Mon Sep 17 00:00:00 2001 From: Jannik Date: Fri, 8 Jan 2021 09:31:50 +0100 Subject: [PATCH 02/28] return to main activity if pip was launched before --- app/src/main/AndroidManifest.xml | 3 ++ .../org/mosad/teapod/player/PlayerActivity.kt | 31 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ab96abf..26084de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,6 +32,9 @@ android:label="@string/app_name" android:theme="@style/PlayerTheme" android:supportsPictureInPicture="true" + android:launchMode="singleTask" + android:excludeFromRecents="true" + android:taskAffinity=".player.PlayerActivity" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection"> diff --git a/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt b/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt index 5c328d2..5f244b2 100644 --- a/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt +++ b/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt @@ -3,7 +3,10 @@ package org.mosad.teapod.player import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.annotation.SuppressLint +import android.app.ActivityManager import android.app.PictureInPictureParams +import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration import android.os.Build @@ -35,6 +38,7 @@ import java.util.* import java.util.concurrent.TimeUnit import kotlin.concurrent.scheduleAtFixedRate + class PlayerActivity : AppCompatActivity() { private val model: PlayerViewModel by viewModels() @@ -43,6 +47,7 @@ class PlayerActivity : AppCompatActivity() { private lateinit var gestureDetector: GestureDetectorCompat private lateinit var timerUpdates: TimerTask + private var wasInPIP = false private var playWhenReady = true private var currentWindow = 0 private var playbackPosition: Long = 0 @@ -117,6 +122,10 @@ class PlayerActivity : AppCompatActivity() { video_view?.onPause() releasePlayer() } + + if (wasInPIP) { + navToLauncherTask() + } } override fun onSaveInstanceState(outState: Bundle) { @@ -146,10 +155,15 @@ class PlayerActivity : AppCompatActivity() { .build() enterPictureInPictureMode(params) } + + wasInPIP = isInPiPMode() } } - override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) { + override fun onPictureInPictureModeChanged( + isInPictureInPictureMode: Boolean, + newConfig: Configuration? + ) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode. @@ -434,6 +448,21 @@ class PlayerActivity : AppCompatActivity() { } } + /** + * Bring up launcher task to front + */ + private fun navToLauncherTask() { + val activityManager = (this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) + activityManager.appTasks.forEach { task -> + val baseIntent = task.taskInfo.baseIntent + val categories = baseIntent.categories + if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) { + task.moveToFront() + return + } + } + } + /** * pause playback and hide controls */ -- 2.44.0 From 8e8db386a0f5de6448d22711c3b331d4db0abb84 Mon Sep 17 00:00:00 2001 From: Jannik Date: Fri, 8 Jan 2021 10:58:24 +0100 Subject: [PATCH 03/28] play new media if selected while player is in pip & minor code clean up move some player avtivity stuff to ActivityUtils --- app/src/main/AndroidManifest.xml | 13 ++- .../java/org/mosad/teapod/MainActivity.kt | 3 + .../org/mosad/teapod/player/PlayerActivity.kt | 87 ++++++++----------- .../org/mosad/teapod/util/ActivityUtils.kt | 57 ++++++++++++ 4 files changed, 100 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/org/mosad/teapod/util/ActivityUtils.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 26084de..098b551 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,15 +28,14 @@ - + android:theme="@style/PlayerTheme" /> \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/MainActivity.kt b/app/src/main/java/org/mosad/teapod/MainActivity.kt index 5e74c0a..86e9f2e 100644 --- a/app/src/main/java/org/mosad/teapod/MainActivity.kt +++ b/app/src/main/java/org/mosad/teapod/MainActivity.kt @@ -165,6 +165,9 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS } } + /** + * 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) diff --git a/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt b/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt index 5f244b2..0b82c0d 100644 --- a/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt +++ b/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt @@ -3,9 +3,7 @@ package org.mosad.teapod.player import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.annotation.SuppressLint -import android.app.ActivityManager import android.app.PictureInPictureParams -import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration @@ -13,7 +11,9 @@ import android.os.Build import android.os.Bundle import android.util.Log import android.util.Rational -import android.view.* +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity @@ -34,11 +34,13 @@ import org.mosad.teapod.preferences.Preferences import org.mosad.teapod.ui.components.EpisodesListPlayer import org.mosad.teapod.ui.components.LanguageSettingsPlayer import org.mosad.teapod.util.DataTypes +import org.mosad.teapod.util.hideBars +import org.mosad.teapod.util.isInPiPMode +import org.mosad.teapod.util.navToLauncherTask import java.util.* import java.util.concurrent.TimeUnit import kotlin.concurrent.scheduleAtFixedRate - class PlayerActivity : AppCompatActivity() { private val model: PlayerViewModel by viewModels() @@ -47,7 +49,7 @@ class PlayerActivity : AppCompatActivity() { private lateinit var gestureDetector: GestureDetectorCompat private lateinit var timerUpdates: TimerTask - private var wasInPIP = false + private var wasInPiP = false private var playWhenReady = true private var currentWindow = 0 private var playbackPosition: Long = 0 @@ -123,9 +125,8 @@ class PlayerActivity : AppCompatActivity() { releasePlayer() } - if (wasInPIP) { - navToLauncherTask() - } + // if the player was in pip, it's on a different task + if (wasInPiP) { navToLauncherTask() } } override fun onSaveInstanceState(outState: Bundle) { @@ -135,6 +136,22 @@ class PlayerActivity : AppCompatActivity() { super.onSaveInstanceState(outState) } + /** + * used, when the player is in pip and the user selects a new media + */ + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + + // when the intent changed, lead the new media and play it + intent?.let { + model.loadMedia( + it.getIntExtra(getString(R.string.intent_media_id), 0), + it.getIntExtra(getString(R.string.intent_episode_id), 0) + ) + model.playEpisode(model.currentEpisode, replace = true) + } + } + /** * previous to android n, don't override */ @@ -156,7 +173,7 @@ class PlayerActivity : AppCompatActivity() { enterPictureInPictureMode(params) } - wasInPIP = isInPiPMode() + wasInPiP = isInPiPMode() } } @@ -322,11 +339,19 @@ class PlayerActivity : AppCompatActivity() { private fun onMediaChanged() { exo_text_title.text = model.getMediaTitle() + // hide the next ep button, if there is none button_next_ep_c.visibility = if (model.nextEpisode == null) { View.GONE } else { View.VISIBLE } + + // hide the episodes button, if the media type changed + button_episodes.visibility = if (model.media.type == DataTypes.MediaType.MOVIE) { + View.GONE + } else { + View.VISIBLE + } } /** @@ -374,27 +399,6 @@ class PlayerActivity : AppCompatActivity() { hideButtonNextEp() } - /** - * hide the status and navigation bar - */ - private fun hideBars() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - window.setDecorFitsSystemWindows(false) - window.insetsController?.apply { - hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) - systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE - } - } else { - @Suppress("deprecation") - window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE - or View.SYSTEM_UI_FLAG_LAYOUT_STABLE - or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_FULLSCREEN) - } - } - /** * show the next episode button * TODO improve the show animation @@ -440,29 +444,6 @@ class PlayerActivity : AppCompatActivity() { pauseAndHideControls() } - private fun isInPiPMode(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - isInPictureInPictureMode - } else { - false // pip mode not supported - } - } - - /** - * Bring up launcher task to front - */ - private fun navToLauncherTask() { - val activityManager = (this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) - activityManager.appTasks.forEach { task -> - val baseIntent = task.taskInfo.baseIntent - val categories = baseIntent.categories - if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) { - task.moveToFront() - return - } - } - } - /** * pause playback and hide controls */ diff --git a/app/src/main/java/org/mosad/teapod/util/ActivityUtils.kt b/app/src/main/java/org/mosad/teapod/util/ActivityUtils.kt new file mode 100644 index 0000000..6d3af80 --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/util/ActivityUtils.kt @@ -0,0 +1,57 @@ +package org.mosad.teapod.util + +import android.app.Activity +import android.app.ActivityManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.view.View +import android.view.WindowInsets +import android.view.WindowInsetsController + +/** + * hide the status and navigation bar + */ +fun Activity.hideBars() { + window.apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setDecorFitsSystemWindows(false) + insetsController?.apply { + hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) + systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE + } + } else { + @Suppress("deprecation") + decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE + or View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_FULLSCREEN) + } + } +} + +fun Activity.isInPiPMode(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + isInPictureInPictureMode + } else { + false // pip mode not supported + } +} + +/** + * Bring up launcher task to front + */ +fun Activity.navToLauncherTask() { + val activityManager = (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) + activityManager.appTasks.forEach { task -> + val baseIntent = task.taskInfo.baseIntent + val categories = baseIntent.categories + if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) { + task.moveToFront() + return + } + } +} + -- 2.44.0 From 7a95304ee13d81807dfd93dc6b4d5d464c399b33 Mon Sep 17 00:00:00 2001 From: Jannik Date: Mon, 11 Jan 2021 22:12:21 +0100 Subject: [PATCH 04/28] fix episodes not showing, if scroll is not needed in MediaFragment --- .../java/org/mosad/teapod/ui/fragments/MediaFragment.kt | 5 ----- .../org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt | 3 +++ app/src/main/res/layout/fragment_media.xml | 6 ++++-- app/src/main/res/values/strings.xml | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt b/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt index aeb6fa4..ca9373d 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt @@ -8,8 +8,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import jp.wasabeef.glide.transformations.BlurTransformation @@ -26,7 +24,6 @@ class MediaFragment(private val mediaId: Int) : Fragment() { private lateinit var binding: FragmentMediaBinding private lateinit var adapterRecEpisodes: EpisodeItemAdapter - private lateinit var viewManager: RecyclerView.LayoutManager private lateinit var media: Media private lateinit var tmdb: TMDBResponse @@ -95,8 +92,6 @@ class MediaFragment(private val mediaId: Int) : Fragment() { // specific gui if (media.type == MediaType.TVSHOW) { adapterRecEpisodes = EpisodeItemAdapter(media.episodes) - viewManager = LinearLayoutManager(context) - recyclerEpisodes.layoutManager = viewManager recyclerEpisodes.adapter = adapterRecEpisodes binding.textEpisodesOrRuntime.text = getString(R.string.text_episodes_count, media.info.episodesCount) diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt index 9438dc8..95be799 100644 --- a/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt +++ b/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt @@ -1,5 +1,7 @@ package org.mosad.teapod.util.adapter +import android.graphics.Color +import android.graphics.drawable.ColorDrawable import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.content.ContextCompat @@ -34,6 +36,7 @@ class EpisodeItemAdapter(private val episodes: List) : RecyclerView.Ada if (episodes[position].posterUrl.isNotEmpty()) { Glide.with(context).load(ep.posterUrl) + .apply(RequestOptions.placeholderOf(ColorDrawable(Color.DKGRAY))) .apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(10, 0))) .into(holder.binding.imageEpisode) } diff --git a/app/src/main/res/layout/fragment_media.xml b/app/src/main/res/layout/fragment_media.xml index 054913f..1d6bf07 100644 --- a/app/src/main/res/layout/fragment_media.xml +++ b/app/src/main/res/layout/fragment_media.xml @@ -9,8 +9,7 @@ + android:layout_height="match_parent"> @@ -155,6 +155,8 @@ android:layout_marginStart="7dp" android:layout_marginTop="17dp" android:layout_marginEnd="7dp" + android:nestedScrollingEnabled="false" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:layout_editor_absoluteY="298dp" tools:listitem="@layout/item_episode" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 551fa05..4e0b71d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,6 +15,7 @@ Search for movies and series poster + poster backdrop Play -- 2.44.0 From 7dc120ccfebd40a7713ae4b7366cf9c00a4e313b Mon Sep 17 00:00:00 2001 From: Jannik Date: Wed, 13 Jan 2021 14:24:12 +0100 Subject: [PATCH 05/28] =?UTF-8?q?=E2=80=9EREADME.md=E2=80=9C=20=C3=A4ndern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1af88c..832a4e7 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This Project is not associated with Anime-on-Demand in any way. Using this app m If a tv show is selected, the first episode will be marked as watched. This is due to parsing the website. ### Contributing -If you want to contribute to Teapod and need an account on this gitea instance, please write me an email: seil0@mosad.xyz +Currentl you need to have an AoD account to contrtibut to Teapod . Contributing without on is kind of impossible.If you want to contribute to Teapod and need an account on this gitea instance, please write me an email. #### Why is it called Teapod? Teapod is a Acronym for "The ultimate anime app on demand", hence this project is called Teapod and not Teapot. -- 2.44.0 From 3f45d769d2cd6410718940316ebd78170e62cad1 Mon Sep 17 00:00:00 2001 From: Jannik Date: Wed, 13 Jan 2021 20:57:00 +0100 Subject: [PATCH 06/28] rework initial loading, don't crash on login timeout on app start closes #25 --- .../java/org/mosad/teapod/MainActivity.kt | 61 ++++-- .../java/org/mosad/teapod/parser/AoDParser.kt | 177 ++++++++---------- app/src/main/res/values-de-rDE/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 4 files changed, 129 insertions(+), 113 deletions(-) diff --git a/app/src/main/java/org/mosad/teapod/MainActivity.kt b/app/src/main/java/org/mosad/teapod/MainActivity.kt index 86e9f2e..880f5d1 100644 --- a/app/src/main/java/org/mosad/teapod/MainActivity.kt +++ b/app/src/main/java/org/mosad/teapod/MainActivity.kt @@ -29,16 +29,25 @@ 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.databinding.ActivityMainBinding import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.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.ui.fragments.* +import org.mosad.teapod.ui.fragments.AccountFragment +import org.mosad.teapod.ui.fragments.HomeFragment +import org.mosad.teapod.ui.fragments.LibraryFragment +import org.mosad.teapod.ui.fragments.SearchFragment 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 { @@ -116,26 +125,43 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS } } + /** + * initial loading and login are run in parallel, as initial loading doesn't require + * any login cookies + */ private fun load() { - // running login and list in parallel does not bring any speed improvements val time = measureTimeMillis { - Preferences.load(this) + val loadingJob = AoDParser.initialLoading() // start the initial loading - // make sure credentials are set, run's async + // load all saved stuff here + Preferences.load(this) EncryptedPreferences.readCredentials(this) - if (EncryptedPreferences.password.isEmpty()) { - showLoginDialog(true) - } else { - // try to login in, as most sites can only bee loaded once loged in - if (!AoDParser.login()) showLoginDialog(false) + StorageController.load(this) + + try { + // make sure credentials are set, run's async + if (EncryptedPreferences.password.isEmpty()) { + showLoginDialog(true) + } else { + // try to login in, as most sites can only bee loaded once loged in + if (!AoDParser.login()) showLoginDialog(false) + } + } 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() } + } } - StorageController.load(this) - AoDParser.initialLoading() - - wasInitialized = true + runBlocking { loadingJob.joinAll() } // wait for initial loading to finish } - Log.i(javaClass.name, "login and list in $time ms") + Log.i(javaClass.name, "loading and login in $time ms") + + wasInitialized = true } private fun showLoginDialog(firstTry: Boolean) { @@ -186,5 +212,12 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS startActivity(restartIntent) } + /** + * exit and remove the app from tasks + */ + fun exitAndRemoveTask() { + this.finishAndRemoveTask() + exitProcess(0) + } } \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt index 46d73b0..6905a19 100644 --- a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt +++ b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt @@ -78,7 +78,7 @@ object AoDParser { val resLogin = Jsoup.connect(baseUrl + loginPath) .method(Connection.Method.POST) - .timeout(60000) // login can take some time + .timeout(60000) // login can take some time default is 60000 (60 sec) .data(data) .postDataCharset("UTF-8") .cookies(authCookies) @@ -96,20 +96,11 @@ object AoDParser { /** * initially load all media and home screen data - * -> blocking */ - fun initialLoading() = runBlocking { - val loadHomeJob = GlobalScope.async { - loadHome() - } - - val listJob = GlobalScope.async { + fun initialLoading() = listOf( + loadHome(), listAnimes() - } - - loadHomeJob.await() - listJob.await() - } + ) /** * get a media by it's ID (int) @@ -134,7 +125,7 @@ object AoDParser { } // TODO don't use jsoup here - fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) { + private fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) { val headers = mutableMapOf( Pair("Accept", "application/json, text/javascript, */*; q=0.01"), Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"), @@ -158,112 +149,99 @@ object AoDParser { /** * load all media from aod into itemMediaList and mediaList */ - private fun listAnimes() = runBlocking { - if (sessionCookies.isEmpty()) login() + private fun listAnimes() = GlobalScope.launch(Dispatchers.IO) { + val resAnimes = Jsoup.connect(baseUrl + libraryPath).get() + //println(resAnimes) - withContext(Dispatchers.Default) { - val resAnimes = Jsoup.connect(baseUrl + libraryPath) - .cookies(sessionCookies) - .get() - - //println(resAnimes) - - itemMediaList.clear() - mediaList.clear() - resAnimes.select("div.animebox").forEach { - val type = if (it.select("p.animebox-link").select("a").text().toLowerCase(Locale.ROOT) == "zur serie") { - MediaType.TVSHOW - } else { - MediaType.MOVIE - } - val mediaTitle = it.select("h3.animebox-title").text() - val mediaLink = it.select("p.animebox-link").select("a").attr("href") - val mediaImage = it.select("p.animebox-image").select("img").attr("src") - val mediaShortText = it.select("p.animebox-shorttext").text() - val mediaId = mediaLink.substringAfterLast("/").toInt() - - itemMediaList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) - mediaList.add(Media(mediaId, mediaLink, type).apply { - info.title = mediaTitle - info.posterUrl = mediaImage - info.shortDesc = mediaShortText - }) + itemMediaList.clear() + mediaList.clear() + resAnimes.select("div.animebox").forEach { + val type = if (it.select("p.animebox-link").select("a").text().toLowerCase(Locale.ROOT) == "zur serie") { + MediaType.TVSHOW + } else { + MediaType.MOVIE } + val mediaTitle = it.select("h3.animebox-title").text() + val mediaLink = it.select("p.animebox-link").select("a").attr("href") + val mediaImage = it.select("p.animebox-image").select("img").attr("src") + val mediaShortText = it.select("p.animebox-shorttext").text() + val mediaId = mediaLink.substringAfterLast("/").toInt() - Log.i(javaClass.name, "Total library size is: ${mediaList.size}") + itemMediaList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) + mediaList.add(Media(mediaId, mediaLink, type).apply { + info.title = mediaTitle + info.posterUrl = mediaImage + info.shortDesc = mediaShortText + }) } + + Log.i(javaClass.name, "Total library size is: ${mediaList.size}") } /** * load new episodes, titles and highlights */ - private fun loadHome() = runBlocking { - if (sessionCookies.isEmpty()) login() + private fun loadHome() = GlobalScope.launch(Dispatchers.IO) { + val resHome = Jsoup.connect(baseUrl).get() - withContext(Dispatchers.Default) { - val resHome = Jsoup.connect(baseUrl) - .cookies(sessionCookies) - .get() + // get highlights from AoD + highlightsList.clear() + resHome.select("#aod-highlights").select("div.news-item").forEach { + val mediaId = it.select("div.news-item-text").select("a.serienlink") + .attr("href").substringAfterLast("/").toIntOrNull() + val mediaTitle = it.select("div.news-title").select("h2").text() + val mediaImage = it.select("img").attr("src") - // get highlights from AoD - highlightsList.clear() - resHome.select("#aod-highlights").select("div.news-item").forEach { - val mediaId = it.select("div.news-item-text").select("a.serienlink") - .attr("href").substringAfterLast("/").toIntOrNull() - val mediaTitle = it.select("div.news-title").select("h2").text() - val mediaImage = it.select("img").attr("src") - - if (mediaId != null) { - highlightsList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) - } + if (mediaId != null) { + highlightsList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) } + } - // get all new episodes from AoD - newEpisodesList.clear() - resHome.select("h2:contains(Neue Episoden)").next().select("li").forEach { - val mediaId = it.select("a.thumbs").attr("href") - .substringAfterLast("/").toIntOrNull() - val mediaImage = it.select("a.thumbs > img").attr("src") - val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}" + // get all new episodes from AoD + newEpisodesList.clear() + resHome.select("h2:contains(Neue Episoden)").next().select("li").forEach { + val mediaId = it.select("a.thumbs").attr("href") + .substringAfterLast("/").toIntOrNull() + val mediaImage = it.select("a.thumbs > img").attr("src") + val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}" - if (mediaId != null) { - newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) - } + if (mediaId != null) { + newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) } + } - // get new simulcasts from AoD - newSimulcastsList.clear() - resHome.select("h2:contains(Neue Simulcasts)").next().select("li").forEach { - val mediaId = it.select("a.thumbs").attr("href") - .substringAfterLast("/").toIntOrNull() - val mediaImage = it.select("a.thumbs > img").attr("src") - val mediaTitle = it.select("a").text() + // get new simulcasts from AoD + newSimulcastsList.clear() + resHome.select("h2:contains(Neue Simulcasts)").next().select("li").forEach { + val mediaId = it.select("a.thumbs").attr("href") + .substringAfterLast("/").toIntOrNull() + val mediaImage = it.select("a.thumbs > img").attr("src") + val mediaTitle = it.select("a").text() - if (mediaId != null) { - newSimulcastsList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) - } + if (mediaId != null) { + newSimulcastsList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) } + } - // get new titles from AoD - newTitlesList.clear() - resHome.select("h2:contains(Neue Anime-Titel)").next().select("li").forEach { - val mediaId = it.select("a.thumbs").attr("href") - .substringAfterLast("/").toIntOrNull() - val mediaImage = it.select("a.thumbs > img").attr("src") - val mediaTitle = it.select("a").text() + // get new titles from AoD + newTitlesList.clear() + resHome.select("h2:contains(Neue Anime-Titel)").next().select("li").forEach { + val mediaId = it.select("a.thumbs").attr("href") + .substringAfterLast("/").toIntOrNull() + val mediaImage = it.select("a.thumbs > img").attr("src") + val mediaTitle = it.select("a").text() - if (mediaId != null) { - newTitlesList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) - } + if (mediaId != null) { + newTitlesList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) } + } - // if highlights is empty, add a random new title - if (highlightsList.isEmpty()) { - if (newTitlesList.isNotEmpty()) { - highlightsList.add(newTitlesList[Random.nextInt(0, newTitlesList.size)]) - } else { - highlightsList.add(ItemMedia(0,"", "")) - } + // if highlights is empty, add a random new title + if (highlightsList.isEmpty()) { + if (newTitlesList.isNotEmpty()) { + highlightsList.add(newTitlesList[Random.nextInt(0, newTitlesList.size)]) + } else { + highlightsList.add(ItemMedia(0,"", "")) } } } @@ -272,7 +250,7 @@ object AoDParser { * load streams for the media path, movies have one episode * @param media is used as call ba reference */ - private suspend fun loadStreams(media: Media) = GlobalScope.launch(Dispatchers.IO) { + private fun loadStreams(media: Media) = GlobalScope.launch(Dispatchers.IO) { if (sessionCookies.isEmpty()) login() if (!loginSuccess) { @@ -363,6 +341,7 @@ object AoDParser { } } } + Log.i(javaClass.name, "media loaded successfully") } /** diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 034486b..bc52c0a 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -59,6 +59,8 @@ speichern @android:string/cancel + Anmelden fehlgeschlagen + Der Server scheint langsam zu antworten. Bitte versuche es später noch einmal. Login diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e0b71d..5ddf5ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,6 +75,8 @@ save @android:string/cancel + Login failed + Looks like the server is taking to long to respond. Please try again later. Login -- 2.44.0 From 8a225548467a04ad4dc5651d3e92c6f198ab7fb9 Mon Sep 17 00:00:00 2001 From: Jannik Date: Sat, 16 Jan 2021 00:16:47 +0100 Subject: [PATCH 07/28] add onboarding (login), change default theme to dark closes #14 --- app/src/main/AndroidManifest.xml | 21 +++-- .../teapod/{ => activity}/SplashActivity.kt | 3 +- .../{ => activity/main}/MainActivity.kt | 63 +++++++------ .../main}/fragments/AboutFragment.kt | 2 +- .../main}/fragments/AccountFragment.kt | 4 +- .../main}/fragments/HomeFragment.kt | 4 +- .../main}/fragments/LibraryFragment.kt | 4 +- .../main}/fragments/MediaFragment.kt | 4 +- .../main}/fragments/SearchFragment.kt | 4 +- .../activity/onboarding/OnLoginFragment.kt | 54 +++++++++++ .../activity/onboarding/OnWelcomeFragment.kt | 31 +++++++ .../activity/onboarding/OnboardingActivity.kt | 78 ++++++++++++++++ .../{ => activity}/player/PlayerActivity.kt | 2 +- .../{ => activity}/player/PlayerViewModel.kt | 3 +- .../java/org/mosad/teapod/parser/AoDParser.kt | 2 +- .../mosad/teapod/preferences/Preferences.kt | 6 +- .../ui/components/EpisodesListPlayer.kt | 2 +- .../ui/components/LanguageSettingsPlayer.kt | 2 +- app/src/main/res/drawable/dot_default.xml | 12 +++ app/src/main/res/drawable/dot_selected.xml | 12 +++ .../main/res/drawable/dot_tab_selector.xml | 6 ++ .../main/res/layout/activity_onboarding.xml | 50 +++++++++++ app/src/main/res/layout/activity_player.xml | 2 +- app/src/main/res/layout/fragment_about.xml | 2 +- app/src/main/res/layout/fragment_account.xml | 2 +- app/src/main/res/layout/fragment_home.xml | 2 +- app/src/main/res/layout/fragment_library.xml | 2 +- app/src/main/res/layout/fragment_media.xml | 6 +- app/src/main/res/layout/fragment_on_login.xml | 90 +++++++++++++++++++ .../main/res/layout/fragment_on_welcome.xml | 74 +++++++++++++++ app/src/main/res/layout/fragment_search.xml | 2 +- .../main/res/navigation/mobile_navigation.xml | 8 +- app/src/main/res/values-de-rDE/strings.xml | 10 +++ app/src/main/res/values/strings.xml | 12 ++- 34 files changed, 514 insertions(+), 67 deletions(-) rename app/src/main/java/org/mosad/teapod/{ => activity}/SplashActivity.kt (81%) rename app/src/main/java/org/mosad/teapod/{ => activity/main}/MainActivity.kt (79%) rename app/src/main/java/org/mosad/teapod/{ui => activity/main}/fragments/AboutFragment.kt (98%) rename app/src/main/java/org/mosad/teapod/{ui => activity/main}/fragments/AccountFragment.kt (97%) rename app/src/main/java/org/mosad/teapod/{ui => activity/main}/fragments/HomeFragment.kt (98%) rename app/src/main/java/org/mosad/teapod/{ui => activity/main}/fragments/LibraryFragment.kt (94%) rename app/src/main/java/org/mosad/teapod/{ui => activity/main}/fragments/MediaFragment.kt (98%) rename app/src/main/java/org/mosad/teapod/{ui => activity/main}/fragments/SearchFragment.kt (95%) create mode 100644 app/src/main/java/org/mosad/teapod/activity/onboarding/OnLoginFragment.kt create mode 100644 app/src/main/java/org/mosad/teapod/activity/onboarding/OnWelcomeFragment.kt create mode 100644 app/src/main/java/org/mosad/teapod/activity/onboarding/OnboardingActivity.kt rename app/src/main/java/org/mosad/teapod/{ => activity}/player/PlayerActivity.kt (99%) rename app/src/main/java/org/mosad/teapod/{ => activity}/player/PlayerViewModel.kt (98%) create mode 100644 app/src/main/res/drawable/dot_default.xml create mode 100644 app/src/main/res/drawable/dot_selected.xml create mode 100644 app/src/main/res/drawable/dot_tab_selector.xml create mode 100644 app/src/main/res/layout/activity_onboarding.xml create mode 100644 app/src/main/res/layout/fragment_on_login.xml create mode 100644 app/src/main/res/layout/fragment_on_welcome.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 098b551..11b032f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -10,9 +11,9 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/AppTheme.Light"> + android:theme="@style/AppTheme.Dark"> @@ -22,20 +23,28 @@ + + + android:theme="@style/PlayerTheme" + tools:targetApi="n" /> \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/SplashActivity.kt b/app/src/main/java/org/mosad/teapod/activity/SplashActivity.kt similarity index 81% rename from app/src/main/java/org/mosad/teapod/SplashActivity.kt rename to app/src/main/java/org/mosad/teapod/activity/SplashActivity.kt index 54515b2..39b6744 100644 --- a/app/src/main/java/org/mosad/teapod/SplashActivity.kt +++ b/app/src/main/java/org/mosad/teapod/activity/SplashActivity.kt @@ -1,8 +1,9 @@ -package org.mosad.teapod +package org.mosad.teapod.activity import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import org.mosad.teapod.activity.main.MainActivity class SplashActivity : AppCompatActivity() { diff --git a/app/src/main/java/org/mosad/teapod/MainActivity.kt b/app/src/main/java/org/mosad/teapod/activity/main/MainActivity.kt similarity index 79% rename from app/src/main/java/org/mosad/teapod/MainActivity.kt rename to app/src/main/java/org/mosad/teapod/activity/main/MainActivity.kt index 880f5d1..dc8d1ad 100644 --- a/app/src/main/java/org/mosad/teapod/MainActivity.kt +++ b/app/src/main/java/org/mosad/teapod/activity/main/MainActivity.kt @@ -20,7 +20,7 @@ * */ -package org.mosad.teapod +package org.mosad.teapod.activity.main import android.content.Intent import android.os.Bundle @@ -34,16 +34,18 @@ 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.player.PlayerActivity +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.ui.fragments.AccountFragment -import org.mosad.teapod.ui.fragments.HomeFragment -import org.mosad.teapod.ui.fragments.LibraryFragment -import org.mosad.teapod.ui.fragments.SearchFragment +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 @@ -120,8 +122,8 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS private fun getThemeResource(): Int { return when (Preferences.theme) { - DataTypes.Theme.DARK -> R.style.AppTheme_Dark - else -> R.style.AppTheme_Light + DataTypes.Theme.LIGHT -> R.style.AppTheme_Light + else -> R.style.AppTheme_Dark } } @@ -138,22 +140,23 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS EncryptedPreferences.readCredentials(this) StorageController.load(this) - try { - // make sure credentials are set, run's async - if (EncryptedPreferences.password.isEmpty()) { - showLoginDialog(true) - } else { - // try to login in, as most sites can only bee loaded once loged in - if (!AoDParser.login()) showLoginDialog(false) - } - } catch (ex: SocketTimeoutException) { - Log.w(javaClass.name, "Timeout during login!") + // 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() } + // show waring dialog before finishing + MaterialDialog(this).show { + title(R.string.dialog_timeout_head) + message(R.string.dialog_timeout_desc) + onDismiss { exitAndRemoveTask() } + } } } @@ -164,12 +167,12 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS wasInitialized = true } - private fun showLoginDialog(firstTry: Boolean) { - LoginDialog(this, firstTry).positiveButton { + private fun showLoginDialog() { + LoginDialog(this, false).positiveButton { EncryptedPreferences.saveCredentials(login, password, context) if (!AoDParser.login()) { - showLoginDialog(false) + showLoginDialog() Log.w(javaClass.name, "Login failed, please try again.") } }.negativeButton { @@ -178,6 +181,14 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS }.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 diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/AboutFragment.kt b/app/src/main/java/org/mosad/teapod/activity/main/fragments/AboutFragment.kt similarity index 98% rename from app/src/main/java/org/mosad/teapod/ui/fragments/AboutFragment.kt rename to app/src/main/java/org/mosad/teapod/activity/main/fragments/AboutFragment.kt index b1c7fb2..6ab9714 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/AboutFragment.kt +++ b/app/src/main/java/org/mosad/teapod/activity/main/fragments/AboutFragment.kt @@ -1,4 +1,4 @@ -package org.mosad.teapod.ui.fragments +package org.mosad.teapod.activity.main.fragments import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/AccountFragment.kt b/app/src/main/java/org/mosad/teapod/activity/main/fragments/AccountFragment.kt similarity index 97% rename from app/src/main/java/org/mosad/teapod/ui/fragments/AccountFragment.kt rename to app/src/main/java/org/mosad/teapod/activity/main/fragments/AccountFragment.kt index b87a753..04c55f2 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/AccountFragment.kt +++ b/app/src/main/java/org/mosad/teapod/activity/main/fragments/AccountFragment.kt @@ -1,4 +1,4 @@ -package org.mosad.teapod.ui.fragments +package org.mosad.teapod.activity.main.fragments import android.os.Bundle import android.util.Log @@ -9,7 +9,7 @@ import androidx.fragment.app.Fragment import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.list.listItemsSingleChoice import org.mosad.teapod.BuildConfig -import org.mosad.teapod.MainActivity +import org.mosad.teapod.activity.main.MainActivity import org.mosad.teapod.R import org.mosad.teapod.databinding.FragmentAccountBinding import org.mosad.teapod.parser.AoDParser diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/HomeFragment.kt b/app/src/main/java/org/mosad/teapod/activity/main/fragments/HomeFragment.kt similarity index 98% rename from app/src/main/java/org/mosad/teapod/ui/fragments/HomeFragment.kt rename to app/src/main/java/org/mosad/teapod/activity/main/fragments/HomeFragment.kt index c63ed15..7120b82 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/HomeFragment.kt +++ b/app/src/main/java/org/mosad/teapod/activity/main/fragments/HomeFragment.kt @@ -1,4 +1,4 @@ -package org.mosad.teapod.ui.fragments +package org.mosad.teapod.activity.main.fragments import android.graphics.drawable.Drawable import android.os.Bundle @@ -14,7 +14,7 @@ import com.bumptech.glide.request.transition.Transition import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import org.mosad.teapod.MainActivity +import org.mosad.teapod.activity.main.MainActivity import org.mosad.teapod.R import org.mosad.teapod.databinding.FragmentHomeBinding import org.mosad.teapod.parser.AoDParser diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/LibraryFragment.kt b/app/src/main/java/org/mosad/teapod/activity/main/fragments/LibraryFragment.kt similarity index 94% rename from app/src/main/java/org/mosad/teapod/ui/fragments/LibraryFragment.kt rename to app/src/main/java/org/mosad/teapod/activity/main/fragments/LibraryFragment.kt index 4429e6a..036f3f6 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/LibraryFragment.kt +++ b/app/src/main/java/org/mosad/teapod/activity/main/fragments/LibraryFragment.kt @@ -1,4 +1,4 @@ -package org.mosad.teapod.ui.fragments +package org.mosad.teapod.activity.main.fragments import android.os.Bundle import android.view.LayoutInflater @@ -9,7 +9,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.mosad.teapod.MainActivity +import org.mosad.teapod.activity.main.MainActivity import org.mosad.teapod.databinding.FragmentLibraryBinding import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.util.adapter.MediaItemAdapter diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt b/app/src/main/java/org/mosad/teapod/activity/main/fragments/MediaFragment.kt similarity index 98% rename from app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt rename to app/src/main/java/org/mosad/teapod/activity/main/fragments/MediaFragment.kt index ca9373d..6d781e9 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt +++ b/app/src/main/java/org/mosad/teapod/activity/main/fragments/MediaFragment.kt @@ -1,4 +1,4 @@ -package org.mosad.teapod.ui.fragments +package org.mosad.teapod.activity.main.fragments import android.graphics.Color import android.graphics.drawable.ColorDrawable @@ -12,8 +12,8 @@ import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import jp.wasabeef.glide.transformations.BlurTransformation import kotlinx.coroutines.* -import org.mosad.teapod.MainActivity import org.mosad.teapod.R +import org.mosad.teapod.activity.main.MainActivity import org.mosad.teapod.databinding.FragmentMediaBinding import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.util.* diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/SearchFragment.kt b/app/src/main/java/org/mosad/teapod/activity/main/fragments/SearchFragment.kt similarity index 95% rename from app/src/main/java/org/mosad/teapod/ui/fragments/SearchFragment.kt rename to app/src/main/java/org/mosad/teapod/activity/main/fragments/SearchFragment.kt index 6810030..c7581a3 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/SearchFragment.kt +++ b/app/src/main/java/org/mosad/teapod/activity/main/fragments/SearchFragment.kt @@ -1,4 +1,4 @@ -package org.mosad.teapod.ui.fragments +package org.mosad.teapod.activity.main.fragments import android.os.Bundle import android.view.LayoutInflater @@ -7,7 +7,7 @@ import android.view.ViewGroup import android.widget.SearchView import androidx.fragment.app.Fragment import kotlinx.coroutines.* -import org.mosad.teapod.MainActivity +import org.mosad.teapod.activity.main.MainActivity import org.mosad.teapod.databinding.FragmentSearchBinding import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.util.decoration.MediaItemDecoration diff --git a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnLoginFragment.kt b/app/src/main/java/org/mosad/teapod/activity/onboarding/OnLoginFragment.kt new file mode 100644 index 0000000..b60646e --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/activity/onboarding/OnLoginFragment.kt @@ -0,0 +1,54 @@ +package org.mosad.teapod.activity.onboarding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import kotlinx.coroutines.* +import org.mosad.teapod.R +import org.mosad.teapod.databinding.FragmentOnLoginBinding +import org.mosad.teapod.parser.AoDParser +import org.mosad.teapod.preferences.EncryptedPreferences + +class OnLoginFragment: Fragment() { + + private lateinit var binding: FragmentOnLoginBinding + private var loginJob: Job? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragmentOnLoginBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initActions() + } + + private fun initActions() { + binding.buttonLogin.setOnClickListener { + // get login credentials from gui + val email = binding.editTextLogin.text.toString() + val password = binding.editTextPassword.text.toString() + + EncryptedPreferences.saveCredentials(email, password, requireContext()) // save the credentials + + binding.buttonLogin.isClickable = false + loginJob = GlobalScope.launch { + if (AoDParser.login()) { + // if login was successful, switch to main + if (activity is OnboardingActivity) { + (activity as OnboardingActivity).launchMainActivity() + } + } else { + withContext(Dispatchers.Main) { + binding.textLoginDesc.text = getString(R.string.on_login_failed) + binding.buttonLogin.isClickable = true + } + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnWelcomeFragment.kt b/app/src/main/java/org/mosad/teapod/activity/onboarding/OnWelcomeFragment.kt new file mode 100644 index 0000000..28e221c --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/activity/onboarding/OnWelcomeFragment.kt @@ -0,0 +1,31 @@ +package org.mosad.teapod.activity.onboarding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import org.mosad.teapod.databinding.FragmentOnWelcomeBinding + +class OnWelcomeFragment: Fragment() { + + private lateinit var binding: FragmentOnWelcomeBinding + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragmentOnWelcomeBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initActions() + } + + private fun initActions() { + binding.buttonGetStarted.setOnClickListener { + if (activity is OnboardingActivity) { + (activity as OnboardingActivity).nextFragment() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnboardingActivity.kt b/app/src/main/java/org/mosad/teapod/activity/onboarding/OnboardingActivity.kt new file mode 100644 index 0000000..a5a11bd --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/activity/onboarding/OnboardingActivity.kt @@ -0,0 +1,78 @@ +package org.mosad.teapod.activity.onboarding + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.material.tabs.TabLayoutMediator +import org.mosad.teapod.activity.main.MainActivity +import org.mosad.teapod.databinding.ActivityOnboardingBinding + +class OnboardingActivity : AppCompatActivity() { + + private lateinit var binding: ActivityOnboardingBinding + private lateinit var pagerAdapter: FragmentStateAdapter + + private val fragments = arrayOf(OnLoginFragment()) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityOnboardingBinding.inflate(layoutInflater) + setContentView(binding.root) + + pagerAdapter = ScreenSlidePagerAdapter(this) + binding.viewPager.adapter = pagerAdapter + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + + }.attach() + + // we don't use the skip button, instead we use the start button to skip the last fragment + binding.buttonSkip.visibility = View.GONE + + // hide tab layout if only one tab is displayed + if (fragments.size <= 1) { + binding.tabLayout.visibility = View.GONE + } + } + + override fun onBackPressed() { + if (binding.viewPager.currentItem == 0) { + super.onBackPressed() + } else { + binding.viewPager.currentItem = binding.viewPager.currentItem - 1 + } + } + + fun nextFragment() { + if (binding.viewPager.currentItem < fragments.size - 1) { + binding.viewPager.currentItem++ + } else { + launchMainActivity() + } + } + + fun btnSkipClick(@Suppress("UNUSED_PARAMETER")v: View) { + //launchMainActivity() // currently not used in Teapod + } + + fun launchMainActivity() { + startActivity(Intent(this, MainActivity::class.java)) + finish() + } + + /** + * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in + * sequence. + */ + private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { + override fun getItemCount(): Int = fragments.size + + override fun createFragment(position: Int): Fragment = fragments[position] + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt b/app/src/main/java/org/mosad/teapod/activity/player/PlayerActivity.kt similarity index 99% rename from app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt rename to app/src/main/java/org/mosad/teapod/activity/player/PlayerActivity.kt index 0b82c0d..d3e66ec 100644 --- a/app/src/main/java/org/mosad/teapod/player/PlayerActivity.kt +++ b/app/src/main/java/org/mosad/teapod/activity/player/PlayerActivity.kt @@ -1,4 +1,4 @@ -package org.mosad.teapod.player +package org.mosad.teapod.activity.player import android.animation.Animator import android.animation.AnimatorListenerAdapter diff --git a/app/src/main/java/org/mosad/teapod/player/PlayerViewModel.kt b/app/src/main/java/org/mosad/teapod/activity/player/PlayerViewModel.kt similarity index 98% rename from app/src/main/java/org/mosad/teapod/player/PlayerViewModel.kt rename to app/src/main/java/org/mosad/teapod/activity/player/PlayerViewModel.kt index 35117f4..8a1eb89 100644 --- a/app/src/main/java/org/mosad/teapod/player/PlayerViewModel.kt +++ b/app/src/main/java/org/mosad/teapod/activity/player/PlayerViewModel.kt @@ -1,4 +1,4 @@ -package org.mosad.teapod.player +package org.mosad.teapod.activity.player import android.app.Application import android.net.Uri @@ -15,7 +15,6 @@ import kotlinx.coroutines.runBlocking import org.mosad.teapod.R import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.preferences.Preferences -import org.mosad.teapod.ui.fragments.MediaFragment import org.mosad.teapod.util.DataTypes import org.mosad.teapod.util.Episode import org.mosad.teapod.util.Media diff --git a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt index 6905a19..a8a5716 100644 --- a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt +++ b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt @@ -56,7 +56,7 @@ object AoDParser { fun login(): Boolean = runBlocking { - withContext(Dispatchers.Default) { + withContext(Dispatchers.IO) { // get the authenticity token val resAuth = Jsoup.connect(baseUrl + loginPath) .header("User-Agent", userAgent) diff --git a/app/src/main/java/org/mosad/teapod/preferences/Preferences.kt b/app/src/main/java/org/mosad/teapod/preferences/Preferences.kt index 172fb92..0c8b0c2 100644 --- a/app/src/main/java/org/mosad/teapod/preferences/Preferences.kt +++ b/app/src/main/java/org/mosad/teapod/preferences/Preferences.kt @@ -11,7 +11,7 @@ object Preferences { internal set var autoplay = true internal set - var theme = DataTypes.Theme.LIGHT + var theme = DataTypes.Theme.DARK internal set private fun getSharedPref(context: Context): SharedPreferences { @@ -62,8 +62,8 @@ object Preferences { ) theme = DataTypes.Theme.valueOf( sharedPref.getString( - context.getString(R.string.save_key_theme), DataTypes.Theme.LIGHT.toString() - ) ?: DataTypes.Theme.LIGHT.toString() + context.getString(R.string.save_key_theme), DataTypes.Theme.DARK.toString() + ) ?: DataTypes.Theme.DARK.toString() ) } diff --git a/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt b/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt index 9f41056..259305f 100644 --- a/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt +++ b/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt @@ -6,7 +6,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.widget.LinearLayout import org.mosad.teapod.databinding.PlayerEpisodesListBinding -import org.mosad.teapod.player.PlayerViewModel +import org.mosad.teapod.activity.player.PlayerViewModel import org.mosad.teapod.util.adapter.PlayerEpisodeItemAdapter class EpisodesListPlayer @JvmOverloads constructor( diff --git a/app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt b/app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt index ff4510a..a1b1d92 100644 --- a/app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt +++ b/app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt @@ -13,7 +13,7 @@ import android.widget.TextView import androidx.core.view.children import org.mosad.teapod.R import org.mosad.teapod.databinding.PlayerLanguageSettingsBinding -import org.mosad.teapod.player.PlayerViewModel +import org.mosad.teapod.activity.player.PlayerViewModel import java.util.* class LanguageSettingsPlayer @JvmOverloads constructor( diff --git a/app/src/main/res/drawable/dot_default.xml b/app/src/main/res/drawable/dot_default.xml new file mode 100644 index 0000000..cb7aef7 --- /dev/null +++ b/app/src/main/res/drawable/dot_default.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_selected.xml b/app/src/main/res/drawable/dot_selected.xml new file mode 100644 index 0000000..078fd8f --- /dev/null +++ b/app/src/main/res/drawable/dot_selected.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dot_tab_selector.xml b/app/src/main/res/drawable/dot_tab_selector.xml new file mode 100644 index 0000000..b32d447 --- /dev/null +++ b/app/src/main/res/drawable/dot_tab_selector.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml new file mode 100644 index 0000000..1bdea86 --- /dev/null +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -0,0 +1,50 @@ + + + + + + + + +