diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4407a8ccfb..7b6632b7dc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -93,6 +93,9 @@
android:name=".widget.CustomLayoutPickerActivity"
android:label="@string/app_name"
android:theme="@style/FilePickerTheme" />
+
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt
index 10943d24a6..6f03090e6c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt
@@ -96,11 +96,11 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
return track
}
+ suspend fun login(csrfToken: String) = login("myanimelist", csrfToken)
+
override suspend fun login(username: String, password: String): Boolean {
- logout()
return try {
- val csrf = api.login(username, password)
- saveCSRF(csrf)
+ saveCSRF(password)
saveCredentials(username, password)
true
} catch (e: Exception) {
@@ -110,28 +110,10 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
}
}
- suspend fun refreshLogin() {
- val username = getUsername()
- val password = getPassword()
- logout()
-
- try {
- val csrf = api.login(username, password)
- saveCSRF(csrf)
- saveCredentials(username, password)
- } catch (e: Exception) {
- Timber.e(e)
- logout()
- throw e
- }
- }
-
// Attempt to login again if cookies have been cleared but credentials are still filled
suspend fun ensureLoggedIn() {
if (isAuthorized) return
if (!isLogged) throw Exception("MAL Login Credentials not found")
-
- refreshLogin()
}
override fun logout() {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
index f6621af6c7..21ad64b35b 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
@@ -114,32 +114,6 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
}
}
- suspend fun login(username: String, password: String): String {
- return withContext(Dispatchers.IO) {
- val csrf = getSessionInfo()
- login(username, password, csrf)
- csrf
- }
- }
-
- private suspend fun getSessionInfo(): String {
- val response = client.newCall(GET(loginUrl())).execute()
-
- return Jsoup.parse(response.consumeBody()).select("meta[name=csrf_token]").attr("content")
- }
-
- private suspend fun login(username: String, password: String, csrf: String) {
- withContext(Dispatchers.IO) {
- val response =
- client.newCall(POST(loginUrl(), body = loginPostBody(username, password, csrf)))
- .execute()
-
- response.use {
- if (response.priorResponse?.code != 302) throw Exception("Authentication error")
- }
- }
- }
-
private suspend fun getList(): List {
val results = getListXml(getListUrl()).select("manga")
@@ -174,7 +148,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
companion object {
const val CSRF = "csrf_token"
- private const val baseUrl = "https://myanimelist.net"
+ const val baseUrl = "https://myanimelist.net"
private const val baseMangaUrl = "$baseUrl/manga/"
private const val baseModifyListUrl = "$baseUrl/ownlist/manga"
private const val PREFIX_MY = "my:"
@@ -182,7 +156,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
private fun mangaUrl(remoteId: Int) = baseMangaUrl + remoteId
- private fun loginUrl() = baseUrl.toUri().buildUpon().appendPath("login.php").toString()
+ fun loginUrl() = baseUrl.toUri().buildUpon().appendPath("login.php").build()
private fun searchUrl(query: String): String {
val col = "c[]"
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt
index 192f5ec672..03e3c6a95c 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt
@@ -21,17 +21,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor
myanimelist.ensureLoggedIn()
}
val request = chain.request()
- var response = chain.proceed(updateRequest(request))
-
- if (response.code == 400) {
- runBlocking {
- myanimelist.refreshLogin()
- }
- response.close()
- response = chain.proceed(updateRequest(request))
- }
-
- return response
+ return chain.proceed(updateRequest(request))
}
private fun updateRequest(request: Request): Request {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt
index cc0ad52415..cac8a04edf 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
+import eu.kanade.tachiyomi.ui.setting.track.MyAnimeListLoginActivity
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.widget.preference.LoginPreference
import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
@@ -38,7 +39,13 @@ class SettingsTrackingController :
trackPreference(trackManager.myAnimeList) {
onClick {
- showDialog(trackManager.myAnimeList)
+ if (trackManager.myAnimeList.isLogged) {
+ val dialog = TrackLogoutDialog(trackManager.myAnimeList)
+ dialog.targetController = this@SettingsTrackingController
+ dialog.showDialog(router)
+ } else {
+ startActivity(MyAnimeListLoginActivity.newIntent(context))
+ }
}
}
trackPreference(trackManager.aniList) {
@@ -79,7 +86,7 @@ class SettingsTrackingController :
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
- // Manually refresh anilist holder
+ updatePreference(trackManager.myAnimeList.id)
updatePreference(trackManager.aniList.id)
updatePreference(trackManager.shikimori.id)
updatePreference(trackManager.bangumi.id)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/MyAnimeListLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/MyAnimeListLoginActivity.kt
new file mode 100644
index 0000000000..bcec7b8b9a
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/MyAnimeListLoginActivity.kt
@@ -0,0 +1,77 @@
+package eu.kanade.tachiyomi.ui.setting.track
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.webkit.WebView
+import eu.kanade.tachiyomi.data.track.TrackManager
+import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
+import eu.kanade.tachiyomi.ui.main.MainActivity
+import eu.kanade.tachiyomi.ui.webview.BaseWebViewActivity
+import eu.kanade.tachiyomi.util.system.WebViewClientCompat
+import kotlinx.android.synthetic.main.webview_activity.*
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import uy.kohesive.injekt.injectLazy
+
+class MyAnimeListLoginActivity : BaseWebViewActivity() {
+
+ private val trackManager: TrackManager by injectLazy()
+
+ private val scope = CoroutineScope(Job() + Dispatchers.Main)
+
+ override fun onCreate(savedState: Bundle?) {
+ super.onCreate(savedState)
+
+ title = "MyAnimeList"
+
+ webview.webViewClient = object : WebViewClientCompat() {
+ override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean {
+ view.loadUrl(url)
+ return true
+ }
+
+ override fun onPageFinished(view: WebView?, url: String?) {
+ super.onPageFinished(view, url)
+
+ // Get CSRF token from HTML after post-login redirect
+ if (url == MyAnimeListApi.baseUrl + "/") {
+ view?.evaluateJavascript(
+ "(function(){return document.querySelector('meta[name=csrf_token]').getAttribute('content')})();"
+ ) {
+ scope.launch {
+ withContext(Dispatchers.IO) { trackManager.myAnimeList.login(it.replace("\"", "")) }
+ returnToSettings()
+ }
+ }
+ }
+ }
+ }
+ webview.loadUrl(MyAnimeListApi.loginUrl().toString())
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ scope.cancel()
+ }
+
+ private fun returnToSettings() {
+ finish()
+
+ val intent = Intent(this, MainActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ startActivity(intent)
+ }
+
+ companion object {
+ fun newIntent(context: Context): Intent {
+ val intent = Intent(context, MyAnimeListLoginActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ return intent
+ }
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt
new file mode 100644
index 0000000000..4e80bee3f6
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt
@@ -0,0 +1,255 @@
+package eu.kanade.tachiyomi.ui.webview
+
+import android.content.Intent
+import android.content.res.Configuration
+import android.graphics.Color
+import android.os.Build
+import android.os.Bundle
+import android.util.TypedValue
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.webkit.WebChromeClient
+import android.webkit.WebView
+import android.widget.LinearLayout
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.graphics.ColorUtils
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
+import eu.kanade.tachiyomi.util.system.getResourceColor
+import eu.kanade.tachiyomi.util.system.isInNightMode
+import eu.kanade.tachiyomi.util.system.openInBrowser
+import eu.kanade.tachiyomi.util.system.setDefaultSettings
+import eu.kanade.tachiyomi.util.system.toast
+import eu.kanade.tachiyomi.util.view.invisible
+import eu.kanade.tachiyomi.util.view.marginBottom
+import eu.kanade.tachiyomi.util.view.setStyle
+import eu.kanade.tachiyomi.util.view.updateLayoutParams
+import eu.kanade.tachiyomi.util.view.updatePadding
+import eu.kanade.tachiyomi.util.view.visible
+import kotlinx.android.synthetic.main.webview_activity.*
+
+open class BaseWebViewActivity : BaseActivity() {
+
+ private var bundle: Bundle? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ delegate.localNightMode = when (preferences.theme()) {
+ 1, 8 -> AppCompatDelegate.MODE_NIGHT_NO
+ 2, 3, 4 -> AppCompatDelegate.MODE_NIGHT_YES
+ else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
+ }
+ setContentView(R.layout.webview_activity)
+ setSupportActionBar(toolbar)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ toolbar.setNavigationOnClickListener {
+ super.onBackPressed()
+ }
+ toolbar.navigationIcon?.setTint(getResourceColor(R.attr.actionBarTintColor))
+
+ val container: ViewGroup = findViewById(R.id.web_view_layout)
+ val content: LinearLayout = findViewById(R.id.web_linear_layout)
+ container.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ content.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+
+ container.setOnApplyWindowInsetsListener { v, insets ->
+ val contextView = window?.decorView?.findViewById(R.id.action_mode_bar)
+ contextView?.updateLayoutParams {
+ leftMargin = insets.systemWindowInsetLeft
+ rightMargin = insets.systemWindowInsetRight
+ }
+ // Consume any horizontal insets and pad all content in. There's not much we can do
+ // with horizontal insets
+ v.updatePadding(
+ left = insets.systemWindowInsetLeft,
+ right = insets.systemWindowInsetRight
+ )
+ insets.replaceSystemWindowInsets(
+ 0,
+ insets.systemWindowInsetTop,
+ 0,
+ insets.systemWindowInsetBottom
+ )
+ }
+ swipe_refresh.setStyle()
+ swipe_refresh.setOnRefreshListener {
+ refreshPage()
+ }
+
+ window.statusBarColor = ColorUtils.setAlphaComponent(
+ getResourceColor(
+ R.attr
+ .colorSecondary
+ ),
+ 255
+ )
+
+ content.setOnApplyWindowInsetsListener { v, insets ->
+ // if pure white theme on a device that does not support dark status bar
+ /*if (getResourceColor(android.R.attr.statusBarColor) != Color.TRANSPARENT)
+ window.statusBarColor = Color.BLACK
+ else window.statusBarColor = getResourceColor(R.attr.colorPrimary)*/
+ window.navigationBarColor = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ val colorPrimary = getResourceColor(R.attr.colorPrimaryVariant)
+ if (colorPrimary == Color.WHITE) Color.BLACK
+ else getResourceColor(android.R.attr.colorPrimary)
+ }
+ // if the android q+ device has gesture nav, transparent nav bar
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
+ (v.rootWindowInsets.systemWindowInsetBottom != v.rootWindowInsets.tappableElementInsets.bottom)
+ ) {
+ getColor(android.R.color.transparent)
+ } else {
+ getResourceColor(android.R.attr.colorBackground)
+ }
+ v.setPadding(
+ insets.systemWindowInsetLeft,
+ insets.systemWindowInsetTop,
+ insets.systemWindowInsetRight,
+ 0
+ )
+ if (Build.VERSION.SDK_INT >= 26 && !isInNightMode()) {
+ content.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+ }
+ insets
+ }
+
+ swipe_refresh.isEnabled = false
+
+ if (bundle == null) {
+ webview.setDefaultSettings()
+
+ webview.webChromeClient = object : WebChromeClient() {
+ override fun onProgressChanged(view: WebView?, newProgress: Int) {
+ progressBar.visible()
+ progressBar.progress = newProgress
+ if (newProgress == 100)
+ progressBar.invisible()
+ super.onProgressChanged(view, newProgress)
+ }
+ }
+ val marginB = webview.marginBottom
+ webview.setOnApplyWindowInsetsListener { v, insets ->
+ val bottomInset =
+ if (Build.VERSION.SDK_INT >= 29) insets.tappableElementInsets.bottom
+ else insets.systemWindowInsetBottom
+ v.updateLayoutParams {
+ bottomMargin = marginB + bottomInset
+ }
+ insets
+ }
+ } else {
+ webview.restoreState(bundle)
+ }
+ }
+
+ private fun refreshPage() {
+ swipe_refresh.isRefreshing = true
+ webview.reload()
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ val lightMode = !isInNightMode()
+ window.statusBarColor = ColorUtils.setAlphaComponent(
+ getResourceColor(
+ R.attr
+ .colorSecondary
+ ),
+ 255
+ )
+ toolbar.setBackgroundColor(getResourceColor(R.attr.colorSecondary))
+ toolbar.popupTheme = if (lightMode) R.style.ThemeOverlay_MaterialComponents else R
+ .style.ThemeOverlay_MaterialComponents_Dark
+ val tintColor = getResourceColor(R.attr.actionBarTintColor)
+ toolbar.navigationIcon?.setTint(tintColor)
+ toolbar.overflowIcon?.mutate()
+ toolbar.setTitleTextColor(tintColor)
+ toolbar.overflowIcon?.setTint(tintColor)
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+ window.navigationBarColor = getResourceColor(R.attr.colorPrimaryVariant)
+ else if (window.navigationBarColor != getColor(android.R.color.transparent))
+ window.navigationBarColor = getResourceColor(android.R.attr.colorBackground)
+
+ web_linear_layout.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && lightMode) {
+ web_linear_layout.systemUiVisibility = web_linear_layout.systemUiVisibility.or(
+ View
+ .SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+ )
+ }
+ val typedValue = TypedValue()
+ theme.resolveAttribute(android.R.attr.windowLightStatusBar, typedValue, true)
+
+ if (typedValue.data == -1)
+ web_linear_layout.systemUiVisibility = web_linear_layout.systemUiVisibility
+ .or(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
+ else
+ web_linear_layout.systemUiVisibility = web_linear_layout.systemUiVisibility
+ .rem(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
+ invalidateOptionsMenu()
+ }
+
+ /**
+ * Called when the options menu of the toolbar is being created. It adds our custom menu.
+ */
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.webview, menu)
+ return true
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
+ val backItem = toolbar.menu.findItem(R.id.action_web_back)
+ val forwardItem = toolbar.menu.findItem(R.id.action_web_forward)
+ backItem?.isEnabled = webview.canGoBack()
+ forwardItem?.isEnabled = webview.canGoForward()
+ val hasHistory = webview.canGoBack() || webview.canGoForward()
+ backItem?.isVisible = hasHistory
+ forwardItem?.isVisible = hasHistory
+ val tintColor = getResourceColor(R.attr.actionBarTintColor)
+ val translucentWhite = ColorUtils.setAlphaComponent(tintColor, 127)
+ backItem.icon?.setTint(if (webview.canGoBack()) tintColor else translucentWhite)
+ forwardItem?.icon?.setTint(if (webview.canGoForward()) tintColor else translucentWhite)
+ return super.onPrepareOptionsMenu(menu)
+ }
+
+ override fun onBackPressed() {
+ if (webview.canGoBack()) webview.goBack()
+ else super.onBackPressed()
+ }
+
+ /**
+ * Called when an item of the options menu was clicked. Used to handle clicks on our menu
+ * entries.
+ */
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_web_back -> webview.goBack()
+ R.id.action_web_forward -> webview.goForward()
+ R.id.action_web_share -> shareWebpage()
+ R.id.action_web_browser -> openInBrowser()
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun shareWebpage() {
+ try {
+ val intent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_TEXT, webview.url)
+ }
+ startActivity(Intent.createChooser(intent, getString(R.string.share)))
+ } catch (e: Exception) {
+ toast(e.message)
+ }
+ }
+
+ private fun openInBrowser() {
+ openInBrowser(webview.url)
+ }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt
index b75abe0f64..bdb5c9382a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt
@@ -35,7 +35,7 @@ import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.webview_activity.*
import uy.kohesive.injekt.injectLazy
-class WebViewActivity : BaseActivity() {
+open class WebViewActivity : BaseActivity() {
private val sourceManager by injectLazy()
private var bundle: Bundle? = null
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt
index 83e864a05a..eab28cf034 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt
@@ -1,7 +1,20 @@
package eu.kanade.tachiyomi.util.system
+import android.webkit.WebSettings
import android.webkit.WebView
+fun WebView.setDefaultSettings() {
+ with(settings) {
+ javaScriptEnabled = true
+ domStorageEnabled = true
+ databaseEnabled = true
+ setAppCacheEnabled(true)
+ useWideViewPort = true
+ loadWithOverviewMode = true
+ cacheMode = WebSettings.LOAD_DEFAULT
+ }
+}
+
private val WEBVIEW_UA_VERSION_REGEX by lazy {
Regex(""".*Chrome/(\d+)\..*""")
}