mirror of
https://github.com/rebelonion/Dantotsu.git
synced 2026-01-23 11:11:05 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d109914537 | ||
|
|
da4d55a9a8 | ||
|
|
63526c6ed3 | ||
|
|
dc165fa6bc | ||
|
|
dc959796e6 | ||
|
|
0b9f2bb019 | ||
|
|
6ddbd4760c | ||
|
|
d1270c7c83 | ||
|
|
79618e1963 | ||
|
|
da81646297 | ||
|
|
41b90e3a39 | ||
|
|
57a584a820 | ||
|
|
dbe573131e |
@@ -26,7 +26,7 @@ Dantotsu is crafted from the ashes of Saikou and based on simplistic yet state-o
|
|||||||
| Type | Status |
|
| Type | Status |
|
||||||
| ---------------- | ------- |
|
| ---------------- | ------- |
|
||||||
| Anime Extensions | Working |
|
| Anime Extensions | Working |
|
||||||
| Manga Extensions | Not Working |
|
| Manga Extensions | "Working" |
|
||||||
| Light Novel Extensions | Not Working |
|
| Light Novel Extensions | Not Working |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ android {
|
|||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode ((System.currentTimeMillis() / 60000).toInteger())
|
versionCode ((System.currentTimeMillis() / 60000).toInteger())
|
||||||
versionName "0.0.2"
|
versionName "0.1.3"
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +97,9 @@ dependencies {
|
|||||||
implementation 'com.alexvasilkov:gesture-views:2.8.3'
|
implementation 'com.alexvasilkov:gesture-views:2.8.3'
|
||||||
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
implementation 'com.github.VipulOG:ebook-reader:0.1.6'
|
||||||
|
|
||||||
|
// string matching
|
||||||
|
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||||
|
|
||||||
// Aniyomi
|
// Aniyomi
|
||||||
implementation 'io.reactivex:rxjava:1.3.8'
|
implementation 'io.reactivex:rxjava:1.3.8'
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.software.leanback"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
@@ -9,9 +16,9 @@
|
|||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="29"
|
android:maxSdkVersion="32" />
|
||||||
tools:ignore="ScopedStorage" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
android:maxSdkVersion="32" />
|
||||||
|
|
||||||
<!-- For background jobs -->
|
<!-- For background jobs -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
@@ -48,7 +55,7 @@
|
|||||||
android:theme="@style/Theme.Dantotsu"
|
android:theme="@style/Theme.Dantotsu"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="AllowBackup"
|
tools:ignore="AllowBackup"
|
||||||
>
|
android:banner="@drawable/ic_banner_foreground">
|
||||||
<activity
|
<activity
|
||||||
android:name="ani.dantotsu.media.novel.novelreader.NovelReaderActivity"
|
android:name="ani.dantotsu.media.novel.novelreader.NovelReaderActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
@@ -207,10 +214,22 @@
|
|||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.Main" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallActivity"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".subcriptions.AlarmReceiver"
|
android:name=".subcriptions.AlarmReceiver"
|
||||||
@@ -243,7 +262,10 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service android:name=".aniyomi.anime.util.AnimeExtensionInstallService"
|
<service android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<service android:name="eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import androidx.multidex.MultiDex
|
|||||||
import androidx.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import ani.dantotsu.aniyomi.anime.custom.AppModule
|
import ani.dantotsu.aniyomi.anime.custom.AppModule
|
||||||
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
|
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
|
||||||
import ani.dantotsu.aniyomi.data.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import ani.dantotsu.aniyomi.util.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import ani.dantotsu.others.DisabledReports
|
import ani.dantotsu.others.DisabledReports
|
||||||
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
import logcat.AndroidLogcatLogger
|
import logcat.AndroidLogcatLogger
|
||||||
@@ -33,6 +34,11 @@ class App : MultiDexApplication() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
val sharedPreferences = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
|
val useMaterialYou = sharedPreferences.getBoolean("use_material_you", false)
|
||||||
|
if(useMaterialYou) {
|
||||||
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
|
}
|
||||||
registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
|
registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
|
||||||
|
|
||||||
Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)
|
Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package ani.dantotsu
|
package ani.dantotsu
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -9,6 +14,7 @@ import android.os.Bundle
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AnticipateInterpolator
|
import android.view.animation.AnticipateInterpolator
|
||||||
@@ -17,6 +23,8 @@ import androidx.activity.addCallback
|
|||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.doOnAttach
|
import androidx.core.view.doOnAttach
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -24,7 +32,7 @@ import androidx.fragment.app.FragmentManager
|
|||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import ani.dantotsu.aniyomi.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
|
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
|
||||||
import ani.dantotsu.databinding.ActivityMainBinding
|
import ani.dantotsu.databinding.ActivityMainBinding
|
||||||
@@ -37,13 +45,18 @@ import ani.dantotsu.home.NoInternet
|
|||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.others.CustomBottomDialog
|
import ani.dantotsu.others.CustomBottomDialog
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
import ani.dantotsu.settings.SettingsActivity
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
|
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
@@ -58,18 +71,26 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private var uiSettings = UserInterfaceSettings()
|
private var uiSettings = UserInterfaceSettings()
|
||||||
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
||||||
|
private val mangaExtensionManager: MangaExtensionManager by injectLazy()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
val myScope = CoroutineScope(Dispatchers.Default)
|
val animeScope = CoroutineScope(Dispatchers.Default)
|
||||||
myScope.launch {
|
animeScope.launch {
|
||||||
animeExtensionManager.findAvailableExtensions()
|
animeExtensionManager.findAvailableExtensions()
|
||||||
|
logger("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
|
||||||
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
|
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
|
||||||
|
}
|
||||||
|
val mangaScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
mangaScope.launch {
|
||||||
|
mangaExtensionManager.findAvailableExtensions()
|
||||||
|
logger("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
||||||
|
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
var doubleBackToExitPressedOnce = false
|
var doubleBackToExitPressedOnce = false
|
||||||
@@ -212,8 +233,21 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (ActivityHelper.shouldRefreshMainActivity) {
|
||||||
|
ActivityHelper.shouldRefreshMainActivity = false
|
||||||
|
Refresh.all()
|
||||||
|
finish()
|
||||||
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
|
initActivity(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//ViewPager
|
//ViewPager
|
||||||
private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
||||||
FragmentStateAdapter(fragmentManager, lifecycle) {
|
FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||||
@@ -231,3 +265,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ActivityHelper {
|
||||||
|
var shouldRefreshMainActivity: Boolean = false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
NOTICE
|
|
||||||
|
|
||||||
This software includes code modified from Aniyomi, available at https://github.com/aniyomiorg/aniyomi/.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package ani.dantotsu.aniyomi.anime.custom
|
|
||||||
/*
|
|
||||||
import android.app.Application
|
|
||||||
import ani.dantotsu.aniyomi.data.Notifications
|
|
||||||
import ani.dantotsu.aniyomi.util.logcat
|
|
||||||
import logcat.AndroidLogcatLogger
|
|
||||||
import logcat.LogPriority
|
|
||||||
import logcat.LogcatLogger
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
|
|
||||||
class App : Application() {
|
|
||||||
override fun onCreate() {
|
|
||||||
super<Application>.onCreate()
|
|
||||||
Injekt.importModule(AppModule(this))
|
|
||||||
Injekt.importModule(PreferenceModule(this))
|
|
||||||
|
|
||||||
setupNotificationChannels()
|
|
||||||
if (!LogcatLogger.isInstalled) {
|
|
||||||
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupNotificationChannels() {
|
|
||||||
try {
|
|
||||||
Notifications.createChannels(this)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
package ani.dantotsu.aniyomi.anime.custom
|
package ani.dantotsu.aniyomi.anime.custom
|
||||||
|
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import ani.dantotsu.aniyomi.anime.AnimeExtensionManager
|
import android.content.Context
|
||||||
import ani.dantotsu.aniyomi.core.preference.PreferenceStore
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import ani.dantotsu.aniyomi.domain.base.BasePreferences
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import ani.dantotsu.aniyomi.domain.source.service.SourcePreferences
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import ani.dantotsu.aniyomi.core.preference.AndroidPreferenceStore
|
import eu.kanade.domain.base.BasePreferences
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
@@ -18,16 +23,23 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
override fun InjektRegistrar.registerInjectables() {
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
addSingleton(app)
|
addSingleton(app)
|
||||||
|
|
||||||
addSingletonFactory { NetworkHelper(app) }
|
addSingletonFactory { NetworkHelper(app, get()) }
|
||||||
|
|
||||||
addSingletonFactory { AnimeExtensionManager(app) }
|
addSingletonFactory { AnimeExtensionManager(app) }
|
||||||
|
|
||||||
|
addSingletonFactory { MangaExtensionManager(app) }
|
||||||
|
|
||||||
|
val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
|
addSingleton(sharedPreferences)
|
||||||
|
|
||||||
addSingletonFactory {
|
addSingletonFactory {
|
||||||
Json {
|
Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
explicitNulls = false
|
explicitNulls = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addSingletonFactory { MangaCache() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +49,13 @@ class PreferenceModule(val application: Application) : InjektModule {
|
|||||||
AndroidPreferenceStore(application)
|
AndroidPreferenceStore(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addSingletonFactory {
|
||||||
|
NetworkPreferences(
|
||||||
|
preferenceStore = get(),
|
||||||
|
verboseLogging = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
addSingletonFactory {
|
addSingletonFactory {
|
||||||
SourcePreferences(get())
|
SourcePreferences(get())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
package ani.dantotsu.aniyomi.util.srcapi
|
|
||||||
|
|
||||||
//actual suspend fun <T> Observable<T>.awaitSingle(): T = awaitSingle()
|
|
||||||
@@ -7,10 +7,12 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import ani.dantotsu.logError
|
import ani.dantotsu.logError
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.logger
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class Login : AppCompatActivity() {
|
class Login : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
val data: Uri? = intent?.data
|
val data: Uri? = intent?.data
|
||||||
logger(data.toString())
|
logger(data.toString())
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import android.os.Bundle
|
|||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import ani.dantotsu.loadMedia
|
import ani.dantotsu.loadMedia
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class UrlMedia : Activity() {
|
class UrlMedia : Activity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
var id: Int? = intent?.extras?.getInt("media", 0) ?: 0
|
var id: Int? = intent?.extras?.getInt("media", 0) ?: 0
|
||||||
var isMAL = false
|
var isMAL = false
|
||||||
var continueMedia = true
|
var continueMedia = true
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
package ani.dantotsu.connections.discord
|
package ani.dantotsu.connections.discord
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Application.getProcessName
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.discord.Discord.saveToken
|
import ani.dantotsu.connections.discord.Discord.saveToken
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class Login : AppCompatActivity() {
|
class Login : AppCompatActivity() {
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
val process = getProcessName()
|
||||||
|
if (packageName != process) WebView.setDataDirectorySuffix(process)
|
||||||
|
}
|
||||||
setContentView(R.layout.activity_discord)
|
setContentView(R.layout.activity_discord)
|
||||||
|
|
||||||
val webView = findViewById<WebView>(R.id.discordWebview)
|
val webView = findViewById<WebView>(R.id.discordWebview)
|
||||||
|
|
||||||
webView.apply {
|
webView.apply {
|
||||||
settings.javaScriptEnabled = true
|
settings.javaScriptEnabled = true
|
||||||
settings.databaseEnabled = true
|
settings.databaseEnabled = true
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.connections.mal.MAL.clientId
|
import ani.dantotsu.connections.mal.MAL.clientId
|
||||||
import ani.dantotsu.connections.mal.MAL.saveResponse
|
import ani.dantotsu.connections.mal.MAL.saveResponse
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class Login : AppCompatActivity() {
|
class Login : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
try {
|
try {
|
||||||
val data: Uri = intent?.data
|
val data: Uri = intent?.data
|
||||||
?: throw Exception(getString(R.string.mal_login_uri_not_found))
|
?: throw Exception(getString(R.string.mal_login_uri_not_found))
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ object Helper {
|
|||||||
SubtitleType.VTT -> MimeTypes.TEXT_VTT
|
SubtitleType.VTT -> MimeTypes.TEXT_VTT
|
||||||
SubtitleType.ASS -> MimeTypes.TEXT_SSA
|
SubtitleType.ASS -> MimeTypes.TEXT_SSA
|
||||||
SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP
|
SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP
|
||||||
|
SubtitleType.UNKNOWN -> MimeTypes.TEXT_SSA
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
fun updateAvatar() {
|
fun updateAvatar() {
|
||||||
if (Anilist.avatar != null && ready.value == true) {
|
if (Anilist.avatar != null && ready.value == true) {
|
||||||
binding.animeUserAvatar.loadImage(Anilist.avatar)
|
binding.animeUserAvatar.loadImage(Anilist.avatar)
|
||||||
|
binding.animeUserAvatar.imageTintList = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.media.GenreActivity
|
import ani.dantotsu.media.GenreActivity
|
||||||
import ani.dantotsu.MediaPageTransformer
|
import ani.dantotsu.MediaPageTransformer
|
||||||
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.databinding.ItemMangaPageBinding
|
import ani.dantotsu.databinding.ItemMangaPageBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
@@ -153,6 +154,7 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
fun updateAvatar() {
|
fun updateAvatar() {
|
||||||
if (Anilist.avatar != null && ready.value == true) {
|
if (Anilist.avatar != null && ready.value == true) {
|
||||||
binding.mangaUserAvatar.loadImage(Anilist.avatar)
|
binding.mangaUserAvatar.loadImage(Anilist.avatar)
|
||||||
|
binding.mangaUserAvatar.imageTintList = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import ani.dantotsu.isOnline
|
|||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class NoInternet : AppCompatActivity() {
|
class NoInternet : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
val binding = ActivityNoInternetBinding.inflate(layoutInflater)
|
val binding = ActivityNoInternetBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.ActivityAuthorBinding
|
import ani.dantotsu.databinding.ActivityAuthorBinding
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -28,6 +29,7 @@ class AuthorActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityAuthorBinding.inflate(layoutInflater)
|
binding = ActivityAuthorBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ani.dantotsu.media
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -12,6 +13,7 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityListBinding
|
import ani.dantotsu.databinding.ActivityListBinding
|
||||||
import ani.dantotsu.media.user.ListViewPagerAdapter
|
import ani.dantotsu.media.user.ListViewPagerAdapter
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -27,10 +29,27 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityListBinding.inflate(layoutInflater)
|
binding = ActivityListBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg)
|
val typedValue = TypedValue()
|
||||||
|
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue, true)
|
||||||
|
val primaryColor = typedValue.data
|
||||||
|
val typedValue2 = TypedValue()
|
||||||
|
theme.resolveAttribute(com.google.android.material.R.attr.colorOnPrimary, typedValue2, true)
|
||||||
|
val primaryTextColor = typedValue2.data
|
||||||
|
val typedValue3 = TypedValue()
|
||||||
|
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimaryContainer, typedValue3, true)
|
||||||
|
val secondaryColor = typedValue3.data
|
||||||
|
|
||||||
|
window.statusBarColor = primaryColor
|
||||||
|
window.navigationBarColor = primaryColor
|
||||||
|
binding.listTabLayout.setBackgroundColor(primaryColor)
|
||||||
|
binding.listAppBar.setBackgroundColor(primaryColor)
|
||||||
|
binding.listTitle.setTextColor(primaryTextColor)
|
||||||
|
binding.listTabLayout.setTabTextColors(primaryTextColor, secondaryColor)
|
||||||
|
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
||||||
binding.listTitle.setText(R.string.release_calendar)
|
binding.listTitle.setText(R.string.release_calendar)
|
||||||
binding.listSort.visibility = View.GONE
|
binding.listSort.visibility = View.GONE
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import ani.dantotsu.databinding.ActivityCharacterBinding
|
|||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -33,6 +34,7 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityCharacterBinding.inflate(layoutInflater)
|
binding = ActivityCharacterBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import ani.dantotsu.initActivity
|
|||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -25,6 +26,7 @@ class GenreActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityGenreBinding.inflate(layoutInflater)
|
binding = ActivityGenreBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ data class Media(
|
|||||||
this.relation = mediaEdge.relationType?.toString()
|
this.relation = mediaEdge.relationType?.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mainName() = nameMAL ?: name ?: nameRomaji
|
fun mainName() = name ?: nameMAL ?: nameRomaji
|
||||||
fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji
|
fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object MediaSingleton {
|
||||||
|
var media: Media? = null
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import ani.dantotsu.saveData
|
|||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.flaviofaria.kenburnsview.RandomTransitionGenerator
|
import com.flaviofaria.kenburnsview.RandomTransitionGenerator
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
@@ -71,6 +72,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityMediaBinding.inflate(layoutInflater)
|
binding = ActivityMediaBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
||||||
@@ -79,7 +81,8 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||||
if (!uiSettings.immersiveMode) this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg_inv)
|
if (!uiSettings.immersiveMode) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.nav_bg_inv)
|
||||||
|
|
||||||
binding.mediaBanner.updateLayoutParams { height += statusBarHeight }
|
binding.mediaBanner.updateLayoutParams { height += statusBarHeight }
|
||||||
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
|
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
|
||||||
@@ -101,10 +104,14 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
if (uiSettings.bannerAnimations) {
|
if (uiSettings.bannerAnimations) {
|
||||||
val adi = AccelerateDecelerateInterpolator()
|
val adi = AccelerateDecelerateInterpolator()
|
||||||
val generator = RandomTransitionGenerator((10000 + 15000 * (uiSettings.animationSpeed)).toLong(), adi)
|
val generator = RandomTransitionGenerator(
|
||||||
|
(10000 + 15000 * (uiSettings.animationSpeed)).toLong(),
|
||||||
|
adi
|
||||||
|
)
|
||||||
binding.mediaBanner.setTransitionGenerator(generator)
|
binding.mediaBanner.setTransitionGenerator(generator)
|
||||||
}
|
}
|
||||||
val banner = if (uiSettings.bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen
|
val banner =
|
||||||
|
if (uiSettings.bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen
|
||||||
val viewPager = binding.mediaViewPager
|
val viewPager = binding.mediaViewPager
|
||||||
tabLayout = binding.mediaTab as NavigationBarView
|
tabLayout = binding.mediaTab as NavigationBarView
|
||||||
viewPager.isUserInputEnabled = false
|
viewPager.isUserInputEnabled = false
|
||||||
@@ -167,8 +174,8 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
binding.mediaFav,
|
binding.mediaFav,
|
||||||
R.drawable.ic_round_favorite_24,
|
R.drawable.ic_round_favorite_24,
|
||||||
R.drawable.ic_round_favorite_border_24,
|
R.drawable.ic_round_favorite_border_24,
|
||||||
R.color.nav_tab,
|
com.google.android.material.R.attr.colorSecondary,
|
||||||
R.color.fav,
|
com.google.android.material.R.attr.colorSecondary,
|
||||||
media.isFav
|
media.isFav
|
||||||
) {
|
) {
|
||||||
media.isFav = it
|
media.isFav = it
|
||||||
@@ -180,17 +187,32 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ResourceType")
|
||||||
fun total() {
|
fun total() {
|
||||||
val text = SpannableStringBuilder().apply {
|
val text = SpannableStringBuilder().apply {
|
||||||
val white = ContextCompat.getColor(this@MediaDetailsActivity, R.color.bg_opp)
|
val typedValue = TypedValue()
|
||||||
|
this@MediaDetailsActivity.theme.resolveAttribute(com.google.android.material.R.attr.colorOnBackground, typedValue, true)
|
||||||
|
val white = typedValue.data
|
||||||
if (media.userStatus != null) {
|
if (media.userStatus != null) {
|
||||||
append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num))
|
append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num))
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorSecondary, typedValue, true)
|
theme.resolveAttribute(
|
||||||
|
com.google.android.material.R.attr.colorSecondary,
|
||||||
|
typedValue,
|
||||||
|
true
|
||||||
|
)
|
||||||
bold { color(typedValue.data) { append("${media.userProgress}") } }
|
bold { color(typedValue.data) { append("${media.userProgress}") } }
|
||||||
append(if (media.anime != null) getString(R.string.episodes_out_of) else getString(R.string.chapters_out_of))
|
append(
|
||||||
|
if (media.anime != null) getString(R.string.episodes_out_of) else getString(
|
||||||
|
R.string.chapters_out_of
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
append(if (media.anime != null) getString(R.string.episodes_total_of) else getString(R.string.chapters_total_of))
|
append(
|
||||||
|
if (media.anime != null) getString(R.string.episodes_total_of) else getString(
|
||||||
|
R.string.chapters_total_of
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (media.anime != null) {
|
if (media.anime != null) {
|
||||||
if (media.anime!!.nextAiringEpisode != null) {
|
if (media.anime!!.nextAiringEpisode != null) {
|
||||||
@@ -206,8 +228,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
fun progress() {
|
fun progress() {
|
||||||
val statuses: Array<String> = resources.getStringArray(R.array.status)
|
val statuses: Array<String> = resources.getStringArray(R.array.status)
|
||||||
val statusStrings = if (media.manga==null) resources.getStringArray(R.array.status_anime) else resources.getStringArray(R.array.status_manga)
|
val statusStrings =
|
||||||
val userStatus = if(media.userStatus != null) statusStrings[statuses.indexOf(media.userStatus)] else statusStrings[0]
|
if (media.manga == null) resources.getStringArray(R.array.status_anime) else resources.getStringArray(
|
||||||
|
R.array.status_manga
|
||||||
|
)
|
||||||
|
val userStatus =
|
||||||
|
if (media.userStatus != null) statusStrings[statuses.indexOf(media.userStatus)] else statusStrings[0]
|
||||||
|
|
||||||
if (media.userStatus != null) {
|
if (media.userStatus != null) {
|
||||||
binding.mediaTotal.visibility = View.VISIBLE
|
binding.mediaTotal.visibility = View.VISIBLE
|
||||||
@@ -234,7 +260,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
if (it != null) {
|
if (it != null) {
|
||||||
media = it
|
media = it
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if(media.isFav!=favButton?.clicked) favButton?.clicked()
|
if (media.isFav != favButton?.clicked) favButton?.clicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.mediaNotify.setOnClickListener {
|
binding.mediaNotify.setOnClickListener {
|
||||||
@@ -258,10 +284,15 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
tabLayout.menu.clear()
|
tabLayout.menu.clear()
|
||||||
if (media.anime != null) {
|
if (media.anime != null) {
|
||||||
viewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, SupportedMedia.ANIME)
|
viewPager.adapter =
|
||||||
|
ViewPagerAdapter(supportFragmentManager, lifecycle, SupportedMedia.ANIME)
|
||||||
tabLayout.inflateMenu(R.menu.anime_menu_detail)
|
tabLayout.inflateMenu(R.menu.anime_menu_detail)
|
||||||
} else if (media.manga != null) {
|
} else if (media.manga != null) {
|
||||||
viewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, if(media.format=="NOVEL") SupportedMedia.NOVEL else SupportedMedia.MANGA)
|
viewPager.adapter = ViewPagerAdapter(
|
||||||
|
supportFragmentManager,
|
||||||
|
lifecycle,
|
||||||
|
if (media.format == "NOVEL") SupportedMedia.NOVEL else SupportedMedia.MANGA
|
||||||
|
)
|
||||||
tabLayout.inflateMenu(R.menu.manga_menu_detail)
|
tabLayout.inflateMenu(R.menu.manga_menu_detail)
|
||||||
anime = false
|
anime = false
|
||||||
}
|
}
|
||||||
@@ -303,9 +334,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
private fun selectFromID(id: Int) {
|
private fun selectFromID(id: Int) {
|
||||||
when (id) {
|
when (id) {
|
||||||
R.id.info -> {
|
R.id.info -> {
|
||||||
selected = 0
|
selected = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.watch, R.id.read -> {
|
R.id.watch, R.id.read -> {
|
||||||
selected = 1
|
selected = 1
|
||||||
}
|
}
|
||||||
@@ -329,9 +361,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class SupportedMedia{
|
private enum class SupportedMedia {
|
||||||
ANIME, MANGA, NOVEL
|
ANIME, MANGA, NOVEL
|
||||||
}
|
}
|
||||||
|
|
||||||
//ViewPager
|
//ViewPager
|
||||||
private class ViewPagerAdapter(
|
private class ViewPagerAdapter(
|
||||||
fragmentManager: FragmentManager,
|
fragmentManager: FragmentManager,
|
||||||
@@ -342,13 +375,14 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
override fun getItemCount(): Int = 2
|
override fun getItemCount(): Int = 2
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = when (position){
|
override fun createFragment(position: Int): Fragment = when (position) {
|
||||||
0 -> MediaInfoFragment()
|
0 -> MediaInfoFragment()
|
||||||
1 -> when(media){
|
1 -> when (media) {
|
||||||
SupportedMedia.ANIME -> AnimeWatchFragment()
|
SupportedMedia.ANIME -> AnimeWatchFragment()
|
||||||
SupportedMedia.MANGA -> MangaReadFragment()
|
SupportedMedia.MANGA -> MangaReadFragment()
|
||||||
SupportedMedia.NOVEL -> NovelReadFragment()
|
SupportedMedia.NOVEL -> NovelReadFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> MediaInfoFragment()
|
else -> MediaInfoFragment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,27 +397,41 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
if (mMaxScrollSize == 0) mMaxScrollSize = appBar.totalScrollRange
|
if (mMaxScrollSize == 0) mMaxScrollSize = appBar.totalScrollRange
|
||||||
val percentage = abs(i) * 100 / mMaxScrollSize
|
val percentage = abs(i) * 100 / mMaxScrollSize
|
||||||
|
|
||||||
binding.mediaCover.visibility = if (binding.mediaCover.scaleX == 0f) View.GONE else View.VISIBLE
|
binding.mediaCover.visibility =
|
||||||
|
if (binding.mediaCover.scaleX == 0f) View.GONE else View.VISIBLE
|
||||||
val duration = (200 * uiSettings.animationSpeed).toLong()
|
val duration = (200 * uiSettings.animationSpeed).toLong()
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
this@MediaDetailsActivity.theme.resolveAttribute(com.google.android.material.R.attr.colorSecondary, typedValue, true)
|
||||||
|
val color = typedValue.data
|
||||||
if (percentage >= percent && !isCollapsed) {
|
if (percentage >= percent && !isCollapsed) {
|
||||||
isCollapsed = true
|
isCollapsed = true
|
||||||
ObjectAnimator.ofFloat(binding.mediaTitle, "translationX", 0f).setDuration(duration).start()
|
ObjectAnimator.ofFloat(binding.mediaTitle, "translationX", 0f).setDuration(duration)
|
||||||
ObjectAnimator.ofFloat(binding.mediaAccessContainer, "translationX", screenWidth).setDuration(duration).start()
|
.start()
|
||||||
ObjectAnimator.ofFloat(binding.mediaCover, "translationX", screenWidth).setDuration(duration).start()
|
ObjectAnimator.ofFloat(binding.mediaAccessContainer, "translationX", screenWidth)
|
||||||
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", screenWidth).setDuration(duration).start()
|
.setDuration(duration).start()
|
||||||
|
ObjectAnimator.ofFloat(binding.mediaCover, "translationX", screenWidth)
|
||||||
|
.setDuration(duration).start()
|
||||||
|
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", screenWidth)
|
||||||
|
.setDuration(duration).start()
|
||||||
binding.mediaBanner.pause()
|
binding.mediaBanner.pause()
|
||||||
if (!uiSettings.immersiveMode) this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg)
|
if (!uiSettings.immersiveMode) this.window.statusBarColor = color
|
||||||
}
|
}
|
||||||
if (percentage <= percent && isCollapsed) {
|
if (percentage <= percent && isCollapsed) {
|
||||||
isCollapsed = false
|
isCollapsed = false
|
||||||
ObjectAnimator.ofFloat(binding.mediaTitle, "translationX", -screenWidth).setDuration(duration).start()
|
ObjectAnimator.ofFloat(binding.mediaTitle, "translationX", -screenWidth)
|
||||||
ObjectAnimator.ofFloat(binding.mediaAccessContainer, "translationX", 0f).setDuration(duration).start()
|
.setDuration(duration).start()
|
||||||
ObjectAnimator.ofFloat(binding.mediaCover, "translationX", 0f).setDuration(duration).start()
|
ObjectAnimator.ofFloat(binding.mediaAccessContainer, "translationX", 0f)
|
||||||
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", 0f).setDuration(duration).start()
|
.setDuration(duration).start()
|
||||||
|
ObjectAnimator.ofFloat(binding.mediaCover, "translationX", 0f).setDuration(duration)
|
||||||
|
.start()
|
||||||
|
ObjectAnimator.ofFloat(binding.mediaCollapseContainer, "translationX", 0f)
|
||||||
|
.setDuration(duration).start()
|
||||||
if (uiSettings.bannerAnimations) binding.mediaBanner.resume()
|
if (uiSettings.bannerAnimations) binding.mediaBanner.resume()
|
||||||
if (!uiSettings.immersiveMode) this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg_inv)
|
if (!uiSettings.immersiveMode) this.window.statusBarColor = color
|
||||||
}
|
}
|
||||||
if (percentage == 1 && model.scrolledToTop.value != false) model.scrolledToTop.postValue(false)
|
if (percentage == 1 && model.scrolledToTop.value != false) model.scrolledToTop.postValue(
|
||||||
|
false
|
||||||
|
)
|
||||||
if (percentage == 0 && model.scrolledToTop.value != true) model.scrolledToTop.postValue(true)
|
if (percentage == 0 && model.scrolledToTop.value != true) model.scrolledToTop.postValue(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +474,8 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
ObjectAnimator.ofFloat(image, "scaleY", 1f, 0f).setDuration(100).start()
|
ObjectAnimator.ofFloat(image, "scaleY", 1f, 0f).setDuration(100).start()
|
||||||
delay(100)
|
delay(100)
|
||||||
if (clicked) {
|
if (clicked) {
|
||||||
ObjectAnimator.ofArgb(image,
|
ObjectAnimator.ofArgb(
|
||||||
|
image,
|
||||||
"ColorFilter",
|
"ColorFilter",
|
||||||
ContextCompat.getColor(context, c1),
|
ContextCompat.getColor(context, c1),
|
||||||
ContextCompat.getColor(context, c2)
|
ContextCompat.getColor(context, c2)
|
||||||
@@ -449,7 +498,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
|
|
||||||
fun enabled(enabled: Boolean) {
|
fun enabled(enabled: Boolean) {
|
||||||
disabled = !enabled
|
disabled = !enabled
|
||||||
image.alpha = if(disabled) 0.33f else 1f
|
image.alpha = if (disabled) 0.33f else 1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
@@ -28,10 +30,20 @@ import ani.dantotsu.snackString
|
|||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
|
import ani.dantotsu.parsers.AniyomiAdapter
|
||||||
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
|
import ani.dantotsu.parsers.HAnimeSources
|
||||||
|
import ani.dantotsu.parsers.HMangaSources
|
||||||
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MediaDetailsViewModel : ViewModel() {
|
class MediaDetailsViewModel : ViewModel() {
|
||||||
val scrolledToTop = MutableLiveData(true)
|
val scrolledToTop = MutableLiveData(true)
|
||||||
@@ -40,16 +52,26 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
saveData("$id-select", data, activity)
|
saveData("$id-select", data, activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun loadSelected(media: Media): Selected {
|
fun loadSelected(media: Media): Selected {
|
||||||
return loadData<Selected>("${media.id}-select") ?: Selected().let {
|
val sharedPreferences = Injekt.get<SharedPreferences>()
|
||||||
it.source = if (media.isAdult) 0 else when (media.anime != null) {
|
val data = loadData<Selected>("${media.id}-select") ?: Selected().let {
|
||||||
true -> loadData("settings_def_anime_source") ?: 0
|
it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) {
|
||||||
else -> loadData("settings_def_manga_source") ?: 0
|
true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0)
|
||||||
|
else -> sharedPreferences.getInt(("settings_def_manga_source_s_r"), 0)
|
||||||
}
|
}
|
||||||
it.preferDub = loadData("settings_prefer_dub") ?: false
|
it.preferDub = loadData("settings_prefer_dub") ?: false
|
||||||
saveSelected(media.id, it)
|
saveSelected(media.id, it)
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSelectedStringLocation(sourceName: String): Int {
|
||||||
|
//find the location of the source in the list
|
||||||
|
var location = watchSources?.list?.indexOfFirst { it.name == sourceName } ?: 0
|
||||||
|
if (location == -1) {location = 0}
|
||||||
|
return location
|
||||||
}
|
}
|
||||||
|
|
||||||
var continueMedia: Boolean? = null
|
var continueMedia: Boolean? = null
|
||||||
@@ -167,7 +189,8 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
val server = selected.server ?: return false
|
val server = selected.server ?: return false
|
||||||
val link = ep.link ?: return false
|
val link = ep.link ?: return false
|
||||||
|
|
||||||
ep.extractors = mutableListOf(watchSources?.get(selected.source)?.let {
|
ep.extractors = mutableListOf(watchSources?.get(selected.sourceIndex)?.let {
|
||||||
|
selected.sourceIndex = selected.sourceIndex
|
||||||
if (!post && !it.allowsPreloading) null
|
if (!post && !it.allowsPreloading) null
|
||||||
else ep.sEpisode?.let { it1 ->
|
else ep.sEpisode?.let { it1 ->
|
||||||
it.loadSingleVideoServer(server, link, ep.extra,
|
it.loadSingleVideoServer(server, link, ep.extra,
|
||||||
@@ -238,7 +261,7 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, post: Boolean = true): Boolean {
|
suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, post: Boolean = true): Boolean {
|
||||||
return tryWithSuspend(true) {
|
return tryWithSuspend(true) {
|
||||||
chapter.addImages(
|
chapter.addImages(
|
||||||
mangaReadSources?.get(selected.source)?.loadImages(chapter.link) ?: return@tryWithSuspend false
|
mangaReadSources?.get(selected.sourceIndex)?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false
|
||||||
)
|
)
|
||||||
if (post) mangaChapter.postValue(chapter)
|
if (post) mangaChapter.postValue(chapter)
|
||||||
true
|
true
|
||||||
@@ -261,7 +284,7 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun autoSearchNovels(media: Media) {
|
suspend fun autoSearchNovels(media: Media) {
|
||||||
val source = novelSources[media.selected?.source ?: 0]
|
val source = novelSources[media.selected?.sourceIndex?:0]
|
||||||
tryWithSuspend(post = true) {
|
tryWithSuspend(post = true) {
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
novelResponses.postValue(source.sortedSearch(media))
|
novelResponses.postValue(source.sortedSearch(media))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import ani.dantotsu.connections.anilist.Anilist
|
|||||||
import ani.dantotsu.databinding.BottomSheetMediaListSmallBinding
|
import ani.dantotsu.databinding.BottomSheetMediaListSmallBinding
|
||||||
import ani.dantotsu.connections.mal.MAL
|
import ani.dantotsu.connections.mal.MAL
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import ani.dantotsu.connections.anilist.Anilist
|
|||||||
import ani.dantotsu.connections.anilist.AnilistSearch
|
import ani.dantotsu.connections.anilist.AnilistSearch
|
||||||
import ani.dantotsu.connections.anilist.SearchResults
|
import ani.dantotsu.connections.anilist.SearchResults
|
||||||
import ani.dantotsu.databinding.ActivitySearchBinding
|
import ani.dantotsu.databinding.ActivitySearchBinding
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -37,6 +38,7 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivitySearchBinding.inflate(layoutInflater)
|
binding = ActivitySearchBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ data class Selected(
|
|||||||
var recyclerStyle: Int? = null,
|
var recyclerStyle: Int? = null,
|
||||||
var recyclerReversed: Boolean = false,
|
var recyclerReversed: Boolean = false,
|
||||||
var chip: Int = 0,
|
var chip: Int = 0,
|
||||||
var source: Int = 0,
|
//var source: String = "",
|
||||||
|
var sourceIndex: Int = 0,
|
||||||
var preferDub: Boolean = false,
|
var preferDub: Boolean = false,
|
||||||
var server: String? = null,
|
var server: String? = null,
|
||||||
var video: Int = 0,
|
var video: Int = 0,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() {
|
|||||||
binding.searchRecyclerView.visibility = View.GONE
|
binding.searchRecyclerView.visibility = View.GONE
|
||||||
binding.searchProgress.visibility = View.VISIBLE
|
binding.searchProgress.visibility = View.VISIBLE
|
||||||
|
|
||||||
i = media!!.selected!!.source
|
i = media!!.selected!!.sourceIndex
|
||||||
|
|
||||||
val source = if (media!!.anime != null) {
|
val source = if (media!!.anime != null) {
|
||||||
(if (!media!!.isAdult) AnimeSources else HAnimeSources)[i!!]
|
(if (!media!!.isAdult) AnimeSources else HAnimeSources)[i!!]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.ActivityStudioBinding
|
import ani.dantotsu.databinding.ActivityStudioBinding
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -28,6 +29,7 @@ class StudioActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityStudioBinding.inflate(layoutInflater)
|
binding = ActivityStudioBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
48
app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt
Normal file
48
app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Environment
|
||||||
|
import ani.dantotsu.parsers.SubtitleType
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import okhttp3.Request
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
class SubtitleDownloader {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
//doesn't really download the subtitles -\_(o_o)_/-
|
||||||
|
suspend fun downloadSubtitles(context: Context, url: String): SubtitleType =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
|
||||||
|
val networkHelper = Injekt.get<NetworkHelper>()
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val response = networkHelper.client.newCall(request).execute()
|
||||||
|
|
||||||
|
// Check if response is successful
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val responseBody = response.body?.string()
|
||||||
|
|
||||||
|
|
||||||
|
val subtitleType = when {
|
||||||
|
responseBody?.contains("[Script Info]") == true -> SubtitleType.ASS
|
||||||
|
responseBody?.contains("WEBVTT") == true -> SubtitleType.VTT
|
||||||
|
else -> SubtitleType.SRT
|
||||||
|
}
|
||||||
|
|
||||||
|
return@withContext subtitleType
|
||||||
|
} else {
|
||||||
|
return@withContext SubtitleType.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,7 +68,7 @@ class AnimeWatchAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Source Selection
|
//Source Selection
|
||||||
val source = media.selected!!.source.let { if (it >= watchSources.names.size) 0 else it }
|
val source = media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it }
|
||||||
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
||||||
binding.animeSource.setText(watchSources.names[source])
|
binding.animeSource.setText(watchSources.names[source])
|
||||||
watchSources[source].apply {
|
watchSources[source].apply {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
async { model.loadKitsuEpisodes(media) },
|
async { model.loadKitsuEpisodes(media) },
|
||||||
async { model.loadFillerEpisodes(media) }
|
async { model.loadFillerEpisodes(media) }
|
||||||
)
|
)
|
||||||
model.loadEpisodes(media, media.selected!!.source)
|
model.loadEpisodes(media, media.selected!!.sourceIndex)
|
||||||
}
|
}
|
||||||
loaded = true
|
loaded = true
|
||||||
} else {
|
} else {
|
||||||
@@ -140,7 +140,7 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
model.getEpisodes().observe(viewLifecycleOwner) { loadedEpisodes ->
|
model.getEpisodes().observe(viewLifecycleOwner) { loadedEpisodes ->
|
||||||
if (loadedEpisodes != null) {
|
if (loadedEpisodes != null) {
|
||||||
val episodes = loadedEpisodes[media.selected!!.source]
|
val episodes = loadedEpisodes[media.selected!!.sourceIndex]
|
||||||
if (episodes != null) {
|
if (episodes != null) {
|
||||||
episodes.forEach { (i, episode) ->
|
episodes.forEach { (i, episode) ->
|
||||||
if (media.anime?.fillerEpisodes != null) {
|
if (media.anime?.fillerEpisodes != null) {
|
||||||
@@ -206,8 +206,8 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
media.anime?.episodes = null
|
media.anime?.episodes = null
|
||||||
reload()
|
reload()
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
model.watchSources?.get(selected.source)?.showUserTextListener = null
|
model.watchSources?.get(selected.sourceIndex)?.showUserTextListener = null
|
||||||
selected.source = i
|
selected.sourceIndex = i
|
||||||
selected.server = null
|
selected.server = null
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
@@ -216,11 +216,11 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
|
|
||||||
fun onDubClicked(checked: Boolean) {
|
fun onDubClicked(checked: Boolean) {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
model.watchSources?.get(selected.source)?.selectDub = checked
|
model.watchSources?.get(selected.sourceIndex)?.selectDub = checked
|
||||||
selected.preferDub = checked
|
selected.preferDub = checked
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
lifecycleScope.launch(Dispatchers.IO) { model.forceLoadEpisode(media, selected.source) }
|
lifecycleScope.launch(Dispatchers.IO) { model.forceLoadEpisode(media, selected.sourceIndex) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadEpisodes(i: Int) {
|
fun loadEpisodes(i: Int) {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import androidx.media3.datasource.okhttp.OkHttpDataSource
|
|||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
|
import androidx.media3.exoplayer.util.EventLogger
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import androidx.media3.ui.*
|
import androidx.media3.ui.*
|
||||||
import androidx.media3.ui.CaptionStyleCompat.*
|
import androidx.media3.ui.CaptionStyleCompat.*
|
||||||
@@ -65,6 +66,7 @@ import ani.dantotsu.connections.updateProgress
|
|||||||
import ani.dantotsu.databinding.ActivityExoplayerBinding
|
import ani.dantotsu.databinding.ActivityExoplayerBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
|
import ani.dantotsu.media.SubtitleDownloader
|
||||||
import ani.dantotsu.others.AniSkip
|
import ani.dantotsu.others.AniSkip
|
||||||
import ani.dantotsu.others.AniSkip.getType
|
import ani.dantotsu.others.AniSkip.getType
|
||||||
import ani.dantotsu.others.Download.download
|
import ani.dantotsu.others.Download.download
|
||||||
@@ -74,6 +76,7 @@ import ani.dantotsu.parsers.*
|
|||||||
import ani.dantotsu.settings.PlayerSettings
|
import ani.dantotsu.settings.PlayerSettings
|
||||||
import ani.dantotsu.settings.PlayerSettingsActivity
|
import ani.dantotsu.settings.PlayerSettingsActivity
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import com.lagradost.nicehttp.ignoreAllSSLErrors
|
import com.lagradost.nicehttp.ignoreAllSSLErrors
|
||||||
@@ -81,12 +84,15 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import okhttp3.internal.immutableListOf
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||||
class ExoplayerView : AppCompatActivity(), Player.Listener {
|
class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
@@ -183,7 +189,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
val displayCutout = window.decorView.rootWindowInsets.displayCutout
|
val displayCutout = window.decorView.rootWindowInsets.displayCutout
|
||||||
if (displayCutout != null) {
|
if (displayCutout != null) {
|
||||||
if (displayCutout.boundingRects.size > 0) {
|
if (displayCutout.boundingRects.size > 0) {
|
||||||
notchHeight = min(displayCutout.boundingRects[0].width(), displayCutout.boundingRects[0].height())
|
notchHeight = min(
|
||||||
|
displayCutout.boundingRects[0].width(),
|
||||||
|
displayCutout.boundingRects[0].height()
|
||||||
|
)
|
||||||
checkNotch()
|
checkNotch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,101 +203,104 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
private fun checkNotch() {
|
private fun checkNotch() {
|
||||||
if (notchHeight != 0) {
|
if (notchHeight != 0) {
|
||||||
val orientation = resources.configuration.orientation
|
val orientation = resources.configuration.orientation
|
||||||
playerView.findViewById<View>(R.id.exo_controller_margin).updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
playerView.findViewById<View>(R.id.exo_controller_margin)
|
||||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
marginStart = notchHeight
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
marginEnd = notchHeight
|
marginStart = notchHeight
|
||||||
topMargin = 0
|
marginEnd = notchHeight
|
||||||
} else {
|
topMargin = 0
|
||||||
topMargin = notchHeight
|
} else {
|
||||||
marginStart = 0
|
topMargin = notchHeight
|
||||||
marginEnd = 0
|
marginStart = 0
|
||||||
|
marginEnd = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
playerView.findViewById<View>(androidx.media3.ui.R.id.exo_buffering).translationY =
|
playerView.findViewById<View>(androidx.media3.ui.R.id.exo_buffering).translationY =
|
||||||
(if (orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else (notchHeight + 8f.px)).dp
|
(if (orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else (notchHeight + 8f.px)).dp
|
||||||
exoBrightnessCont.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
exoBrightnessCont.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
marginEnd = if (orientation == Configuration.ORIENTATION_LANDSCAPE) notchHeight else 0
|
marginEnd =
|
||||||
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) notchHeight else 0
|
||||||
}
|
}
|
||||||
exoVolumeCont.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
exoVolumeCont.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
marginStart = if (orientation == Configuration.ORIENTATION_LANDSCAPE) notchHeight else 0
|
marginStart =
|
||||||
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) notchHeight else 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSubFormatting(playerView: PlayerView, settings: PlayerSettings) {
|
private fun setupSubFormatting(playerView: PlayerView, settings: PlayerSettings) {
|
||||||
val primaryColor = when (settings.primaryColor) {
|
val primaryColor = when (settings.primaryColor) {
|
||||||
0 -> Color.BLACK
|
0 -> Color.BLACK
|
||||||
1 -> Color.DKGRAY
|
1 -> Color.DKGRAY
|
||||||
2 -> Color.GRAY
|
2 -> Color.GRAY
|
||||||
3 -> Color.LTGRAY
|
3 -> Color.LTGRAY
|
||||||
4 -> Color.WHITE
|
4 -> Color.WHITE
|
||||||
5 -> Color.RED
|
5 -> Color.RED
|
||||||
6 -> Color.YELLOW
|
6 -> Color.YELLOW
|
||||||
7 -> Color.GREEN
|
7 -> Color.GREEN
|
||||||
8 -> Color.CYAN
|
8 -> Color.CYAN
|
||||||
9 -> Color.BLUE
|
9 -> Color.BLUE
|
||||||
10 -> Color.MAGENTA
|
10 -> Color.MAGENTA
|
||||||
11 -> Color.TRANSPARENT
|
11 -> Color.TRANSPARENT
|
||||||
else -> Color.WHITE
|
else -> Color.WHITE
|
||||||
}
|
}
|
||||||
val secondaryColor = when (settings.secondaryColor) {
|
val secondaryColor = when (settings.secondaryColor) {
|
||||||
0 -> Color.BLACK
|
0 -> Color.BLACK
|
||||||
1 -> Color.DKGRAY
|
1 -> Color.DKGRAY
|
||||||
2 -> Color.GRAY
|
2 -> Color.GRAY
|
||||||
3 -> Color.LTGRAY
|
3 -> Color.LTGRAY
|
||||||
4 -> Color.WHITE
|
4 -> Color.WHITE
|
||||||
5 -> Color.RED
|
5 -> Color.RED
|
||||||
6 -> Color.YELLOW
|
6 -> Color.YELLOW
|
||||||
7 -> Color.GREEN
|
7 -> Color.GREEN
|
||||||
8 -> Color.CYAN
|
8 -> Color.CYAN
|
||||||
9 -> Color.BLUE
|
9 -> Color.BLUE
|
||||||
10 -> Color.MAGENTA
|
10 -> Color.MAGENTA
|
||||||
11 -> Color.TRANSPARENT
|
11 -> Color.TRANSPARENT
|
||||||
else -> Color.BLACK
|
else -> Color.BLACK
|
||||||
}
|
}
|
||||||
val outline = when (settings.outline) {
|
val outline = when (settings.outline) {
|
||||||
0 -> EDGE_TYPE_OUTLINE // Normal
|
0 -> EDGE_TYPE_OUTLINE // Normal
|
||||||
1 -> EDGE_TYPE_DEPRESSED // Shine
|
1 -> EDGE_TYPE_DEPRESSED // Shine
|
||||||
2 -> EDGE_TYPE_DROP_SHADOW // Drop shadow
|
2 -> EDGE_TYPE_DROP_SHADOW // Drop shadow
|
||||||
3 -> EDGE_TYPE_NONE // No outline
|
3 -> EDGE_TYPE_NONE // No outline
|
||||||
else -> EDGE_TYPE_OUTLINE // Normal
|
else -> EDGE_TYPE_OUTLINE // Normal
|
||||||
}
|
}
|
||||||
val subBackground = when (settings.subBackground) {
|
val subBackground = when (settings.subBackground) {
|
||||||
0 -> Color.TRANSPARENT
|
0 -> Color.TRANSPARENT
|
||||||
1 -> Color.BLACK
|
1 -> Color.BLACK
|
||||||
2 -> Color.DKGRAY
|
2 -> Color.DKGRAY
|
||||||
3 -> Color.GRAY
|
3 -> Color.GRAY
|
||||||
4 -> Color.LTGRAY
|
4 -> Color.LTGRAY
|
||||||
5 -> Color.WHITE
|
5 -> Color.WHITE
|
||||||
6 -> Color.RED
|
6 -> Color.RED
|
||||||
7 -> Color.YELLOW
|
7 -> Color.YELLOW
|
||||||
8 -> Color.GREEN
|
8 -> Color.GREEN
|
||||||
9 -> Color.CYAN
|
9 -> Color.CYAN
|
||||||
10 -> Color.BLUE
|
10 -> Color.BLUE
|
||||||
11 -> Color.MAGENTA
|
11 -> Color.MAGENTA
|
||||||
else -> Color.TRANSPARENT
|
else -> Color.TRANSPARENT
|
||||||
}
|
}
|
||||||
val subWindow = when (settings.subWindow) {
|
val subWindow = when (settings.subWindow) {
|
||||||
0 -> Color.TRANSPARENT
|
0 -> Color.TRANSPARENT
|
||||||
1 -> Color.BLACK
|
1 -> Color.BLACK
|
||||||
2 -> Color.DKGRAY
|
2 -> Color.DKGRAY
|
||||||
3 -> Color.GRAY
|
3 -> Color.GRAY
|
||||||
4 -> Color.LTGRAY
|
4 -> Color.LTGRAY
|
||||||
5 -> Color.WHITE
|
5 -> Color.WHITE
|
||||||
6 -> Color.RED
|
6 -> Color.RED
|
||||||
7 -> Color.YELLOW
|
7 -> Color.YELLOW
|
||||||
8 -> Color.GREEN
|
8 -> Color.GREEN
|
||||||
9 -> Color.CYAN
|
9 -> Color.CYAN
|
||||||
10 -> Color.BLUE
|
10 -> Color.BLUE
|
||||||
11 -> Color.MAGENTA
|
11 -> Color.MAGENTA
|
||||||
else -> Color.TRANSPARENT
|
else -> Color.TRANSPARENT
|
||||||
}
|
}
|
||||||
val font = when (settings.font) {
|
val font = when (settings.font) {
|
||||||
0 -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
|
0 -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
|
||||||
1 -> ResourcesCompat.getFont(this, R.font.poppins_bold)
|
1 -> ResourcesCompat.getFont(this, R.font.poppins_bold)
|
||||||
2 -> ResourcesCompat.getFont(this, R.font.poppins)
|
2 -> ResourcesCompat.getFont(this, R.font.poppins)
|
||||||
3 -> ResourcesCompat.getFont(this, R.font.poppins_thin)
|
3 -> ResourcesCompat.getFont(this, R.font.poppins_thin)
|
||||||
else -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
|
else -> ResourcesCompat.getFont(this, R.font.poppins_semi_bold)
|
||||||
}
|
}
|
||||||
playerView.subtitleView?.setStyle(
|
playerView.subtitleView?.setStyle(
|
||||||
@@ -305,6 +317,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityExoplayerBinding.inflate(layoutInflater)
|
binding = ActivityExoplayerBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -316,8 +329,18 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
finishAndRemoveTask()
|
finishAndRemoveTask()
|
||||||
}
|
}
|
||||||
|
|
||||||
settings = loadData("player_settings") ?: PlayerSettings().apply { saveData("player_settings", this) }
|
settings = loadData("player_settings") ?: PlayerSettings().apply {
|
||||||
uiSettings = loadData("ui_settings") ?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
saveData(
|
||||||
|
"player_settings",
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
uiSettings = loadData("ui_settings") ?: UserInterfaceSettings().apply {
|
||||||
|
saveData(
|
||||||
|
"ui_settings",
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
playerView = findViewById(R.id.player_view)
|
playerView = findViewById(R.id.player_view)
|
||||||
exoQuality = playerView.findViewById(R.id.exo_quality)
|
exoQuality = playerView.findViewById(R.id.exo_quality)
|
||||||
@@ -360,17 +383,20 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
requestedOrientation = rotation
|
requestedOrientation = rotation
|
||||||
it.visibility = View.GONE
|
it.visibility = View.GONE
|
||||||
}
|
}
|
||||||
orientationListener = object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
orientationListener =
|
||||||
override fun onOrientationChanged(orientation: Int) {
|
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||||
if (orientation in 45..135) {
|
override fun onOrientationChanged(orientation: Int) {
|
||||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility = View.VISIBLE
|
if (orientation in 45..135) {
|
||||||
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility =
|
||||||
} else if (orientation in 225..315) {
|
View.VISIBLE
|
||||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) exoRotate.visibility = View.VISIBLE
|
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||||
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
} else if (orientation in 225..315) {
|
||||||
|
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) exoRotate.visibility =
|
||||||
|
View.VISIBLE
|
||||||
|
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
orientationListener?.enable()
|
orientationListener?.enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +404,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
|
|
||||||
playerView.subtitleView?.alpha = when (settings.subtitles) {
|
playerView.subtitleView?.alpha = when (settings.subtitles) {
|
||||||
true -> 1f
|
true -> 1f
|
||||||
false -> 0f
|
false -> 0f
|
||||||
}
|
}
|
||||||
val fontSize = settings.fontSize.toFloat()
|
val fontSize = settings.fontSize.toFloat()
|
||||||
@@ -401,7 +427,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
isTimeStampsLoaded = true
|
isTimeStampsLoaded = true
|
||||||
exoSkipOpEd.visibility = if (it != null) {
|
exoSkipOpEd.visibility = if (it != null) {
|
||||||
val adGroups = it.flatMap {
|
val adGroups = it.flatMap {
|
||||||
listOf(it.interval.startTime.toLong() * 1000, it.interval.endTime.toLong() * 1000)
|
listOf(
|
||||||
|
it.interval.startTime.toLong() * 1000,
|
||||||
|
it.interval.endTime.toLong() * 1000
|
||||||
|
)
|
||||||
}.toLongArray()
|
}.toLongArray()
|
||||||
val playedAdGroups = it.flatMap {
|
val playedAdGroups = it.flatMap {
|
||||||
listOf(false, false)
|
listOf(false, false)
|
||||||
@@ -441,7 +470,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
// Picture-in-picture
|
// Picture-in-picture
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
pipEnabled = packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && settings.pip
|
pipEnabled =
|
||||||
|
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && settings.pip
|
||||||
if (pipEnabled) {
|
if (pipEnabled) {
|
||||||
exoPip.visibility = View.VISIBLE
|
exoPip.visibility = View.VISIBLE
|
||||||
exoPip.setOnClickListener {
|
exoPip.setOnClickListener {
|
||||||
@@ -456,7 +486,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
val container = playerView.findViewById<View>(R.id.exo_controller_cont)
|
val container = playerView.findViewById<View>(R.id.exo_controller_cont)
|
||||||
val screen = playerView.findViewById<View>(R.id.exo_black_screen)
|
val screen = playerView.findViewById<View>(R.id.exo_black_screen)
|
||||||
val lockButton = playerView.findViewById<ImageButton>(R.id.exo_unlock)
|
val lockButton = playerView.findViewById<ImageButton>(R.id.exo_unlock)
|
||||||
val timeline = playerView.findViewById<ExtendedTimeBar>(androidx.media3.ui.R.id.exo_progress)
|
val timeline =
|
||||||
|
playerView.findViewById<ExtendedTimeBar>(androidx.media3.ui.R.id.exo_progress)
|
||||||
playerView.findViewById<ImageButton>(R.id.exo_lock).setOnClickListener {
|
playerView.findViewById<ImageButton>(R.id.exo_lock).setOnClickListener {
|
||||||
locked = true
|
locked = true
|
||||||
screen.visibility = View.GONE
|
screen.visibility = View.GONE
|
||||||
@@ -496,17 +527,22 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
dialog.findViewById<Slider>(R.id.seekbar).addOnChangeListener { _, value, _ ->
|
dialog.findViewById<Slider>(R.id.seekbar).addOnChangeListener { _, value, _ ->
|
||||||
settings.skipTime = value.toInt()
|
settings.skipTime = value.toInt()
|
||||||
saveData(player, settings)
|
saveData(player, settings)
|
||||||
playerView.findViewById<TextView>(R.id.exo_skip_time).text = settings.skipTime.toString()
|
playerView.findViewById<TextView>(R.id.exo_skip_time).text =
|
||||||
dialog.findViewById<TextView>(R.id.seekbar_value).text = settings.skipTime.toString()
|
settings.skipTime.toString()
|
||||||
|
dialog.findViewById<TextView>(R.id.seekbar_value).text =
|
||||||
|
settings.skipTime.toString()
|
||||||
}
|
}
|
||||||
dialog.findViewById<Slider>(R.id.seekbar).addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
|
dialog.findViewById<Slider>(R.id.seekbar)
|
||||||
override fun onStartTrackingTouch(slider: Slider) {}
|
.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
|
||||||
override fun onStopTrackingTouch(slider: Slider) {
|
override fun onStartTrackingTouch(slider: Slider) {}
|
||||||
dialog.dismiss()
|
override fun onStopTrackingTouch(slider: Slider) {
|
||||||
}
|
dialog.dismiss()
|
||||||
})
|
}
|
||||||
dialog.findViewById<TextView>(R.id.seekbar_title).text = getString(R.string.skip_time)
|
})
|
||||||
dialog.findViewById<TextView>(R.id.seekbar_value).text = settings.skipTime.toString()
|
dialog.findViewById<TextView>(R.id.seekbar_title).text =
|
||||||
|
getString(R.string.skip_time)
|
||||||
|
dialog.findViewById<TextView>(R.id.seekbar_value).text =
|
||||||
|
settings.skipTime.toString()
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
dialog.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
dialog.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
dialog.show()
|
dialog.show()
|
||||||
@@ -521,7 +557,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
val brightnessRunnable = Runnable {
|
val brightnessRunnable = Runnable {
|
||||||
if (exoBrightnessCont.alpha == 1f)
|
if (exoBrightnessCont.alpha == 1f)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
ObjectAnimator.ofFloat(exoBrightnessCont, "alpha", 1f, 0f).setDuration(gestureSpeed).start()
|
ObjectAnimator.ofFloat(exoBrightnessCont, "alpha", 1f, 0f)
|
||||||
|
.setDuration(gestureSpeed).start()
|
||||||
delay(gestureSpeed)
|
delay(gestureSpeed)
|
||||||
exoBrightnessCont.visibility = View.GONE
|
exoBrightnessCont.visibility = View.GONE
|
||||||
checkNotch()
|
checkNotch()
|
||||||
@@ -530,7 +567,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
val volumeRunnable = Runnable {
|
val volumeRunnable = Runnable {
|
||||||
if (exoVolumeCont.alpha == 1f)
|
if (exoVolumeCont.alpha == 1f)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
ObjectAnimator.ofFloat(exoVolumeCont, "alpha", 1f, 0f).setDuration(gestureSpeed).start()
|
ObjectAnimator.ofFloat(exoVolumeCont, "alpha", 1f, 0f).setDuration(gestureSpeed)
|
||||||
|
.start()
|
||||||
delay(gestureSpeed)
|
delay(gestureSpeed)
|
||||||
exoVolumeCont.visibility = View.GONE
|
exoVolumeCont.visibility = View.GONE
|
||||||
checkNotch()
|
checkNotch()
|
||||||
@@ -548,25 +586,65 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
fun handleController() {
|
fun handleController() {
|
||||||
if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) !isInPictureInPictureMode else true) {
|
if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) !isInPictureInPictureMode else true) {
|
||||||
if (playerView.isControllerFullyVisible) {
|
if (playerView.isControllerFullyVisible) {
|
||||||
ObjectAnimator.ofFloat(playerView.findViewById(R.id.exo_controller), "alpha", 1f, 0f)
|
ObjectAnimator.ofFloat(
|
||||||
|
playerView.findViewById(R.id.exo_controller),
|
||||||
|
"alpha",
|
||||||
|
1f,
|
||||||
|
0f
|
||||||
|
)
|
||||||
.setDuration(controllerDuration).start()
|
.setDuration(controllerDuration).start()
|
||||||
ObjectAnimator.ofFloat(playerView.findViewById(R.id.exo_bottom_cont), "translationY", 0f, 128f)
|
ObjectAnimator.ofFloat(
|
||||||
|
playerView.findViewById(R.id.exo_bottom_cont),
|
||||||
|
"translationY",
|
||||||
|
0f,
|
||||||
|
128f
|
||||||
|
)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
ObjectAnimator.ofFloat(playerView.findViewById(R.id.exo_timeline_cont), "translationY", 0f, 128f)
|
ObjectAnimator.ofFloat(
|
||||||
|
playerView.findViewById(R.id.exo_timeline_cont),
|
||||||
|
"translationY",
|
||||||
|
0f,
|
||||||
|
128f
|
||||||
|
)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
ObjectAnimator.ofFloat(playerView.findViewById(R.id.exo_top_cont), "translationY", 0f, -128f)
|
ObjectAnimator.ofFloat(
|
||||||
|
playerView.findViewById(R.id.exo_top_cont),
|
||||||
|
"translationY",
|
||||||
|
0f,
|
||||||
|
-128f
|
||||||
|
)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
playerView.postDelayed({ playerView.hideController() }, controllerDuration)
|
playerView.postDelayed({ playerView.hideController() }, controllerDuration)
|
||||||
} else {
|
} else {
|
||||||
checkNotch()
|
checkNotch()
|
||||||
playerView.showController()
|
playerView.showController()
|
||||||
ObjectAnimator.ofFloat(playerView.findViewById(R.id.exo_controller), "alpha", 0f, 1f)
|
ObjectAnimator.ofFloat(
|
||||||
|
playerView.findViewById(R.id.exo_controller),
|
||||||
|
"alpha",
|
||||||
|
0f,
|
||||||
|
1f
|
||||||
|
)
|
||||||
.setDuration(controllerDuration).start()
|
.setDuration(controllerDuration).start()
|
||||||
ObjectAnimator.ofFloat(playerView.findViewById(R.id.exo_bottom_cont), "translationY", 128f, 0f)
|
ObjectAnimator.ofFloat(
|
||||||
|
playerView.findViewById(R.id.exo_bottom_cont),
|
||||||
|
"translationY",
|
||||||
|
128f,
|
||||||
|
0f
|
||||||
|
)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
ObjectAnimator.ofFloat(playerView.findViewById(R.id.exo_timeline_cont), "translationY", 128f, 0f)
|
ObjectAnimator.ofFloat(
|
||||||
|
playerView.findViewById(R.id.exo_timeline_cont),
|
||||||
|
"translationY",
|
||||||
|
128f,
|
||||||
|
0f
|
||||||
|
)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
ObjectAnimator.ofFloat(playerView.findViewById(R.id.exo_top_cont), "translationY", -128f, 0f)
|
ObjectAnimator.ofFloat(
|
||||||
|
playerView.findViewById(R.id.exo_top_cont),
|
||||||
|
"translationY",
|
||||||
|
-128f,
|
||||||
|
0f
|
||||||
|
)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -651,8 +729,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.doubleTap) {
|
if (!settings.doubleTap) {
|
||||||
playerView.findViewById<View>(R.id.exo_fast_forward_button_cont).visibility = View.VISIBLE
|
playerView.findViewById<View>(R.id.exo_fast_forward_button_cont).visibility =
|
||||||
playerView.findViewById<View>(R.id.exo_fast_rewind_button_cont).visibility = View.VISIBLE
|
View.VISIBLE
|
||||||
|
playerView.findViewById<View>(R.id.exo_fast_rewind_button_cont).visibility =
|
||||||
|
View.VISIBLE
|
||||||
playerView.findViewById<ImageButton>(R.id.exo_fast_forward_button).setOnClickListener {
|
playerView.findViewById<ImageButton>(R.id.exo_fast_forward_button).setOnClickListener {
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
seek(true)
|
seek(true)
|
||||||
@@ -696,7 +776,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
exoBrightness.addOnChangeListener { _, value, _ ->
|
exoBrightness.addOnChangeListener { _, value, _ ->
|
||||||
val lp = window.attributes
|
val lp = window.attributes
|
||||||
lp.screenBrightness = brightnessConverter((value.takeIf { !it.isNaN() } ?: 0f) / 10, false)
|
lp.screenBrightness =
|
||||||
|
brightnessConverter((value.takeIf { !it.isNaN() } ?: 0f) / 10, false)
|
||||||
window.attributes = lp
|
window.attributes = lp
|
||||||
brightnessHide()
|
brightnessHide()
|
||||||
}
|
}
|
||||||
@@ -757,7 +838,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSingleClick(event: MotionEvent) = if (isSeeking) doubleTap(false, event) else handleController()
|
override fun onSingleClick(event: MotionEvent) =
|
||||||
|
if (isSeeking) doubleTap(false, event) else handleController()
|
||||||
})
|
})
|
||||||
val rewindArea = playerView.findViewById<View>(R.id.exo_rewind_area)
|
val rewindArea = playerView.findViewById<View>(R.id.exo_rewind_area)
|
||||||
rewindArea.isClickable = true
|
rewindArea.isClickable = true
|
||||||
@@ -786,7 +868,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSingleClick(event: MotionEvent) = if (isSeeking) doubleTap(true, event) else handleController()
|
override fun onSingleClick(event: MotionEvent) =
|
||||||
|
if (isSeeking) doubleTap(true, event) else handleController()
|
||||||
})
|
})
|
||||||
val forwardArea = playerView.findViewById<View>(R.id.exo_forward_area)
|
val forwardArea = playerView.findViewById<View>(R.id.exo_forward_area)
|
||||||
forwardArea.isClickable = true
|
forwardArea.isClickable = true
|
||||||
@@ -817,7 +900,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources
|
model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources
|
||||||
serverInfo.text = model.watchSources!!.names.getOrNull(media.selected!!.source) ?: model.watchSources!!.names[0]
|
serverInfo.text = model.watchSources!!.names.getOrNull(media.selected!!.sourceIndex)
|
||||||
|
?: model.watchSources!!.names[0]
|
||||||
|
|
||||||
model.epChanged.observe(this) {
|
model.epChanged.observe(this) {
|
||||||
epChanging = !it
|
epChanging = !it
|
||||||
@@ -906,7 +990,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
rpc?.send {
|
rpc?.send {
|
||||||
type = RPC.Type.WATCHING
|
type = RPC.Type.WATCHING
|
||||||
activityName = media.userPreferredName
|
activityName = media.userPreferredName
|
||||||
details = ep.title?.takeIf { it.isNotEmpty() } ?: getString(R.string.episode_num, ep.number)
|
details = ep.title?.takeIf { it.isNotEmpty() } ?: getString(
|
||||||
|
R.string.episode_num,
|
||||||
|
ep.number
|
||||||
|
)
|
||||||
state = "Episode : ${ep.number}/${media.anime?.totalEpisodes ?: "??"}"
|
state = "Episode : ${ep.number}/${media.anime?.totalEpisodes ?: "??"}"
|
||||||
media.cover?.let { cover ->
|
media.cover?.let { cover ->
|
||||||
largeImage = RPC.Link(media.userPreferredName, cover)
|
largeImage = RPC.Link(media.userPreferredName, cover)
|
||||||
@@ -922,25 +1009,25 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
//FullScreen
|
//FullScreen
|
||||||
isFullscreen = loadData("${media.id}_fullscreenInt", this) ?: isFullscreen
|
isFullscreen = loadData("${media.id}_fullscreenInt", this) ?: isFullscreen
|
||||||
playerView.resizeMode = when (isFullscreen) {
|
playerView.resizeMode = when (isFullscreen) {
|
||||||
0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||||
2 -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
2 -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
||||||
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
}
|
}
|
||||||
|
|
||||||
exoScreen.setOnClickListener {
|
exoScreen.setOnClickListener {
|
||||||
if (isFullscreen < 2) isFullscreen += 1 else isFullscreen = 0
|
if (isFullscreen < 2) isFullscreen += 1 else isFullscreen = 0
|
||||||
playerView.resizeMode = when (isFullscreen) {
|
playerView.resizeMode = when (isFullscreen) {
|
||||||
0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||||
2 -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
2 -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
||||||
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
}
|
}
|
||||||
snackString(
|
snackString(
|
||||||
when (isFullscreen) {
|
when (isFullscreen) {
|
||||||
0 -> "Original"
|
0 -> "Original"
|
||||||
1 -> "Zoom"
|
1 -> "Zoom"
|
||||||
2 -> "Stretch"
|
2 -> "Stretch"
|
||||||
else -> "Original"
|
else -> "Original"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -959,7 +1046,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
//Settings
|
//Settings
|
||||||
exoSettings.setOnClickListener {
|
exoSettings.setOnClickListener {
|
||||||
saveData("${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition, this)
|
saveData(
|
||||||
|
"${media.id}_${media.anime!!.selectedEpisode}",
|
||||||
|
exoPlayer.currentPosition,
|
||||||
|
this
|
||||||
|
)
|
||||||
val intent = Intent(this, PlayerSettingsActivity::class.java).apply {
|
val intent = Intent(this, PlayerSettingsActivity::class.java).apply {
|
||||||
putExtra("subtitle", subtitle)
|
putExtra("subtitle", subtitle)
|
||||||
}
|
}
|
||||||
@@ -979,7 +1070,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
playbackParameters = PlaybackParameters(speeds[curSpeed])
|
playbackParameters = PlaybackParameters(speeds[curSpeed])
|
||||||
var speed: Float
|
var speed: Float
|
||||||
val speedDialog = AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.speed))
|
val speedDialog =
|
||||||
|
AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.speed))
|
||||||
exoSpeed.setOnClickListener {
|
exoSpeed.setOnClickListener {
|
||||||
speedDialog.setSingleChoiceItems(speedsName, curSpeed) { dialog, i ->
|
speedDialog.setSingleChoiceItems(speedsName, curSpeed) { dialog, i ->
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
@@ -1018,16 +1110,19 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
isFullscreen = settings.resize
|
isFullscreen = settings.resize
|
||||||
playerView.resizeMode = when (settings.resize) {
|
playerView.resizeMode = when (settings.resize) {
|
||||||
0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
0 -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
1 -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||||
2 -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
2 -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
||||||
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
}
|
}
|
||||||
|
|
||||||
preloading = false
|
preloading = false
|
||||||
val showProgressDialog = if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog") ?: true else false
|
val showProgressDialog =
|
||||||
|
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog")
|
||||||
|
?: true else false
|
||||||
if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true)
|
if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true)
|
||||||
AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.auto_update, media.userPreferredName))
|
AlertDialog.Builder(this, R.style.DialogTheme)
|
||||||
|
.setTitle(getString(R.string.auto_update, media.userPreferredName))
|
||||||
.apply {
|
.apply {
|
||||||
setOnCancelListener { hideSystemBars() }
|
setOnCancelListener { hideSystemBars() }
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
@@ -1074,16 +1169,16 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
subtitle = intent.getSerialized("subtitle")
|
subtitle = intent.getSerialized("subtitle")
|
||||||
?: when (val subLang: String? = loadData("subLang_${media.id}", this)) {
|
?: when (val subLang: String? = loadData("subLang_${media.id}", this)) {
|
||||||
null -> {
|
null -> {
|
||||||
when (episode.selectedSubtitle) {
|
when (episode.selectedSubtitle) {
|
||||||
null -> null
|
null -> null
|
||||||
-1 -> ext.subtitles.find { it.language.trim() == "English" || it.language == "en-US" }
|
-1 -> ext.subtitles.find { it.language.trim() == "English" || it.language == "en-US" }
|
||||||
else -> ext.subtitles.getOrNull(episode.selectedSubtitle!!)
|
else -> ext.subtitles.getOrNull(episode.selectedSubtitle!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"None" -> ext.subtitles.let { null }
|
"None" -> ext.subtitles.let { null }
|
||||||
else -> ext.subtitles.find { it.language == subLang }
|
else -> ext.subtitles.find { it.language == subLang }
|
||||||
}
|
}
|
||||||
|
|
||||||
//Subtitles
|
//Subtitles
|
||||||
@@ -1091,21 +1186,44 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
exoSubtitle.setOnClickListener {
|
exoSubtitle.setOnClickListener {
|
||||||
subClick()
|
subClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
var sub: MediaItem.SubtitleConfiguration? = null
|
var sub: MediaItem.SubtitleConfiguration? = null
|
||||||
if (subtitle != null) {
|
if (subtitle != null) {
|
||||||
sub = MediaItem.SubtitleConfiguration
|
//var localFile: String? = null
|
||||||
.Builder(Uri.parse(subtitle!!.file.url))
|
if (subtitle?.type == SubtitleType.UNKNOWN) {
|
||||||
.setSelectionFlags(C.SELECTION_FLAG_FORCED)
|
val context = this
|
||||||
.setMimeType(
|
runBlocking {
|
||||||
when (subtitle?.type) {
|
val type = SubtitleDownloader.downloadSubtitles(context, subtitle!!.file.url)
|
||||||
SubtitleType.VTT -> MimeTypes.TEXT_VTT
|
val fileUri = Uri.parse(subtitle!!.file.url)
|
||||||
SubtitleType.ASS -> MimeTypes.TEXT_SSA
|
sub = MediaItem.SubtitleConfiguration
|
||||||
SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP
|
.Builder(fileUri)
|
||||||
else -> MimeTypes.TEXT_UNKNOWN
|
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
||||||
}
|
.setMimeType(
|
||||||
)
|
when (type) {
|
||||||
.build()
|
SubtitleType.VTT -> MimeTypes.TEXT_SSA
|
||||||
|
SubtitleType.ASS -> MimeTypes.TEXT_SSA
|
||||||
|
SubtitleType.SRT -> MimeTypes.TEXT_SSA
|
||||||
|
else -> MimeTypes.TEXT_SSA
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setId("2")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
println("sub: $sub")
|
||||||
|
} else {
|
||||||
|
sub = MediaItem.SubtitleConfiguration
|
||||||
|
.Builder(Uri.parse(subtitle!!.file.url))
|
||||||
|
.setSelectionFlags(C.SELECTION_FLAG_FORCED)
|
||||||
|
.setMimeType(
|
||||||
|
when (subtitle?.type) {
|
||||||
|
SubtitleType.VTT -> MimeTypes.TEXT_VTT
|
||||||
|
SubtitleType.ASS -> MimeTypes.TEXT_SSA
|
||||||
|
SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP
|
||||||
|
else -> MimeTypes.TEXT_UNKNOWN
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setId("2")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
@@ -1113,7 +1231,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val but = playerView.findViewById<ImageButton>(R.id.exo_download)
|
val but = playerView.findViewById<ImageButton>(R.id.exo_download)
|
||||||
if (video?.format == VideoType.CONTAINER || (loadData<Int>("settings_download_manager") ?: 0) != 0) {
|
if (video?.format == VideoType.CONTAINER || (loadData<Int>("settings_download_manager")
|
||||||
|
?: 0) != 0
|
||||||
|
) {
|
||||||
but.visibility = View.VISIBLE
|
but.visibility = View.VISIBLE
|
||||||
but.setOnClickListener {
|
but.setOnClickListener {
|
||||||
download(this, episode, animeTitle.text.toString())
|
download(this, episode, animeTitle.text.toString())
|
||||||
@@ -1146,12 +1266,15 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
val mimeType = when (video?.format) {
|
val mimeType = when (video?.format) {
|
||||||
VideoType.M3U8 -> MimeTypes.APPLICATION_M3U8
|
VideoType.M3U8 -> MimeTypes.APPLICATION_M3U8
|
||||||
VideoType.DASH -> MimeTypes.APPLICATION_MPD
|
VideoType.DASH -> MimeTypes.APPLICATION_MPD
|
||||||
else -> MimeTypes.APPLICATION_MP4
|
else -> MimeTypes.APPLICATION_MP4
|
||||||
}
|
}
|
||||||
|
|
||||||
val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
|
val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
|
||||||
|
|
||||||
if (sub != null) builder.setSubtitleConfigurations(mutableListOf(sub))
|
if (sub != null) {
|
||||||
|
val listofnotnullsubs = immutableListOf(sub).filterNotNull()
|
||||||
|
builder.setSubtitleConfigurations(listofnotnullsubs)
|
||||||
|
}
|
||||||
mediaItem = builder.build()
|
mediaItem = builder.build()
|
||||||
|
|
||||||
//Source
|
//Source
|
||||||
@@ -1163,8 +1286,22 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
trackSelector = DefaultTrackSelector(this)
|
trackSelector = DefaultTrackSelector(this)
|
||||||
trackSelector.setParameters(
|
trackSelector.setParameters(
|
||||||
trackSelector.buildUponParameters()
|
trackSelector.buildUponParameters()
|
||||||
.setMinVideoSize(loadData("maxWidth", this) ?: 720, loadData("maxHeight", this) ?: 480)
|
.setAllowVideoMixedMimeTypeAdaptiveness(true)
|
||||||
|
.setAllowVideoNonSeamlessAdaptiveness(true)
|
||||||
|
.setSelectUndeterminedTextLanguage(true)
|
||||||
|
.setAllowAudioMixedMimeTypeAdaptiveness(true)
|
||||||
|
.setAllowMultipleAdaptiveSelections(true)
|
||||||
|
.setPreferredTextLanguage(subtitle?.language ?: "en")
|
||||||
|
.setPreferredTextRoleFlags(C.ROLE_FLAG_SUBTITLE)
|
||||||
|
.setRendererDisabled(C.TRACK_TYPE_VIDEO, false)
|
||||||
|
.setRendererDisabled(C.TRACK_TYPE_AUDIO, false)
|
||||||
|
.setRendererDisabled(C.TRACK_TYPE_TEXT, false)
|
||||||
|
.setMinVideoSize(
|
||||||
|
loadData("maxWidth", this) ?: 720,
|
||||||
|
loadData("maxHeight", this) ?: 480
|
||||||
|
)
|
||||||
.setMaxVideoSize(1, 1)
|
.setMaxVideoSize(1, 1)
|
||||||
|
//.setOverrideForType(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (playbackPosition != 0L && !changingServer && !settings.alwaysContinue) {
|
if (playbackPosition != 0L && !changingServer && !settings.alwaysContinue) {
|
||||||
@@ -1181,7 +1318,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.continue_from, time)).apply {
|
AlertDialog.Builder(this, R.style.DialogTheme)
|
||||||
|
.setTitle(getString(R.string.continue_from, time)).apply {
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
setPositiveButton(getString(R.string.yes)) { d, _ ->
|
setPositiveButton(getString(R.string.yes)) { d, _ ->
|
||||||
buildExoplayer()
|
buildExoplayer()
|
||||||
@@ -1221,8 +1359,34 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exoPlayer.addListener(this)
|
exoPlayer.addListener(this)
|
||||||
|
exoPlayer.addAnalyticsListener(EventLogger())
|
||||||
isInitialized = true
|
isInitialized = true
|
||||||
}
|
}
|
||||||
|
/*private fun selectSubtitleTrack() {
|
||||||
|
// Get the current track groups
|
||||||
|
val trackGroups = exoPlayer.currentTrackGroups
|
||||||
|
|
||||||
|
// Prepare a track selector parameters builder
|
||||||
|
val parametersBuilder = DefaultTrackSelector.ParametersBuilder(this)
|
||||||
|
|
||||||
|
// Iterate through the track groups to find the subtitle tracks
|
||||||
|
for (i in 0 until trackGroups.length) {
|
||||||
|
val trackGroup = trackGroups[i]
|
||||||
|
for (j in 0 until trackGroup.length) {
|
||||||
|
val trackMetadata = trackGroup.getFormat(j)
|
||||||
|
|
||||||
|
// Check if the track is a subtitle track
|
||||||
|
if (MimeTypes.isText(trackMetadata.sampleMimeType)) {
|
||||||
|
parametersBuilder.setRendererDisabled(i, false) // Enable the renderer for this track group
|
||||||
|
parametersBuilder.setSelectionOverride(i, trackGroups, DefaultTrackSelector.SelectionOverride(j, 0)) // Override to select this track
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the track selector parameters to select the subtitle
|
||||||
|
trackSelector.setParameters(parametersBuilder)
|
||||||
|
}*/
|
||||||
|
|
||||||
private fun releasePlayer() {
|
private fun releasePlayer() {
|
||||||
isPlayerPlaying = exoPlayer.playWhenReady
|
isPlayerPlaying = exoPlayer.playWhenReady
|
||||||
@@ -1266,7 +1430,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
orientationListener?.disable()
|
orientationListener?.disable()
|
||||||
if (isInitialized) {
|
if (isInitialized) {
|
||||||
playerView.player?.pause()
|
playerView.player?.pause()
|
||||||
saveData("${media.id}_${media.anime!!.selectedEpisode}", exoPlayer.currentPosition, this)
|
saveData(
|
||||||
|
"${media.id}_${media.anime!!.selectedEpisode}",
|
||||||
|
exoPlayer.currentPosition,
|
||||||
|
this
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1304,7 +1472,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
playerView.keepScreenOn = isPlaying
|
playerView.keepScreenOn = isPlaying
|
||||||
(exoPlay.drawable as Animatable?)?.start()
|
(exoPlay.drawable as Animatable?)?.start()
|
||||||
if (!this.isDestroyed) Glide.with(this)
|
if (!this.isDestroyed) Glide.with(this)
|
||||||
.load(if (isPlaying) R.drawable.anim_play_to_pause else R.drawable.anim_pause_to_play).into(exoPlay)
|
.load(if (isPlaying) R.drawable.anim_play_to_pause else R.drawable.anim_pause_to_play)
|
||||||
|
.into(exoPlay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1353,7 +1522,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
if (media.selected!!.server != null)
|
if (media.selected!!.server != null)
|
||||||
model.loadEpisodeSingleVideo(ep, selected, false)
|
model.loadEpisodeSingleVideo(ep, selected, false)
|
||||||
else
|
else
|
||||||
model.loadEpisodeVideos(ep, selected.source, false)
|
model.loadEpisodeVideos(ep, selected.sourceIndex, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1383,7 +1552,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
exoPlayer.seekTo((new.interval.endTime * 1000).toLong())
|
exoPlayer.seekTo((new.interval.endTime * 1000).toLong())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settings.autoSkipOPED && (new.skipType == "op" || new.skipType == "ed") && !skippedTimeStamps.contains(new)) {
|
if (settings.autoSkipOPED && (new.skipType == "op" || new.skipType == "ed") && !skippedTimeStamps.contains(
|
||||||
|
new
|
||||||
|
)
|
||||||
|
) {
|
||||||
exoPlayer.seekTo((new.interval.endTime * 1000).toLong())
|
exoPlayer.seekTo((new.interval.endTime * 1000).toLong())
|
||||||
skippedTimeStamps.add(new)
|
skippedTimeStamps.add(new)
|
||||||
}
|
}
|
||||||
@@ -1426,6 +1598,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
private var isBuffering = true
|
private var isBuffering = true
|
||||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
if (playbackState == ExoPlayer.STATE_READY) {
|
if (playbackState == ExoPlayer.STATE_READY) {
|
||||||
|
|
||||||
exoPlayer.play()
|
exoPlayer.play()
|
||||||
if (episodeLength == 0f) {
|
if (episodeLength == 0f) {
|
||||||
episodeLength = exoPlayer.duration.toFloat()
|
episodeLength = exoPlayer.duration.toFloat()
|
||||||
@@ -1453,7 +1626,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
var i = 1
|
var i = 1
|
||||||
while (isFiller) {
|
while (isFiller) {
|
||||||
if (episodeArr.size > currentEpisodeIndex + i) {
|
if (episodeArr.size > currentEpisodeIndex + i) {
|
||||||
isFiller = if (settings.autoSkipFiller) episodes[episodeArr[currentEpisodeIndex + i]]?.filler ?: false else false
|
isFiller =
|
||||||
|
if (settings.autoSkipFiller) episodes[episodeArr[currentEpisodeIndex + i]]?.filler
|
||||||
|
?: false else false
|
||||||
if (!isFiller) runnable.invoke(i)
|
if (!isFiller) runnable.invoke(i)
|
||||||
i++
|
i++
|
||||||
} else {
|
} else {
|
||||||
@@ -1509,7 +1684,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
shareVideo.setDataAndType(Uri.parse(videoURL), "video/*")
|
shareVideo.setDataAndType(Uri.parse(videoURL), "video/*")
|
||||||
shareVideo.setPackage("com.instantbits.cast.webvideo")
|
shareVideo.setPackage("com.instantbits.cast.webvideo")
|
||||||
if (subtitle != null) shareVideo.putExtra("subtitle", subtitle!!.file.url)
|
if (subtitle != null) shareVideo.putExtra("subtitle", subtitle!!.file.url)
|
||||||
shareVideo.putExtra("title", media.userPreferredName + " : Ep " + episodeTitleArr[currentEpisodeIndex])
|
shareVideo.putExtra(
|
||||||
|
"title",
|
||||||
|
media.userPreferredName + " : Ep " + episodeTitleArr[currentEpisodeIndex]
|
||||||
|
)
|
||||||
shareVideo.putExtra("poster", episode.thumb?.url ?: media.cover)
|
shareVideo.putExtra("poster", episode.thumb?.url ?: media.cover)
|
||||||
val headers = Bundle()
|
val headers = Bundle()
|
||||||
defaultHeaders.forEach {
|
defaultHeaders.forEach {
|
||||||
@@ -1579,7 +1757,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
|
override fun onPictureInPictureModeChanged(
|
||||||
|
isInPictureInPictureMode: Boolean,
|
||||||
|
newConfig: Configuration
|
||||||
|
) {
|
||||||
onPiPChanged(isInPictureInPictureMode)
|
onPiPChanged(isInPictureInPictureMode)
|
||||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import ani.dantotsu.media.MediaDetailsViewModel
|
|||||||
import ani.dantotsu.others.Download.download
|
import ani.dantotsu.others.Download.download
|
||||||
import ani.dantotsu.parsers.VideoExtractor
|
import ani.dantotsu.parsers.VideoExtractor
|
||||||
import ani.dantotsu.parsers.VideoType
|
import ani.dantotsu.parsers.VideoType
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -135,7 +136,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
model.loadEpisodeVideos(ep, media!!.selected!!.source)
|
model.loadEpisodeVideos(ep, media!!.selected!!.sourceIndex)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
binding.selectorProgressBar.visibility = View.GONE
|
binding.selectorProgressBar.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|||||||
109
app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt
Normal file
109
app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package ani.dantotsu.media.manga
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.util.LruCache
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
data class ImageData(
|
||||||
|
val page: Page,
|
||||||
|
val source: HttpSource
|
||||||
|
){
|
||||||
|
suspend fun fetchAndProcessImage(page: Page, httpSource: HttpSource, context: Context): Bitmap? {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
// Fetch the image
|
||||||
|
val response = httpSource.getImage(page)
|
||||||
|
println("Response: ${response.code}")
|
||||||
|
println("Response: ${response.message}")
|
||||||
|
|
||||||
|
// Convert the Response to an InputStream
|
||||||
|
val inputStream = response.body?.byteStream()
|
||||||
|
|
||||||
|
// Convert InputStream to Bitmap
|
||||||
|
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
|
||||||
|
inputStream?.close()
|
||||||
|
saveImage(bitmap, context.contentResolver, page.imageUrl!!, Bitmap.CompressFormat.JPEG, 100)
|
||||||
|
|
||||||
|
return@withContext bitmap
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Handle any exceptions
|
||||||
|
println("An error occurred: ${e.message}")
|
||||||
|
return@withContext null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String, format: Bitmap.CompressFormat, quality: Int) {
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/${format.name.lowercase()}")
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Manga")
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
|
||||||
|
uri?.let {
|
||||||
|
contentResolver.openOutputStream(it)?.use { os ->
|
||||||
|
bitmap.compress(format, quality, os)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Anime")
|
||||||
|
if (!directory.exists()) {
|
||||||
|
directory.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(directory, filename)
|
||||||
|
FileOutputStream(file).use { outputStream ->
|
||||||
|
bitmap.compress(format, quality, outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Handle exception here
|
||||||
|
println("Exception while saving image: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MangaCache() {
|
||||||
|
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024 / 2).toInt()
|
||||||
|
private val cache = LruCache<String, ImageData>(maxMemory)
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun put(key: String, imageDate: ImageData) {
|
||||||
|
cache.put(key, imageDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun get(key: String): ImageData? = cache.get(key)
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun remove(key: String) {
|
||||||
|
cache.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun clear() {
|
||||||
|
cache.evictAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun size(): Int = cache.size()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package ani.dantotsu.media.manga
|
|||||||
|
|
||||||
import ani.dantotsu.parsers.MangaChapter
|
import ani.dantotsu.parsers.MangaChapter
|
||||||
import ani.dantotsu.parsers.MangaImage
|
import ani.dantotsu.parsers.MangaImage
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
@@ -10,8 +11,9 @@ data class MangaChapter(
|
|||||||
var link: String,
|
var link: String,
|
||||||
var title: String? = null,
|
var title: String? = null,
|
||||||
var description: String? = null,
|
var description: String? = null,
|
||||||
|
var sChapter: SChapter
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description)
|
constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description, chapter.sChapter)
|
||||||
|
|
||||||
private val images = mutableListOf<MangaImage>()
|
private val images = mutableListOf<MangaImage>()
|
||||||
fun images(): List<MangaImage> = images
|
fun images(): List<MangaImage> = images
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
|||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
import ani.dantotsu.connections.updateProgress
|
import ani.dantotsu.connections.updateProgress
|
||||||
|
import java.util.regex.Matcher
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
class MangaChapterAdapter(
|
class MangaChapterAdapter(
|
||||||
private var type: Int,
|
private var type: Int,
|
||||||
@@ -63,12 +65,12 @@ class MangaChapterAdapter(
|
|||||||
val ep = arr[position]
|
val ep = arr[position]
|
||||||
binding.itemEpisodeNumber.text = ep.number
|
binding.itemEpisodeNumber.text = ep.number
|
||||||
if (media.userProgress != null) {
|
if (media.userProgress != null) {
|
||||||
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat())
|
if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 9999f) <= media.userProgress!!.toFloat())
|
||||||
binding.itemEpisodeViewedCover.visibility = View.VISIBLE
|
binding.itemEpisodeViewedCover.visibility = View.VISIBLE
|
||||||
else {
|
else {
|
||||||
binding.itemEpisodeViewedCover.visibility = View.GONE
|
binding.itemEpisodeViewedCover.visibility = View.GONE
|
||||||
binding.itemEpisodeCont.setOnLongClickListener {
|
binding.itemEpisodeCont.setOnLongClickListener {
|
||||||
updateProgress(media, ep.number)
|
updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString())
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,14 +93,14 @@ class MangaChapterAdapter(
|
|||||||
} else binding.itemChapterTitle.visibility = View.GONE
|
} else binding.itemChapterTitle.visibility = View.GONE
|
||||||
|
|
||||||
if (media.userProgress != null) {
|
if (media.userProgress != null) {
|
||||||
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) {
|
if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 9999f) <= media.userProgress!!.toFloat()) {
|
||||||
binding.itemEpisodeViewedCover.visibility = View.VISIBLE
|
binding.itemEpisodeViewedCover.visibility = View.VISIBLE
|
||||||
binding.itemEpisodeViewed.visibility = View.VISIBLE
|
binding.itemEpisodeViewed.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
binding.itemEpisodeViewedCover.visibility = View.GONE
|
binding.itemEpisodeViewedCover.visibility = View.GONE
|
||||||
binding.itemEpisodeViewed.visibility = View.GONE
|
binding.itemEpisodeViewed.visibility = View.GONE
|
||||||
binding.root.setOnLongClickListener {
|
binding.root.setOnLongClickListener {
|
||||||
updateProgress(media, ep.number)
|
updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString())
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,4 +115,6 @@ class MangaChapterAdapter(
|
|||||||
fun updateType(t: Int) {
|
fun updateType(t: Int) {
|
||||||
type = t
|
type = t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package ani.dantotsu.media.manga
|
||||||
|
|
||||||
|
import java.util.regex.Matcher
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
class MangaNameAdapter {
|
||||||
|
companion object {
|
||||||
|
fun findChapterNumber(text: String): Float? {
|
||||||
|
val regex = "(chapter|chap|ch|c)[\\s:.\\-]*([\\d]+\\.?[\\d]*)"
|
||||||
|
val pattern: Pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE)
|
||||||
|
val matcher: Matcher = pattern.matcher(text)
|
||||||
|
|
||||||
|
return if (matcher.find()) {
|
||||||
|
matcher.group(2)?.toFloat()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ class MangaReadAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Source Selection
|
//Source Selection
|
||||||
val source = media.selected!!.source.let { if (it >= mangaReadSources.names.size) 0 else it }
|
val source = media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it }
|
||||||
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
||||||
binding.animeSource.setText(mangaReadSources.names[source])
|
binding.animeSource.setText(mangaReadSources.names[source])
|
||||||
|
|
||||||
@@ -174,7 +174,9 @@ class MangaReadAdapter(
|
|||||||
val anilistEp = (media.userProgress ?: 0).plus(1)
|
val anilistEp = (media.userProgress ?: 0).plus(1)
|
||||||
val appEp = loadData<String>("${media.id}_current_chp")?.toIntOrNull() ?: 1
|
val appEp = loadData<String>("${media.id}_current_chp")?.toIntOrNull() ?: 1
|
||||||
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
||||||
if (chapters.contains(continueEp)) {
|
val formattedChapters = chapters.map { MangaNameAdapter.findChapterNumber(it)?.toInt()?.toString() }
|
||||||
|
if (formattedChapters.contains(continueEp)) {
|
||||||
|
continueEp = chapters[formattedChapters.indexOf(continueEp)]
|
||||||
binding.animeSourceContinue.visibility = View.VISIBLE
|
binding.animeSourceContinue.visibility = View.VISIBLE
|
||||||
handleProgress(
|
handleProgress(
|
||||||
binding.itemEpisodeProgressCont,
|
binding.itemEpisodeProgressCont,
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ open class MangaReadFragment : Fragment() {
|
|||||||
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, chapterAdapter)
|
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, chapterAdapter)
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
model.loadMangaChapters(media, media.selected!!.source)
|
model.loadMangaChapters(media, media.selected!!.sourceIndex)
|
||||||
}
|
}
|
||||||
loaded = true
|
loaded = true
|
||||||
} else {
|
} else {
|
||||||
@@ -136,7 +136,7 @@ open class MangaReadFragment : Fragment() {
|
|||||||
|
|
||||||
model.getMangaChapters().observe(viewLifecycleOwner) { loadedChapters ->
|
model.getMangaChapters().observe(viewLifecycleOwner) { loadedChapters ->
|
||||||
if (loadedChapters != null) {
|
if (loadedChapters != null) {
|
||||||
val chapters = loadedChapters[media.selected!!.source]
|
val chapters = loadedChapters[media.selected!!.sourceIndex]
|
||||||
if (chapters != null) {
|
if (chapters != null) {
|
||||||
media.manga?.chapters = chapters
|
media.manga?.chapters = chapters
|
||||||
|
|
||||||
@@ -177,8 +177,8 @@ open class MangaReadFragment : Fragment() {
|
|||||||
media.manga?.chapters = null
|
media.manga?.chapters = null
|
||||||
reload()
|
reload()
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
model.mangaReadSources?.get(selected.source)?.showUserTextListener = null
|
model.mangaReadSources?.get(selected.sourceIndex)?.showUserTextListener = null
|
||||||
selected.source = i
|
selected.sourceIndex = i
|
||||||
selected.server = null
|
selected.server = null
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
|
|||||||
@@ -14,15 +14,21 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.settings.CurrentReaderSettings
|
import ani.dantotsu.settings.CurrentReaderSettings
|
||||||
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
abstract class BaseImageAdapter(
|
abstract class BaseImageAdapter(
|
||||||
val activity: MangaReaderActivity,
|
val activity: MangaReaderActivity,
|
||||||
@@ -44,10 +50,33 @@ abstract class BaseImageAdapter(
|
|||||||
if (settings.layout != CurrentReaderSettings.Layouts.PAGED) {
|
if (settings.layout != CurrentReaderSettings.Layouts.PAGED) {
|
||||||
if (settings.padding) {
|
if (settings.padding) {
|
||||||
when (settings.direction) {
|
when (settings.direction) {
|
||||||
CurrentReaderSettings.Directions.TOP_TO_BOTTOM -> view.setPadding(0, 0, 0, 16f.px)
|
CurrentReaderSettings.Directions.TOP_TO_BOTTOM -> view.setPadding(
|
||||||
CurrentReaderSettings.Directions.LEFT_TO_RIGHT -> view.setPadding(0, 0, 16f.px, 0)
|
0,
|
||||||
CurrentReaderSettings.Directions.BOTTOM_TO_TOP -> view.setPadding(0, 16f.px, 0, 0)
|
0,
|
||||||
CurrentReaderSettings.Directions.RIGHT_TO_LEFT -> view.setPadding(16f.px, 0, 0, 0)
|
0,
|
||||||
|
16f.px
|
||||||
|
)
|
||||||
|
|
||||||
|
CurrentReaderSettings.Directions.LEFT_TO_RIGHT -> view.setPadding(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
16f.px,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
CurrentReaderSettings.Directions.BOTTOM_TO_TOP -> view.setPadding(
|
||||||
|
0,
|
||||||
|
16f.px,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
CurrentReaderSettings.Directions.RIGHT_TO_LEFT -> view.setPadding(
|
||||||
|
16f.px,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.updateLayoutParams {
|
view.updateLayoutParams {
|
||||||
@@ -87,10 +116,10 @@ abstract class BaseImageAdapter(
|
|||||||
abstract suspend fun loadImage(position: Int, parent: View): Boolean
|
abstract suspend fun loadImage(position: Int, parent: View): Boolean
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun Context.loadBitmap(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? {
|
suspend fun Context.loadBitmap_old(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? {
|
||||||
return tryWithSuspend {
|
return tryWithSuspend {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Glide.with(this@loadBitmap)
|
Glide.with(this@loadBitmap_old)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.let {
|
.let {
|
||||||
if (link.url.startsWith("file://")) {
|
if (link.url.startsWith("file://")) {
|
||||||
@@ -115,6 +144,43 @@ abstract class BaseImageAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun Context.loadBitmap(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? {
|
||||||
|
return tryWithSuspend {
|
||||||
|
val mangaCache = uy.kohesive.injekt.Injekt.get<MangaCache>()
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Glide.with(this@loadBitmap)
|
||||||
|
.asBitmap()
|
||||||
|
.let {
|
||||||
|
if (link.url.startsWith("file://")) {
|
||||||
|
it.load(link.url)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
} else {
|
||||||
|
println("bitmap from cache")
|
||||||
|
println(link.url)
|
||||||
|
println(mangaCache.get(link.url))
|
||||||
|
println("cache size: ${mangaCache.size()}")
|
||||||
|
mangaCache.get(link.url)?.let { imageData ->
|
||||||
|
val bitmap = imageData.fetchAndProcessImage(imageData.page, imageData.source, context = this@loadBitmap)
|
||||||
|
it.load(bitmap)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.let {
|
||||||
|
if (transforms.isNotEmpty()) {
|
||||||
|
it.transform(*transforms.toTypedArray())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.submit()
|
||||||
|
?.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun mergeBitmap(bitmap1: Bitmap, bitmap2: Bitmap, scale: Boolean = false): Bitmap {
|
fun mergeBitmap(bitmap1: Bitmap, bitmap2: Bitmap, scale: Boolean = false): Bitmap {
|
||||||
val height = if (bitmap1.height > bitmap2.height) bitmap1.height else bitmap2.height
|
val height = if (bitmap1.height > bitmap2.height) bitmap1.height else bitmap2.height
|
||||||
val (bit1, bit2) = if (!scale) bitmap1 to bitmap2 else {
|
val (bit1, bit2) = if (!scale) bitmap1 to bitmap2 else {
|
||||||
@@ -133,4 +199,8 @@ abstract class BaseImageAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImageFetcher {
|
||||||
|
suspend fun fetchImage(page: Page): Bitmap?
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ import ani.dantotsu.currActivity
|
|||||||
import ani.dantotsu.databinding.BottomSheetSelectorBinding
|
import ani.dantotsu.databinding.BottomSheetSelectorBinding
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
|
import ani.dantotsu.media.MediaSingleton
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.tryWith
|
import ani.dantotsu.tryWith
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -49,7 +50,8 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
|
|||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
tryWith { dismiss() }
|
tryWith { dismiss() }
|
||||||
if(launch) {
|
if(launch) {
|
||||||
val intent = Intent(activity, MangaReaderActivity::class.java).apply { putExtra("media", m) }
|
MediaSingleton.media = m
|
||||||
|
val intent = Intent(activity, MangaReaderActivity::class.java)//.apply { putExtra("media", m) }
|
||||||
activity.startActivity(intent)
|
activity.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ import ani.dantotsu.connections.updateProgress
|
|||||||
import ani.dantotsu.databinding.ActivityMangaReaderBinding
|
import ani.dantotsu.databinding.ActivityMangaReaderBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
|
import ani.dantotsu.media.MediaSingleton
|
||||||
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
|
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.parsers.HMangaSources
|
import ani.dantotsu.parsers.HMangaSources
|
||||||
@@ -42,17 +45,27 @@ import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.*
|
|||||||
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.*
|
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.*
|
||||||
import ani.dantotsu.settings.ReaderSettings
|
import ani.dantotsu.settings.ReaderSettings
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||||
|
import com.google.firebase.ktx.Firebase
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
class MangaReaderActivity : AppCompatActivity() {
|
class MangaReaderActivity : AppCompatActivity() {
|
||||||
|
private val mangaCache = Injekt.get<MangaCache>()
|
||||||
|
|
||||||
private lateinit var binding: ActivityMangaReaderBinding
|
private lateinit var binding: ActivityMangaReaderBinding
|
||||||
private val model: MediaDetailsViewModel by viewModels()
|
private val model: MediaDetailsViewModel by viewModels()
|
||||||
private val scope = lifecycleScope
|
private val scope = lifecycleScope
|
||||||
@@ -106,12 +119,14 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
mangaCache.clear()
|
||||||
rpc?.close()
|
rpc?.close()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityMangaReaderBinding.inflate(layoutInflater)
|
binding = ActivityMangaReaderBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -158,10 +173,13 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
media = if (model.getMedia().value == null)
|
media = if (model.getMedia().value == null)
|
||||||
try {
|
try {
|
||||||
(intent.getSerialized("media")) ?: return
|
//(intent.getSerialized("media")) ?: return
|
||||||
|
MediaSingleton.media ?: return
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
return
|
return
|
||||||
|
} finally {
|
||||||
|
MediaSingleton.media = null
|
||||||
}
|
}
|
||||||
else model.getMedia().value ?: return
|
else model.getMedia().value ?: return
|
||||||
model.setMedia(media)
|
model.setMedia(media)
|
||||||
@@ -174,7 +192,30 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
||||||
binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE
|
binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE
|
||||||
binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.source]
|
if(model.mangaReadSources!!.names.isEmpty()){
|
||||||
|
//try to reload sources
|
||||||
|
try {
|
||||||
|
if (media.isAdult) {
|
||||||
|
val mangaSources = MangaSources
|
||||||
|
val scope = lifecycleScope
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow)
|
||||||
|
}
|
||||||
|
model.mangaReadSources = mangaSources
|
||||||
|
}else{
|
||||||
|
val mangaSources = HMangaSources
|
||||||
|
val scope = lifecycleScope
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow)
|
||||||
|
}
|
||||||
|
model.mangaReadSources = mangaSources
|
||||||
|
}
|
||||||
|
}catch (e: Exception){
|
||||||
|
Firebase.crashlytics.recordException(e)
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex]
|
||||||
|
|
||||||
binding.mangaReaderTitle.text = media.userPreferredName
|
binding.mangaReaderTitle.text = media.userPreferredName
|
||||||
|
|
||||||
@@ -205,6 +246,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
//Chapter Change
|
//Chapter Change
|
||||||
fun change(index: Int) {
|
fun change(index: Int) {
|
||||||
|
mangaCache.clear()
|
||||||
saveData("${media.id}_${chaptersArr[currentChapterIndex]}", currentChapterPage, this)
|
saveData("${media.id}_${chaptersArr[currentChapterIndex]}", currentChapterPage, this)
|
||||||
ChapterLoaderDialog.newInstance(chapters[chaptersArr[index]]!!).show(supportFragmentManager, "dialog")
|
ChapterLoaderDialog.newInstance(chapters[chaptersArr[index]]!!).show(supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
@@ -258,7 +300,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
type = RPC.Type.WATCHING
|
type = RPC.Type.WATCHING
|
||||||
activityName = media.userPreferredName
|
activityName = media.userPreferredName
|
||||||
details = chap.title?.takeIf { it.isNotEmpty() } ?: getString(R.string.chapter_num, chap.number)
|
details = chap.title?.takeIf { it.isNotEmpty() } ?: getString(R.string.chapter_num, chap.number)
|
||||||
state = "Chapter : ${chap.number}/${media.manga?.totalChapters ?: "??"}"
|
state = "${chap.number}/${media.manga?.totalChapters ?: "??"}"
|
||||||
media.cover?.let { cover ->
|
media.cover?.let { cover ->
|
||||||
largeImage = RPC.Link(media.userPreferredName, cover)
|
largeImage = RPC.Link(media.userPreferredName, cover)
|
||||||
}
|
}
|
||||||
@@ -670,7 +712,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
progressDialog?.setCancelable(false)
|
progressDialog?.setCancelable(false)
|
||||||
?.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
|
?.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
|
||||||
saveData("${media.id}_save_progress", true)
|
saveData("${media.id}_save_progress", true)
|
||||||
updateProgress(media, media.manga!!.selectedChapter!!)
|
updateProgress(media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString())
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
runnable.run()
|
runnable.run()
|
||||||
}
|
}
|
||||||
@@ -682,7 +724,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
progressDialog?.show()
|
progressDialog?.show()
|
||||||
} else {
|
} else {
|
||||||
if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true)
|
if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true)
|
||||||
updateProgress(media, media.manga!!.selectedChapter!!)
|
updateProgress(media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString())
|
||||||
runnable.run()
|
runnable.run()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -691,7 +733,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getTransformation(mangaImage: MangaImage): BitmapTransformation? {
|
fun getTransformation(mangaImage: MangaImage): BitmapTransformation? {
|
||||||
return model.loadTransformation(mangaImage, media.selected!!.source)
|
return model.loadTransformation(mangaImage, media.selected!!.sourceIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onImageLongClicked(
|
fun onImageLongClicked(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ani.dantotsu.loadImage
|
|||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.parsers.ShowResponse
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class NovelReadAdapter(
|
|||||||
|
|
||||||
fun search(): Boolean {
|
fun search(): Boolean {
|
||||||
val query = binding.searchBarText.text.toString()
|
val query = binding.searchBarText.text.toString()
|
||||||
val source = media.selected!!.source.let { if (it >= novelReadSources.names.size) 0 else it }
|
val source = media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it }
|
||||||
fragment.source = source
|
fragment.source = source
|
||||||
|
|
||||||
binding.searchBarText.clearFocus()
|
binding.searchBarText.clearFocus()
|
||||||
@@ -44,7 +44,7 @@ class NovelReadAdapter(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
val source = media.selected!!.source.let { if (it >= novelReadSources.names.size) 0 else it }
|
val source = media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it }
|
||||||
if (novelReadSources.names.isNotEmpty() && source in 0 until novelReadSources.names.size) {
|
if (novelReadSources.names.isNotEmpty() && source in 0 until novelReadSources.names.size) {
|
||||||
binding.animeSource.setText(novelReadSources.names[source], false)
|
binding.animeSource.setText(novelReadSources.names[source], false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class NovelReadFragment : Fragment() {
|
|||||||
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, novelResponseAdapter)
|
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, novelResponseAdapter)
|
||||||
loaded = true
|
loaded = true
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
search(searchQuery, sel?.source ?: 0, auto = sel?.server == null)
|
search(searchQuery, sel?.sourceIndex ?: 0, auto = sel?.server == null)
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ class NovelReadFragment : Fragment() {
|
|||||||
|
|
||||||
fun onSourceChange(i: Int) {
|
fun onSourceChange(i: Int) {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
selected.source = i
|
selected.sourceIndex = i
|
||||||
source = i
|
source = i
|
||||||
selected.server = null
|
selected.server = null
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import ani.dantotsu.settings.CurrentReaderSettings
|
|||||||
import ani.dantotsu.settings.NovelReaderSettings
|
import ani.dantotsu.settings.NovelReaderSettings
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.tryWith
|
import ani.dantotsu.tryWith
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import com.vipulog.ebookreader.Book
|
import com.vipulog.ebookreader.Book
|
||||||
@@ -135,6 +136,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityNovelReaderBinding.inflate(layoutInflater)
|
binding = ActivityNovelReaderBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ani.dantotsu.media.user
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -12,6 +13,7 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityListBinding
|
import ani.dantotsu.databinding.ActivityListBinding
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -26,10 +28,28 @@ class ListActivity : AppCompatActivity() {
|
|||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityListBinding.inflate(layoutInflater)
|
binding = ActivityListBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg)
|
val typedValue = TypedValue()
|
||||||
|
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue, true)
|
||||||
|
val primaryColor = typedValue.data
|
||||||
|
val typedValue2 = TypedValue()
|
||||||
|
theme.resolveAttribute(com.google.android.material.R.attr.colorOnPrimary, typedValue2, true)
|
||||||
|
val primaryTextColor = typedValue2.data
|
||||||
|
val typedValue3 = TypedValue()
|
||||||
|
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimaryContainer, typedValue3, true)
|
||||||
|
val secondaryColor = typedValue3.data
|
||||||
|
|
||||||
|
window.statusBarColor = primaryColor
|
||||||
|
window.navigationBarColor = primaryColor
|
||||||
|
binding.listTabLayout.setBackgroundColor(primaryColor)
|
||||||
|
binding.listAppBar.setBackgroundColor(primaryColor)
|
||||||
|
binding.listTitle.setTextColor(primaryTextColor)
|
||||||
|
binding.listTabLayout.setTabTextColors(primaryTextColor, secondaryColor)
|
||||||
|
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
||||||
|
|
||||||
val anime = intent.getBooleanExtra("anime", true)
|
val anime = intent.getBooleanExtra("anime", true)
|
||||||
binding.listTitle.text = intent.getStringExtra("username") + "'s " + (if (anime) "Anime" else "Manga") + " List"
|
binding.listTitle.text = intent.getStringExtra("username") + "'s " + (if (anime) "Anime" else "Manga") + " List"
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import ani.dantotsu.databinding.FragmentListBinding
|
|||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.OtherDetailsViewModel
|
import ani.dantotsu.media.OtherDetailsViewModel
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class ListFragment : Fragment() {
|
class ListFragment : Fragment() {
|
||||||
private var _binding: FragmentListBinding? = null
|
private var _binding: FragmentListBinding? = null
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ import ani.dantotsu.BottomSheetDialogFragment
|
|||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.BottomSheetImageBinding
|
import ani.dantotsu.databinding.BottomSheetImageBinding
|
||||||
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap
|
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old
|
||||||
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap
|
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.saveImageToDownloads
|
import ani.dantotsu.saveImageToDownloads
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.shareImage
|
import ani.dantotsu.shareImage
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
@@ -76,8 +77,8 @@ class ImageViewDialog : BottomSheetDialogFragment() {
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val binding = _binding ?: return@launch
|
val binding = _binding ?: return@launch
|
||||||
|
|
||||||
var bitmap = requireContext().loadBitmap(image, trans1 ?: listOf())
|
var bitmap = requireContext().loadBitmap_old(image, trans1 ?: listOf())
|
||||||
val bitmap2 = if (image2 != null) requireContext().loadBitmap(image2, trans2 ?: listOf()) else null
|
val bitmap2 = if (image2 != null) requireContext().loadBitmap_old(image2, trans2 ?: listOf()) else null
|
||||||
|
|
||||||
bitmap = if (bitmap2 != null && bitmap != null) mergeBitmap(bitmap, bitmap2,) else bitmap
|
bitmap = if (bitmap2 != null && bitmap != null) mergeBitmap(bitmap, bitmap2,) else bitmap
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ object Jikan {
|
|||||||
val ep = it.malID.toString()
|
val ep = it.malID.toString()
|
||||||
eps[ep] = Episode(ep, title = it.title,
|
eps[ep] = Episode(ep, title = it.title,
|
||||||
//Personal revenge with 34566 :prayge:
|
//Personal revenge with 34566 :prayge:
|
||||||
filler = if(malId!=34566) it.filler else true
|
filler = if(malId!=34566) it.filler else true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
hasNextPage = res?.pagination?.hasNextPage == true
|
hasNextPage = res?.pagination?.hasNextPage == true
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.databinding.ActivityImageSearchBinding
|
import ani.dantotsu.databinding.ActivityImageSearchBinding
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -46,6 +47,7 @@ class ImageSearchActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityImageSearchBinding.inflate(layoutInflater)
|
binding = ActivityImageSearchBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ abstract class AnimeParser : BaseParser() {
|
|||||||
* Returns null, if no latest episode is found.
|
* Returns null, if no latest episode is found.
|
||||||
* **/
|
* **/
|
||||||
open suspend fun getLatestEpisode(animeLink: String, extra: Map<String, String>?, sAnime: SAnime, latest: Float): Episode?{
|
open suspend fun getLatestEpisode(animeLink: String, extra: Map<String, String>?, sAnime: SAnime, latest: Float): Episode?{
|
||||||
return loadEpisodes(animeLink, extra, sAnime)
|
val episodes = loadEpisodes(animeLink, extra, sAnime)
|
||||||
|
val max = episodes
|
||||||
.maxByOrNull { it.number.toFloatOrNull()?:0f }
|
.maxByOrNull { it.number.toFloatOrNull()?:0f }
|
||||||
|
return max
|
||||||
?.takeIf { latest < (it.number.toFloatOrNull() ?: 0.001f) }
|
?.takeIf { latest < (it.number.toFloatOrNull() ?: 0.001f) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +173,17 @@ abstract class AnimeParser : BaseParser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EmptyAnimeParser: AnimeParser() {
|
||||||
|
override val name: String = "None"
|
||||||
|
override val saveName: String = "None"
|
||||||
|
|
||||||
|
override val isDubAvailableSeparately: Boolean = false
|
||||||
|
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> = emptyList()
|
||||||
|
override suspend fun loadVideoServers(episodeLink: String, extra: Map<String, String>?, sEpisode: SEpisode): List<VideoServer> = emptyList()
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<ShowResponse> = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for containing Episode data of a particular parser
|
* A class for containing Episode data of a particular parser
|
||||||
* **/
|
* **/
|
||||||
|
|||||||
@@ -1,34 +1,11 @@
|
|||||||
package ani.dantotsu.parsers
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
import ani.dantotsu.Lazier
|
import ani.dantotsu.Lazier
|
||||||
import ani.dantotsu.aniyomi.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import ani.dantotsu.lazyList
|
import ani.dantotsu.lazyList
|
||||||
//import ani.dantotsu.parsers.anime.AllAnime
|
|
||||||
//import ani.dantotsu.parsers.anime.AnimeDao
|
|
||||||
//import ani.dantotsu.parsers.anime.AnimePahe
|
|
||||||
//import ani.dantotsu.parsers.anime.Gogo
|
|
||||||
//import ani.dantotsu.parsers.anime.Haho
|
|
||||||
//import ani.dantotsu.parsers.anime.HentaiFF
|
|
||||||
//import ani.dantotsu.parsers.anime.HentaiMama
|
|
||||||
//import ani.dantotsu.parsers.anime.HentaiStream
|
|
||||||
//import ani.dantotsu.parsers.anime.Marin
|
|
||||||
//import ani.dantotsu.parsers.anime.AniWave
|
|
||||||
//import ani.dantotsu.parsers.anime.Kaido
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
/*
|
|
||||||
object AnimeSources_old : WatchSources() {
|
|
||||||
override val list: List<Lazier<BaseParser>> = lazyList(
|
|
||||||
"AllAnime" to ::AllAnime,
|
|
||||||
"Gogo" to ::Gogo,
|
|
||||||
"Kaido" to ::Kaido,
|
|
||||||
"Marin" to ::Marin,
|
|
||||||
"AnimePahe" to ::AnimePahe,
|
|
||||||
"AniWave" to ::AniWave,
|
|
||||||
"AnimeDao" to ::AnimeDao,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
object AnimeSources : WatchSources() {
|
object AnimeSources : WatchSources() {
|
||||||
override var list: List<Lazier<BaseParser>> = emptyList()
|
override var list: List<Lazier<BaseParser>> = emptyList()
|
||||||
|
|
||||||
@@ -52,13 +29,8 @@ object AnimeSources : WatchSources() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
object HAnimeSources : WatchSources() {
|
object HAnimeSources : WatchSources() {
|
||||||
private val aList: List<Lazier<BaseParser>> = lazyList(
|
private val aList: List<Lazier<BaseParser>> = lazyList(
|
||||||
//"HentaiMama" to ::HentaiMama,
|
|
||||||
//"Haho" to ::Haho,
|
|
||||||
//"HentaiStream" to ::HentaiStream,
|
|
||||||
//"HentaiFF" to ::HentaiFF,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override val list = listOf(aList,AnimeSources.list).flatten()
|
override val list = listOf(aList,AnimeSources.list).flatten()
|
||||||
|
|||||||
@@ -1,17 +1,56 @@
|
|||||||
package ani.dantotsu.parsers
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.widget.Toast
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.aniyomi.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import ani.dantotsu.aniyomi.animesource.AnimeCatalogueSource
|
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException
|
||||||
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.logger
|
||||||
|
import ani.dantotsu.media.manga.ImageData
|
||||||
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.Track
|
import eu.kanade.tachiyomi.animesource.model.Track
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.*
|
||||||
|
import java.io.UnsupportedEncodingException
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
class AniyomiAdapter {
|
class AniyomiAdapter {
|
||||||
fun aniyomiToAnimeParser(extension: AnimeExtension.Installed): DynamicAnimeParser {
|
fun aniyomiToAnimeParser(extension: AnimeExtension.Installed): DynamicAnimeParser {
|
||||||
@@ -30,63 +69,64 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||||||
override val saveName = extension.name
|
override val saveName = extension.name
|
||||||
override val hostUrl = extension.sources.first().name
|
override val hostUrl = extension.sources.first().name
|
||||||
override val isDubAvailableSeparately = false
|
override val isDubAvailableSeparately = false
|
||||||
|
override val isNSFW = extension.isNsfw
|
||||||
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> {
|
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> {
|
||||||
val source = extension.sources.first()
|
val source = extension.sources.first()
|
||||||
if (source is AnimeCatalogueSource) {
|
if (source is AnimeCatalogueSource) {
|
||||||
var res: SEpisode? = null
|
|
||||||
try {
|
try {
|
||||||
val res = source.getEpisodeList(sAnime)
|
val res = source.getEpisodeList(sAnime)
|
||||||
var EpisodeList: List<Episode> = emptyList()
|
|
||||||
for (episode in res) {
|
// Sort episodes by episode_number
|
||||||
println("episode: $episode")
|
val sortedEpisodes = res.sortedBy { it.episode_number }
|
||||||
EpisodeList += SEpisodeToEpisode(episode)
|
|
||||||
}
|
// Transform SEpisode objects to Episode objects
|
||||||
return EpisodeList
|
|
||||||
}
|
return sortedEpisodes.map { SEpisodeToEpisode(it) }
|
||||||
catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("Exception: $e")
|
println("Exception: $e")
|
||||||
}
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
return emptyList() // Return an empty list if source is not an AnimeCatalogueSource
|
return emptyList() // Return an empty list if source is not an AnimeCatalogueSource
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadVideoServers(episodeLink: String, extra: Map<String, String>?, sEpisode: SEpisode): List<VideoServer> {
|
override suspend fun loadVideoServers(episodeLink: String, extra: Map<String, String>?, sEpisode: SEpisode): List<VideoServer> {
|
||||||
val source = extension.sources.first()
|
val source = extension.sources.first() as? AnimeCatalogueSource ?: return emptyList()
|
||||||
if (source is AnimeCatalogueSource) {
|
|
||||||
val video = source.getVideoList(sEpisode)
|
return try {
|
||||||
var VideoList: List<VideoServer> = emptyList()
|
val videos = source.getVideoList(sEpisode)
|
||||||
for (videoServer in video) {
|
videos.map { VideoToVideoServer(it) }
|
||||||
VideoList += VideoToVideoServer(videoServer)
|
} catch (e: Exception) {
|
||||||
}
|
logger("Exception occurred: ${e.message}")
|
||||||
return VideoList
|
emptyList()
|
||||||
}
|
}
|
||||||
return emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override suspend fun getVideoExtractor(server: VideoServer): VideoExtractor? {
|
override suspend fun getVideoExtractor(server: VideoServer): VideoExtractor? {
|
||||||
return VideoServerPassthrough(server)
|
return VideoServerPassthrough(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(query: String): List<ShowResponse> {
|
override suspend fun search(query: String): List<ShowResponse> {
|
||||||
val source = extension.sources.first()
|
val source = extension.sources.first() as? AnimeCatalogueSource ?: return emptyList()
|
||||||
if (source is AnimeCatalogueSource) {
|
|
||||||
|
|
||||||
var res: AnimesPage? = null
|
return try {
|
||||||
try {
|
val res = source.fetchSearchAnime(1, query, AnimeFilterList()).toBlocking().first()
|
||||||
res = source.fetchSearchAnime(0, query, AnimeFilterList()).toBlocking().first()
|
convertAnimesPageToShowResponse(res)
|
||||||
println("res: $res")
|
} catch (e: CloudflareBypassException) {
|
||||||
|
logger("Exception in search: $e")
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(currContext(), "Failed to bypass Cloudflare", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
emptyList()
|
||||||
logger("Exception: $e")
|
} catch (e: Exception) {
|
||||||
}
|
logger("General exception in search: $e")
|
||||||
|
emptyList()
|
||||||
val conv = convertAnimesPageToShowResponse(res!!)
|
|
||||||
return conv
|
|
||||||
}
|
}
|
||||||
return emptyList() // Return an empty list if source is not an AnimeCatalogueSource
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertAnimesPageToShowResponse(animesPage: AnimesPage): List<ShowResponse> {
|
|
||||||
|
private fun convertAnimesPageToShowResponse(animesPage: AnimesPage): List<ShowResponse> {
|
||||||
return animesPage.animes.map { sAnime ->
|
return animesPage.animes.map { sAnime ->
|
||||||
// Extract required fields from sAnime
|
// Extract required fields from sAnime
|
||||||
val name = sAnime.title
|
val name = sAnime.title
|
||||||
@@ -101,70 +141,328 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SEpisodeToEpisode(sEpisode: SEpisode): Episode {
|
private fun SEpisodeToEpisode(sEpisode: SEpisode): Episode {
|
||||||
val episode = Episode(
|
//if the float episode number is a whole number, convert it to an int
|
||||||
sEpisode.episode_number.toString(),
|
val episodeNumberInt =
|
||||||
|
if (sEpisode.episode_number % 1 == 0f) {
|
||||||
|
sEpisode.episode_number.toInt()
|
||||||
|
} else {
|
||||||
|
sEpisode.episode_number
|
||||||
|
}
|
||||||
|
return Episode(
|
||||||
|
episodeNumberInt.toString(),
|
||||||
sEpisode.url,
|
sEpisode.url,
|
||||||
sEpisode.name,
|
sEpisode.name,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
sEpisode)
|
sEpisode
|
||||||
return episode
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VideoToVideoServer(video: Video): VideoServer {
|
private fun VideoToVideoServer(video: Video): VideoServer {
|
||||||
val videoServer = VideoServer(
|
return VideoServer(
|
||||||
video.quality,
|
video.quality,
|
||||||
video.url,
|
video.url,
|
||||||
null,
|
null,
|
||||||
video)
|
video
|
||||||
return videoServer
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VideoServerPassthrough : VideoExtractor{
|
class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
||||||
val videoServer: VideoServer
|
val mangaCache = Injekt.get<MangaCache>()
|
||||||
constructor(videoServer: VideoServer) {
|
val extension: MangaExtension.Installed
|
||||||
this.videoServer = videoServer
|
init {
|
||||||
|
this.extension = extension
|
||||||
}
|
}
|
||||||
override val server: VideoServer
|
override val name = extension.name
|
||||||
get() {
|
override val saveName = extension.name
|
||||||
return videoServer
|
override val hostUrl = extension.sources.first().name
|
||||||
|
override val isNSFW = extension.isNsfw
|
||||||
|
|
||||||
|
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> {
|
||||||
|
val source = extension.sources.first() as? CatalogueSource ?: return emptyList()
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val res = source.getChapterList(sManga)
|
||||||
|
val reversedRes = res.reversed()
|
||||||
|
val chapterList = reversedRes.map { SChapterToMangaChapter(it) }
|
||||||
|
logger("chapterList size: ${chapterList.size}")
|
||||||
|
logger("chapterList: ${chapterList[1].title}")
|
||||||
|
logger("chapterList: ${chapterList[1].description}")
|
||||||
|
chapterList
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger("loadChapters Exception: $e")
|
||||||
|
emptyList()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> {
|
||||||
|
val source = extension.sources.first() as? HttpSource ?: return emptyList()
|
||||||
|
|
||||||
|
return coroutineScope {
|
||||||
|
try {
|
||||||
|
println("source.name " + source.name)
|
||||||
|
val res = source.getPageList(sChapter)
|
||||||
|
val reIndexedPages = res.mapIndexed { index, page -> Page(index, page.url, page.imageUrl, page.uri) }
|
||||||
|
|
||||||
|
val deferreds = reIndexedPages.map { page ->
|
||||||
|
async(Dispatchers.IO) {
|
||||||
|
mangaCache.put(page.imageUrl ?: "", ImageData(page, source))
|
||||||
|
logger("put page: ${page.imageUrl}")
|
||||||
|
pageToMangaImage(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deferreds.awaitAll()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger("loadImages Exception: $e")
|
||||||
|
Toast.makeText(currContext(), "Failed to load images: $e", Toast.LENGTH_SHORT).show()
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchAndProcessImage(page: Page, httpSource: HttpSource, context: Context): Bitmap? {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
// Fetch the image
|
||||||
|
val response = httpSource.getImage(page)
|
||||||
|
println("Response: ${response.code}")
|
||||||
|
println("Response: ${response.message}")
|
||||||
|
|
||||||
|
// Convert the Response to an InputStream
|
||||||
|
val inputStream = response.body?.byteStream()
|
||||||
|
|
||||||
|
// Convert InputStream to Bitmap
|
||||||
|
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
|
||||||
|
inputStream?.close()
|
||||||
|
ani.dantotsu.media.manga.saveImage(
|
||||||
|
bitmap,
|
||||||
|
context.contentResolver,
|
||||||
|
page.imageUrl!!,
|
||||||
|
Bitmap.CompressFormat.JPEG,
|
||||||
|
100
|
||||||
|
)
|
||||||
|
|
||||||
|
return@withContext bitmap
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Handle any exceptions
|
||||||
|
println("An error occurred: ${e.message}")
|
||||||
|
return@withContext null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun fetchAndSaveImage(page: Page, httpSource: HttpSource, contentResolver: ContentResolver) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
// Fetch the image
|
||||||
|
val response = httpSource.getImage(page)
|
||||||
|
|
||||||
|
// Convert the Response to an InputStream
|
||||||
|
val inputStream = response.body?.byteStream()
|
||||||
|
|
||||||
|
// Convert InputStream to Bitmap
|
||||||
|
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
// Save the Bitmap using MediaStore API
|
||||||
|
saveImage(bitmap, contentResolver, "image_${System.currentTimeMillis()}.jpg", Bitmap.CompressFormat.JPEG, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputStream?.close()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Handle any exceptions
|
||||||
|
println("An error occurred: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String, format: Bitmap.CompressFormat, quality: Int) {
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/${format.name.lowercase()}")
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Anime")
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
|
||||||
|
uri?.let {
|
||||||
|
contentResolver.openOutputStream(it)?.use { os ->
|
||||||
|
bitmap.compress(format, quality, os)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Anime")
|
||||||
|
if (!directory.exists()) {
|
||||||
|
directory.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(directory, filename)
|
||||||
|
FileOutputStream(file).use { outputStream ->
|
||||||
|
bitmap.compress(format, quality, outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Handle exception here
|
||||||
|
println("Exception while saving image: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<ShowResponse> {
|
||||||
|
val source = extension.sources.first() as? HttpSource ?: return emptyList()
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val res = source.fetchSearchManga(1, query, FilterList()).toBlocking().first()
|
||||||
|
logger("res observable: $res")
|
||||||
|
convertMangasPageToShowResponse(res)
|
||||||
|
} catch (e: CloudflareBypassException) {
|
||||||
|
logger("Exception in search: $e")
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(currContext(), "Failed to bypass Cloudflare", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
emptyList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger("General exception in search: $e")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun convertMangasPageToShowResponse(mangasPage: MangasPage): List<ShowResponse> {
|
||||||
|
return mangasPage.mangas.map { sManga ->
|
||||||
|
// Extract required fields from sManga
|
||||||
|
val name = sManga.title
|
||||||
|
val link = sManga.url
|
||||||
|
val coverUrl = sManga.thumbnail_url ?: ""
|
||||||
|
val otherNames = emptyList<String>() // Populate as needed
|
||||||
|
val total = 1
|
||||||
|
val extra: Map<String, String>? = null // Populate as needed
|
||||||
|
|
||||||
|
// Create a new ShowResponse
|
||||||
|
ShowResponse(name, link, coverUrl, sManga)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pageToMangaImage(page: Page): MangaImage {
|
||||||
|
var headersMap = mapOf<String, String>()
|
||||||
|
var urlWithoutHeaders = ""
|
||||||
|
var url = ""
|
||||||
|
|
||||||
|
page.imageUrl?.let {
|
||||||
|
val splitUrl = it.split("&")
|
||||||
|
urlWithoutHeaders = splitUrl.getOrNull(0) ?: ""
|
||||||
|
url = it
|
||||||
|
|
||||||
|
headersMap = splitUrl.mapNotNull { part ->
|
||||||
|
val idx = part.indexOf("=")
|
||||||
|
if (idx != -1) {
|
||||||
|
try {
|
||||||
|
val key = URLDecoder.decode(part.substring(0, idx), "UTF-8")
|
||||||
|
val value = URLDecoder.decode(part.substring(idx + 1), "UTF-8")
|
||||||
|
Pair(key, value)
|
||||||
|
} catch (e: UnsupportedEncodingException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
return MangaImage(
|
||||||
|
FileUrl(url, headersMap),
|
||||||
|
false,
|
||||||
|
page
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter {
|
||||||
|
/*val parsedChapterTitle = parseChapterTitle(sChapter.name)
|
||||||
|
val number = if (sChapter.chapter_number.toInt() != -1){
|
||||||
|
sChapter.chapter_number.toString()
|
||||||
|
} else if(parsedChapterTitle.first != null || parsedChapterTitle.second != null){
|
||||||
|
(parsedChapterTitle.first ?: "") + "." + (parsedChapterTitle.second ?: "")
|
||||||
|
}else{
|
||||||
|
sChapter.name
|
||||||
|
}*/
|
||||||
|
return MangaChapter(
|
||||||
|
sChapter.name,
|
||||||
|
sChapter.url,
|
||||||
|
//if (parsedChapterTitle.first != null || parsedChapterTitle.second != null) {
|
||||||
|
// parsedChapterTitle.third
|
||||||
|
//} else {
|
||||||
|
sChapter.name,
|
||||||
|
//},
|
||||||
|
null,
|
||||||
|
sChapter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseChapterTitle(title: String): Triple<String?, String?, String> {
|
||||||
|
val volumePattern = Pattern.compile("(?:vol\\.?|v|volume\\s?)(\\d+)", Pattern.CASE_INSENSITIVE)
|
||||||
|
val chapterPattern = Pattern.compile("(?:ch\\.?|chapter\\s?)(\\d+)", Pattern.CASE_INSENSITIVE)
|
||||||
|
|
||||||
|
val volumeMatcher = volumePattern.matcher(title)
|
||||||
|
val chapterMatcher = chapterPattern.matcher(title)
|
||||||
|
|
||||||
|
val volumeNumber = if (volumeMatcher.find()) volumeMatcher.group(1) else null
|
||||||
|
val chapterNumber = if (chapterMatcher.find()) chapterMatcher.group(1) else null
|
||||||
|
|
||||||
|
var remainingTitle = title
|
||||||
|
if (volumeNumber != null) {
|
||||||
|
remainingTitle = volumeMatcher.group(0)?.let { remainingTitle.replace(it, "") }.toString()
|
||||||
|
}
|
||||||
|
if (chapterNumber != null) {
|
||||||
|
remainingTitle = chapterMatcher.group(0)?.let { remainingTitle.replace(it, "") }.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Triple(volumeNumber, chapterNumber, remainingTitle.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
||||||
|
override val server: VideoServer
|
||||||
|
get() = videoServer
|
||||||
|
|
||||||
override suspend fun extract(): VideoContainer {
|
override suspend fun extract(): VideoContainer {
|
||||||
val vidList = listOfNotNull(videoServer.video?.let { AniVideoToSaiVideo(it) })
|
val vidList = listOfNotNull(videoServer.video?.let { AniVideoToSaiVideo(it) })
|
||||||
var subList: List<Subtitle> = emptyList()
|
val subList = videoServer.video?.subtitleTracks?.map { TrackToSubtitle(it) } ?: emptyList()
|
||||||
for(sub in videoServer.video?.subtitleTracks ?: emptyList()) {
|
|
||||||
subList += TrackToSubtitle(sub)
|
return if (vidList.isNotEmpty()) {
|
||||||
}
|
VideoContainer(vidList, subList)
|
||||||
if(vidList.isEmpty()) {
|
} else {
|
||||||
throw Exception("No videos found")
|
throw Exception("No videos found")
|
||||||
}else{
|
|
||||||
return VideoContainer(vidList, subList)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AniVideoToSaiVideo(aniVideo: eu.kanade.tachiyomi.animesource.model.Video) : ani.dantotsu.parsers.Video {
|
private fun AniVideoToSaiVideo(aniVideo: eu.kanade.tachiyomi.animesource.model.Video) : ani.dantotsu.parsers.Video {
|
||||||
//try to find the number value from the .quality string
|
// Find the number value from the .quality string
|
||||||
val regex = Regex("""\d+""")
|
val number = Regex("""\d+""").find(aniVideo.quality)?.value?.toInt() ?: 0
|
||||||
val result = regex.find(aniVideo.quality)
|
|
||||||
val number = result?.value?.toInt() ?: 0
|
// Check for null video URL
|
||||||
val videoUrl = aniVideo.videoUrl ?: throw Exception("Video URL is null")
|
val videoUrl = aniVideo.videoUrl ?: throw Exception("Video URL is null")
|
||||||
|
|
||||||
val urlObj = URL(videoUrl)
|
val urlObj = URL(videoUrl)
|
||||||
val path = urlObj.path
|
val path = urlObj.path
|
||||||
val query = urlObj.query
|
val query = urlObj.query
|
||||||
|
|
||||||
|
var format = getVideoType(path)
|
||||||
|
|
||||||
var format = when {
|
if (format == null && query != null) {
|
||||||
path.endsWith(".mp4", ignoreCase = true) || videoUrl.endsWith(".mkv", ignoreCase = true) -> VideoType.CONTAINER
|
|
||||||
path.endsWith(".m3u8", ignoreCase = true) -> VideoType.M3U8
|
|
||||||
path.endsWith(".mpd", ignoreCase = true) -> VideoType.DASH
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
if (format == null) {
|
|
||||||
val queryPairs: List<Pair<String, String>> = query.split("&").map {
|
val queryPairs: List<Pair<String, String>> = query.split("&").map {
|
||||||
val idx = it.indexOf("=")
|
val idx = it.indexOf("=")
|
||||||
val key = URLDecoder.decode(it.substring(0, idx), "UTF-8")
|
val key = URLDecoder.decode(it.substring(0, idx), "UTF-8")
|
||||||
@@ -175,13 +473,9 @@ class VideoServerPassthrough : VideoExtractor{
|
|||||||
// Assume the file is named under the "file" query parameter
|
// Assume the file is named under the "file" query parameter
|
||||||
val fileName = queryPairs.find { it.first == "file" }?.second ?: ""
|
val fileName = queryPairs.find { it.first == "file" }?.second ?: ""
|
||||||
|
|
||||||
format = when {
|
format = getVideoType(fileName)
|
||||||
fileName.endsWith(".mp4", ignoreCase = true) || fileName.endsWith(".mkv", ignoreCase = true) -> VideoType.CONTAINER
|
|
||||||
fileName.endsWith(".m3u8", ignoreCase = true) -> VideoType.M3U8
|
|
||||||
fileName.endsWith(".mpd", ignoreCase = true) -> VideoType.DASH
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the format is still undetermined, log an error or handle it appropriately
|
// If the format is still undetermined, log an error or handle it appropriately
|
||||||
if (format == null) {
|
if (format == null) {
|
||||||
logger("Unknown video format: $videoUrl")
|
logger("Unknown video format: $videoUrl")
|
||||||
@@ -198,7 +492,33 @@ class VideoServerPassthrough : VideoExtractor{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TrackToSubtitle(track: Track, type: SubtitleType = SubtitleType.VTT): Subtitle {
|
private fun getVideoType(fileName: String): VideoType? {
|
||||||
return Subtitle(track.lang, track.url, type)
|
return when {
|
||||||
|
fileName.endsWith(".mp4", ignoreCase = true) || fileName.endsWith(".mkv", ignoreCase = true) -> VideoType.CONTAINER
|
||||||
|
fileName.endsWith(".m3u8", ignoreCase = true) -> VideoType.M3U8
|
||||||
|
fileName.endsWith(".mpd", ignoreCase = true) -> VideoType.DASH
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TrackToSubtitle(track: Track): Subtitle {
|
||||||
|
//use Dispatchers.IO to make a HTTP request to determine the subtitle type
|
||||||
|
var type: SubtitleType? = null
|
||||||
|
runBlocking {
|
||||||
|
type = findSubtitleType(track.url)
|
||||||
|
}
|
||||||
|
return Subtitle(track.lang, track.url, type?: SubtitleType.SRT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSubtitleType(url: String): SubtitleType? {
|
||||||
|
// First, try to determine the type based on the URL file extension
|
||||||
|
var type: SubtitleType? = when {
|
||||||
|
url.endsWith(".vtt", true) -> SubtitleType.VTT
|
||||||
|
url.endsWith(".ass", true) -> SubtitleType.ASS
|
||||||
|
url.endsWith(".srt", true) -> SubtitleType.SRT
|
||||||
|
else -> SubtitleType.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
return type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,12 @@ package ani.dantotsu.parsers
|
|||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
|
|
||||||
|
|
||||||
abstract class BaseParser {
|
abstract class BaseParser {
|
||||||
|
|
||||||
@@ -54,21 +57,41 @@ abstract class BaseParser {
|
|||||||
setUserText("Searching : ${mediaObj.mainName()}")
|
setUserText("Searching : ${mediaObj.mainName()}")
|
||||||
val results = search(mediaObj.mainName())
|
val results = search(mediaObj.mainName())
|
||||||
val sortedResults = if (results.isNotEmpty()) {
|
val sortedResults = if (results.isNotEmpty()) {
|
||||||
StringMatcher.closestShowMovedToTop(mediaObj.mainName(), results)
|
results.sortedByDescending { FuzzySearch.ratio(it.name, mediaObj.mainName()) }
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
response = sortedResults.firstOrNull()
|
response = sortedResults.firstOrNull()
|
||||||
|
|
||||||
if (response == null) {
|
if (response == null || FuzzySearch.ratio(response.name, mediaObj.mainName()) < 100) {
|
||||||
setUserText("Searching : ${mediaObj.nameRomaji}")
|
setUserText("Searching : ${mediaObj.nameRomaji}")
|
||||||
val romajiResults = search(mediaObj.nameRomaji)
|
val romajiResults = search(mediaObj.nameRomaji)
|
||||||
val sortedRomajiResults = if (romajiResults.isNotEmpty()) {
|
val sortedRomajiResults = if (romajiResults.isNotEmpty()) {
|
||||||
StringMatcher.closestShowMovedToTop(mediaObj.nameRomaji, romajiResults)
|
romajiResults.sortedByDescending { FuzzySearch.ratio(it.name, mediaObj.nameRomaji) }
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
response = sortedRomajiResults.firstOrNull()
|
val closestRomaji = sortedRomajiResults.firstOrNull()
|
||||||
|
logger("Closest match from RomajiResults: ${closestRomaji?.name ?: "None"}")
|
||||||
|
|
||||||
|
response = if (response == null) {
|
||||||
|
logger("No exact match found in results. Using closest match from RomajiResults.")
|
||||||
|
closestRomaji
|
||||||
|
} else {
|
||||||
|
val romajiRatio = FuzzySearch.ratio(closestRomaji?.name ?: "", mediaObj.nameRomaji)
|
||||||
|
val mainNameRatio = FuzzySearch.ratio(response.name, mediaObj.mainName())
|
||||||
|
logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name}")
|
||||||
|
logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name ?: "None"}")
|
||||||
|
|
||||||
|
if (romajiRatio > mainNameRatio) {
|
||||||
|
logger("RomajiResults has a closer match. Replacing response.")
|
||||||
|
closestRomaji
|
||||||
|
} else {
|
||||||
|
logger("Results has a closer or equal match. Keeping existing response.")
|
||||||
|
response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
saveShowResponse(mediaObj.id, response)
|
saveShowResponse(mediaObj.id, response)
|
||||||
}
|
}
|
||||||
@@ -141,7 +164,10 @@ data class ShowResponse(
|
|||||||
val extra : Map<String,String>?=null,
|
val extra : Map<String,String>?=null,
|
||||||
|
|
||||||
//SAnime object from Aniyomi
|
//SAnime object from Aniyomi
|
||||||
val sAnime: SAnime?=null
|
val sAnime: SAnime? = null,
|
||||||
|
|
||||||
|
//SManga object from Aniyomi
|
||||||
|
val sManga: SManga? = null
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
constructor(name: String, link: String, coverUrl: String, otherNames: List<String> = listOf(), total: Int? = null, extra: Map<String, String>?=null)
|
constructor(name: String, link: String, coverUrl: String, otherNames: List<String> = listOf(), total: Int? = null, extra: Map<String, String>?=null)
|
||||||
: this(name, link, FileUrl(coverUrl), otherNames, total, extra)
|
: this(name, link, FileUrl(coverUrl), otherNames, total, extra)
|
||||||
@@ -157,6 +183,9 @@ data class ShowResponse(
|
|||||||
|
|
||||||
constructor(name: String, link: String, coverUrl: String, sAnime: SAnime)
|
constructor(name: String, link: String, coverUrl: String, sAnime: SAnime)
|
||||||
: this(name, link, FileUrl(coverUrl), sAnime = sAnime)
|
: this(name, link, FileUrl(coverUrl), sAnime = sAnime)
|
||||||
|
|
||||||
|
constructor(name: String, link: String, coverUrl: String, sManga: SManga)
|
||||||
|
: this(name, link, FileUrl(coverUrl), sManga = sManga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ani.dantotsu.parsers
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
import ani.dantotsu.Lazier
|
import ani.dantotsu.Lazier
|
||||||
|
import ani.dantotsu.logger
|
||||||
import ani.dantotsu.media.anime.Episode
|
import ani.dantotsu.media.anime.Episode
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
@@ -10,9 +11,11 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
|
|||||||
abstract class WatchSources : BaseSources() {
|
abstract class WatchSources : BaseSources() {
|
||||||
|
|
||||||
override operator fun get(i: Int): AnimeParser {
|
override operator fun get(i: Int): AnimeParser {
|
||||||
return (list.getOrNull(i)?:list[0]).get.value as AnimeParser
|
return (list.getOrNull(i) ?: list.firstOrNull())?.get?.value as? AnimeParser
|
||||||
|
?: EmptyAnimeParser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap<String, Episode> {
|
suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap<String, Episode> {
|
||||||
return tryWithSuspend(true) {
|
return tryWithSuspend(true) {
|
||||||
val res = get(i).autoSearch(media) ?: return@tryWithSuspend mutableMapOf()
|
val res = get(i).autoSearch(media) ?: return@tryWithSuspend mutableMapOf()
|
||||||
@@ -39,7 +42,8 @@ abstract class WatchSources : BaseSources() {
|
|||||||
abstract class MangaReadSources : BaseSources() {
|
abstract class MangaReadSources : BaseSources() {
|
||||||
|
|
||||||
override operator fun get(i: Int): MangaParser {
|
override operator fun get(i: Int): MangaParser {
|
||||||
return (list.getOrNull(i)?:list[0]).get.value as MangaParser
|
return (list.getOrNull(i)?:list.firstOrNull())?.get?.value as? MangaParser
|
||||||
|
?: EmptyMangaParser()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadChaptersFromMedia(i: Int, media: Media): MutableMap<String, MangaChapter> {
|
suspend fun loadChaptersFromMedia(i: Int, media: Media): MutableMap<String, MangaChapter> {
|
||||||
@@ -52,11 +56,17 @@ abstract class MangaReadSources : BaseSources() {
|
|||||||
suspend fun loadChapters(i: Int, show: ShowResponse): MutableMap<String, MangaChapter> {
|
suspend fun loadChapters(i: Int, show: ShowResponse): MutableMap<String, MangaChapter> {
|
||||||
val map = mutableMapOf<String, MangaChapter>()
|
val map = mutableMapOf<String, MangaChapter>()
|
||||||
val parser = get(i)
|
val parser = get(i)
|
||||||
tryWithSuspend(true) {
|
show.sManga?.let { sManga ->
|
||||||
parser.loadChapters(show.link, show.extra).forEach {
|
tryWithSuspend(true) {
|
||||||
map[it.number] = MangaChapter(it)
|
parser.loadChapters(show.link, show.extra, sManga).forEach {
|
||||||
|
map[it.number] = MangaChapter(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(show.sManga == null) {
|
||||||
|
logger("sManga is null")
|
||||||
|
}
|
||||||
|
logger("map size ${map.size}")
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package ani.dantotsu.parsers
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
abstract class MangaParser : BaseParser() {
|
abstract class MangaParser : BaseParser() {
|
||||||
@@ -10,7 +15,7 @@ abstract class MangaParser : BaseParser() {
|
|||||||
/**
|
/**
|
||||||
* Takes ShowResponse.link and ShowResponse.extra (if any) as arguments & gives a list of total chapters present on the site.
|
* Takes ShowResponse.link and ShowResponse.extra (if any) as arguments & gives a list of total chapters present on the site.
|
||||||
* **/
|
* **/
|
||||||
abstract suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?): List<MangaChapter>
|
abstract suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes ShowResponse.link, ShowResponse.extra & the Last Largest Chapter Number known by app as arguments
|
* Takes ShowResponse.link, ShowResponse.extra & the Last Largest Chapter Number known by app as arguments
|
||||||
@@ -18,37 +23,34 @@ abstract class MangaParser : BaseParser() {
|
|||||||
* Returns the latest chapter (If overriding, Make sure the chapter is actually the latest chapter)
|
* Returns the latest chapter (If overriding, Make sure the chapter is actually the latest chapter)
|
||||||
* Returns null, if no latest chapter is found.
|
* Returns null, if no latest chapter is found.
|
||||||
* **/
|
* **/
|
||||||
open suspend fun getLatestChapter(mangaLink: String, extra: Map<String, String>?, latest: Float): MangaChapter? {
|
open suspend fun getLatestChapter(mangaLink: String, extra: Map<String, String>?, sManga: SManga, latest: Float): MangaChapter? {
|
||||||
return loadChapters(mangaLink, extra)
|
val chapter = loadChapters(mangaLink, extra, sManga)
|
||||||
.maxByOrNull { it.number.toFloatOrNull() ?: 0f }
|
val max = chapter
|
||||||
?.takeIf { latest < (it.number.toFloatOrNull() ?: 0.001f) }
|
.maxByOrNull { MangaNameAdapter.findChapterNumber(it.number) ?: 0f }
|
||||||
|
return max
|
||||||
|
?.takeIf { latest < (MangaNameAdapter.findChapterNumber(it.number) ?: 0.001f) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes MangaChapter.link as an argument & returns a list of MangaImages with their Url (with headers & transformations, if needed)
|
* Takes MangaChapter.link as an argument & returns a list of MangaImages with their Url (with headers & transformations, if needed)
|
||||||
* **/
|
* **/
|
||||||
abstract suspend fun loadImages(chapterLink: String): List<MangaImage>
|
abstract suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage>
|
||||||
|
|
||||||
override suspend fun autoSearch(mediaObj: Media): ShowResponse? {
|
|
||||||
var response = loadSavedShowResponse(mediaObj.id)
|
|
||||||
if (response != null) {
|
|
||||||
saveShowResponse(mediaObj.id, response, true)
|
|
||||||
} else {
|
|
||||||
setUserText("Searching : ${mediaObj.mangaName()}")
|
|
||||||
response = search(mediaObj.mangaName()).let { if (it.isNotEmpty()) it[0] else null }
|
|
||||||
|
|
||||||
if (response == null) {
|
|
||||||
setUserText("Searching : ${mediaObj.nameRomaji}")
|
|
||||||
response = search(mediaObj.nameRomaji).let { if (it.isNotEmpty()) it[0] else null }
|
|
||||||
}
|
|
||||||
saveShowResponse(mediaObj.id, response)
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getTransformation(): BitmapTransformation? = null
|
open fun getTransformation(): BitmapTransformation? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EmptyMangaParser: MangaParser() {
|
||||||
|
override val name: String = "None"
|
||||||
|
override val saveName: String = "None"
|
||||||
|
|
||||||
|
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> = emptyList()
|
||||||
|
|
||||||
|
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> = emptyList()
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<ShowResponse> = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
data class MangaChapter(
|
data class MangaChapter(
|
||||||
/**
|
/**
|
||||||
* Number of the Chapter in "String",
|
* Number of the Chapter in "String",
|
||||||
@@ -65,6 +67,8 @@ data class MangaChapter(
|
|||||||
//Self-Descriptive
|
//Self-Descriptive
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
|
|
||||||
|
val sChapter: SChapter,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class MangaImage(
|
data class MangaImage(
|
||||||
@@ -75,8 +79,10 @@ data class MangaImage(
|
|||||||
* **/
|
* **/
|
||||||
val url: FileUrl,
|
val url: FileUrl,
|
||||||
|
|
||||||
val useTransformation: Boolean = false
|
val useTransformation: Boolean = false,
|
||||||
|
|
||||||
|
val page: Page
|
||||||
) : Serializable{
|
) : Serializable{
|
||||||
constructor(url: String,useTransformation: Boolean=false)
|
constructor(url: String,useTransformation: Boolean=false, page: Page)
|
||||||
: this(FileUrl(url),useTransformation)
|
: this(FileUrl(url),useTransformation, page)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,36 @@ package ani.dantotsu.parsers
|
|||||||
|
|
||||||
import ani.dantotsu.Lazier
|
import ani.dantotsu.Lazier
|
||||||
import ani.dantotsu.lazyList
|
import ani.dantotsu.lazyList
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
object MangaSources : MangaReadSources() {
|
object MangaSources : MangaReadSources() {
|
||||||
override val list: List<Lazier<BaseParser>> = lazyList(
|
override var list: List<Lazier<BaseParser>> = emptyList()
|
||||||
)
|
|
||||||
|
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
|
||||||
|
// Initialize with the first value from StateFlow
|
||||||
|
val initialExtensions = fromExtensions.first()
|
||||||
|
list = createParsersFromExtensions(initialExtensions)
|
||||||
|
|
||||||
|
// Update as StateFlow emits new values
|
||||||
|
fromExtensions.collect { extensions ->
|
||||||
|
list = createParsersFromExtensions(extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createParsersFromExtensions(extensions: List<MangaExtension.Installed>): List<Lazier<BaseParser>> {
|
||||||
|
return extensions.map { extension ->
|
||||||
|
val name = extension.name
|
||||||
|
Lazier({ DynamicMangaParser(extension) }, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object HMangaSources : MangaReadSources() {
|
object HMangaSources : MangaReadSources() {
|
||||||
val aList: List<Lazier<BaseParser>> = lazyList(
|
val aList: List<Lazier<BaseParser>> = lazyList()
|
||||||
)
|
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
|
||||||
|
//todo
|
||||||
|
}
|
||||||
override val list = listOf(aList,MangaSources.list).flatten()
|
override val list = listOf(aList,MangaSources.list).flatten()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package ani.dantotsu.parsers
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
|
import ani.dantotsu.logger
|
||||||
|
|
||||||
class StringMatcher {
|
class StringMatcher {
|
||||||
companion object {
|
companion object {
|
||||||
private fun levenshteinDistance(s1: String, s2: String): Int {
|
private fun levenshteinDistance(s1: String, s2: String): Int {
|
||||||
@@ -52,8 +54,10 @@ class StringMatcher {
|
|||||||
val closestShowAndIndex = closestShow(target, shows)
|
val closestShowAndIndex = closestShow(target, shows)
|
||||||
val closestIndex = closestShowAndIndex.second
|
val closestIndex = closestShowAndIndex.second
|
||||||
if (closestIndex == -1) {
|
if (closestIndex == -1) {
|
||||||
|
logger("No closest show found for $target")
|
||||||
return shows // Return original list if no closest show found
|
return shows // Return original list if no closest show found
|
||||||
}
|
}
|
||||||
|
logger("Closest show found for $target is ${closestShowAndIndex.first.name}")
|
||||||
return listOf(shows[closestIndex]) + shows.subList(0, closestIndex) + shows.subList(closestIndex + 1, shows.size)
|
return listOf(shows[closestIndex]) + shows.subList(0, closestIndex) + shows.subList(closestIndex + 1, shows.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,5 +159,5 @@ enum class VideoType{
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class SubtitleType{
|
enum class SubtitleType{
|
||||||
VTT, ASS, SRT
|
VTT, ASS, SRT, UNKNOWN
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
|
||||||
|
import ani.dantotsu.loadData
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class AnimeExtensionsFragment : Fragment(),
|
||||||
|
SearchQueryHandler {
|
||||||
|
private var _binding: FragmentAnimeExtensionsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
val skipIcons = loadData("skip_extension_icons") ?: false
|
||||||
|
|
||||||
|
private lateinit var extensionsRecyclerView: RecyclerView
|
||||||
|
private lateinit var allextenstionsRecyclerView: RecyclerView
|
||||||
|
private val animeExtensionManager: AnimeExtensionManager = Injekt.get<AnimeExtensionManager>()
|
||||||
|
private val extensionsAdapter = AnimeExtensionsAdapter({ pkg ->
|
||||||
|
if (isAdded) { // Check if the fragment is currently added to its activity
|
||||||
|
val context = requireContext() // Store context in a variable
|
||||||
|
val notificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
||||||
|
|
||||||
|
if (pkg.hasUpdate) {
|
||||||
|
animeExtensionManager.updateExtension(pkg)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
||||||
|
.subscribe(
|
||||||
|
{ installStep ->
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_sync_24)
|
||||||
|
.setContentTitle("Updating extension")
|
||||||
|
.setContentText("Step: $installStep")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(error)
|
||||||
|
Log.e("AnimeExtensionsAdapter", "Error: ", error) // Log the error
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_info_24)
|
||||||
|
.setContentTitle("Update failed: ${error.message}")
|
||||||
|
.setContentText("Error: ${error.message}")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
||||||
|
.setContentTitle("Update complete")
|
||||||
|
.setContentText("The extension has been successfully updated.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
animeExtensionManager.uninstallExtension(pkg.pkgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, skipIcons)
|
||||||
|
|
||||||
|
private val allExtensionsAdapter = AllAnimeExtensionsAdapter(lifecycleScope, { pkgName ->
|
||||||
|
val context = requireContext()
|
||||||
|
if (isAdded) {
|
||||||
|
val notificationManager =
|
||||||
|
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
// Start the installation process
|
||||||
|
animeExtensionManager.installExtension(pkgName)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ installStep ->
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_sync_24)
|
||||||
|
.setContentTitle("Installing extension")
|
||||||
|
.setContentText("Step: $installStep")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(error)
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_info_24)
|
||||||
|
.setContentTitle("Installation failed: ${error.message}")
|
||||||
|
.setContentText("Error: ${error.message}")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
||||||
|
.setContentTitle("Installation complete")
|
||||||
|
.setContentText("The extension has been successfully installed.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, skipIcons)
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
extensionsRecyclerView = binding.animeExtensionsRecyclerView
|
||||||
|
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
extensionsRecyclerView.adapter = extensionsAdapter
|
||||||
|
|
||||||
|
allextenstionsRecyclerView = binding.allAnimeExtensionsRecyclerView
|
||||||
|
allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
allextenstionsRecyclerView.adapter = allExtensionsAdapter
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
animeExtensionManager.installedExtensionsFlow.collect { extensions ->
|
||||||
|
extensionsAdapter.updateData(extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
combine(
|
||||||
|
animeExtensionManager.availableExtensionsFlow,
|
||||||
|
animeExtensionManager.installedExtensionsFlow
|
||||||
|
) { availableExtensions, installedExtensions ->
|
||||||
|
// Pair of available and installed extensions
|
||||||
|
Pair(availableExtensions, installedExtensions)
|
||||||
|
}.collect { pair ->
|
||||||
|
val (availableExtensions, installedExtensions) = pair
|
||||||
|
|
||||||
|
allExtensionsAdapter.updateData(availableExtensions, installedExtensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val extensionsRecyclerView: RecyclerView = binding.animeExtensionsRecyclerView
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateContentBasedOnQuery(query: String?) {
|
||||||
|
if (query.isNullOrEmpty()) {
|
||||||
|
allExtensionsAdapter.filter("") // Reset the filter
|
||||||
|
allextenstionsRecyclerView.visibility = View.VISIBLE
|
||||||
|
extensionsRecyclerView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
allExtensionsAdapter.filter(query)
|
||||||
|
allextenstionsRecyclerView.visibility = View.VISIBLE
|
||||||
|
extensionsRecyclerView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView();_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class AnimeExtensionsAdapter(
|
||||||
|
private val onUninstallClicked: (AnimeExtension.Installed) -> Unit,
|
||||||
|
skipIcons: Boolean
|
||||||
|
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
|
||||||
|
DIFF_CALLBACK_INSTALLED
|
||||||
|
) {
|
||||||
|
|
||||||
|
val skipIcons = skipIcons
|
||||||
|
|
||||||
|
fun updateData(newExtensions: List<AnimeExtension.Installed>) {
|
||||||
|
submitList(newExtensions) // Use submitList instead of manual list handling
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_extension, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val extension = getItem(position) // Use getItem() from ListAdapter
|
||||||
|
holder.extensionNameTextView.text = extension.name
|
||||||
|
if (!skipIcons) {
|
||||||
|
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||||
|
}
|
||||||
|
if (extension.hasUpdate) {
|
||||||
|
holder.closeTextView.text = "Update"
|
||||||
|
holder.closeTextView.setTextColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
holder.itemView.context,
|
||||||
|
R.color.warning
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
holder.closeTextView.text = "Uninstall"
|
||||||
|
}
|
||||||
|
holder.closeTextView.setOnClickListener {
|
||||||
|
onUninstallClicked(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||||
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
|
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DIFF_CALLBACK_INSTALLED =
|
||||||
|
object : DiffUtil.ItemCallback<AnimeExtension.Installed>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: AnimeExtension.Installed,
|
||||||
|
newItem: AnimeExtension.Installed
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.pkgName == newItem.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: AnimeExtension.Installed,
|
||||||
|
newItem: AnimeExtension.Installed
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class AllAnimeExtensionsAdapter(
|
||||||
|
private val coroutineScope: CoroutineScope,
|
||||||
|
private val onButtonClicked: (AnimeExtension.Available) -> Unit,
|
||||||
|
skipIcons: Boolean
|
||||||
|
) : ListAdapter<AnimeExtension.Available, AllAnimeExtensionsAdapter.ViewHolder>(
|
||||||
|
DIFF_CALLBACK_AVAILABLE
|
||||||
|
) {
|
||||||
|
val skipIcons = skipIcons
|
||||||
|
|
||||||
|
fun updateData(
|
||||||
|
newExtensions: List<AnimeExtension.Available>,
|
||||||
|
installedExtensions: List<AnimeExtension.Installed> = emptyList()
|
||||||
|
) {
|
||||||
|
coroutineScope.launch(Dispatchers.Default) {
|
||||||
|
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
|
||||||
|
val filteredExtensions = newExtensions.filter { it.pkgName !in installedPkgNames }
|
||||||
|
|
||||||
|
// Switch back to main thread to update UI
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
submitList(filteredExtensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): AllAnimeExtensionsAdapter.ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_extension_all, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val extension = getItem(position)
|
||||||
|
|
||||||
|
holder.extensionNameTextView.text = extension.name
|
||||||
|
|
||||||
|
if (!skipIcons) {
|
||||||
|
Glide.with(holder.itemView.context)
|
||||||
|
.load(extension.iconUrl)
|
||||||
|
.into(holder.extensionIconImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.closeTextView.text = "Install"
|
||||||
|
holder.closeTextView.setOnClickListener {
|
||||||
|
onButtonClicked(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filter(query: String) {
|
||||||
|
val filteredExtensions = if (query.isEmpty()) {
|
||||||
|
currentList
|
||||||
|
} else {
|
||||||
|
currentList.filter { it.name.contains(query, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
submitList(filteredExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||||
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
|
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DIFF_CALLBACK_AVAILABLE =
|
||||||
|
object : DiffUtil.ItemCallback<AnimeExtension.Available>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: AnimeExtension.Available,
|
||||||
|
newItem: AnimeExtension.Available
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.pkgName == newItem.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: AnimeExtension.Available,
|
||||||
|
newItem: AnimeExtension.Available
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,129 +3,95 @@ package ani.dantotsu.settings
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build.*
|
import android.os.Build.*
|
||||||
import android.os.Build.VERSION.*
|
import android.os.Build.VERSION.*
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.SearchView
|
import android.widget.SearchView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.aniyomi.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import ani.dantotsu.aniyomi.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
||||||
|
import ani.dantotsu.home.AnimeFragment
|
||||||
|
import ani.dantotsu.home.MangaFragment
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
class ExtensionsActivity : AppCompatActivity() {
|
class ExtensionsActivity : AppCompatActivity() {
|
||||||
private val restartMainActivity = object : OnBackPressedCallback(false) {
|
private val restartMainActivity = object : OnBackPressedCallback(false) {
|
||||||
override fun handleOnBackPressed() = startMainActivity(this@ExtensionsActivity)
|
override fun handleOnBackPressed() = startMainActivity(this@ExtensionsActivity)
|
||||||
}
|
}
|
||||||
lateinit var binding: ActivityExtensionsBinding
|
lateinit var binding: ActivityExtensionsBinding
|
||||||
private lateinit var extensionsRecyclerView: RecyclerView
|
|
||||||
private lateinit var allextenstionsRecyclerView: RecyclerView
|
|
||||||
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
|
|
||||||
private val extensionsAdapter = ExtensionsAdapter { pkgName ->
|
|
||||||
animeExtensionManager.uninstallExtension(pkgName)
|
|
||||||
}
|
|
||||||
private val allExtensionsAdapter = AllExtensionsAdapter(lifecycleScope) { pkgName ->
|
|
||||||
|
|
||||||
val notificationManager =
|
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
|
|
||||||
// Start the installation process
|
|
||||||
animeExtensionManager.installExtension(pkgName)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
{ installStep ->
|
|
||||||
val builder = NotificationCompat.Builder(this,
|
|
||||||
ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
|
||||||
)
|
|
||||||
.setSmallIcon(R.drawable.ic_round_sync_24)
|
|
||||||
.setContentTitle("Installing extension")
|
|
||||||
.setContentText("Step: $installStep")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
},
|
|
||||||
{ error ->
|
|
||||||
val builder = NotificationCompat.Builder(this,
|
|
||||||
ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_ERROR
|
|
||||||
)
|
|
||||||
.setSmallIcon(R.drawable.ic_round_info_24)
|
|
||||||
.setContentTitle("Installation failed")
|
|
||||||
.setContentText("Error: ${error.message}")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
},
|
|
||||||
{
|
|
||||||
val builder = NotificationCompat.Builder(this,
|
|
||||||
ani.dantotsu.aniyomi.data.Notifications.CHANNEL_DOWNLOADER_PROGRESS)
|
|
||||||
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
|
||||||
.setContentTitle("Installation complete")
|
|
||||||
.setContentText("The extension has been successfully installed.")
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
notificationManager.notify(1, builder.build())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityExtensionsBinding.inflate(layoutInflater)
|
binding = ActivityExtensionsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
extensionsRecyclerView = findViewById(R.id.extensionsRecyclerView)
|
|
||||||
extensionsRecyclerView.layoutManager = LinearLayoutManager(this)
|
|
||||||
extensionsRecyclerView.adapter = extensionsAdapter
|
|
||||||
|
|
||||||
allextenstionsRecyclerView = findViewById(R.id.allExtensionsRecyclerView)
|
val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
|
||||||
allextenstionsRecyclerView.layoutManager = LinearLayoutManager(this)
|
val viewPager = findViewById<ViewPager2>(R.id.viewPager)
|
||||||
allextenstionsRecyclerView.adapter = allExtensionsAdapter
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
viewPager.adapter = object : FragmentStateAdapter(this) {
|
||||||
animeExtensionManager.installedExtensionsFlow.collect { extensions ->
|
override fun getItemCount(): Int = 2
|
||||||
extensionsAdapter.updateData(extensions)
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
return when (position) {
|
||||||
|
0 -> AnimeExtensionsFragment()
|
||||||
|
1 -> MangaExtensionsFragment()
|
||||||
|
else -> AnimeExtensionsFragment()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
|
||||||
combine(
|
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||||
animeExtensionManager.availableExtensionsFlow,
|
tab.text = when (position) {
|
||||||
animeExtensionManager.installedExtensionsFlow
|
0 -> "Anime" // Your tab title
|
||||||
) { availableExtensions, installedExtensions ->
|
1 -> "Manga" // Your tab title
|
||||||
// Pair of available and installed extensions
|
else -> null
|
||||||
Pair(availableExtensions, installedExtensions)
|
|
||||||
}.collect { pair ->
|
|
||||||
val (availableExtensions, installedExtensions) = pair
|
|
||||||
allExtensionsAdapter.updateData(availableExtensions, installedExtensions)
|
|
||||||
}
|
}
|
||||||
}
|
}.attach()
|
||||||
|
|
||||||
|
|
||||||
val searchView: SearchView = findViewById(R.id.searchView)
|
val searchView: SearchView = findViewById(R.id.searchView)
|
||||||
val extensionsRecyclerView: RecyclerView = findViewById(R.id.extensionsRecyclerView)
|
|
||||||
val extensionsHeader: LinearLayout = findViewById(R.id.extensionsHeader)
|
val extensionsHeader: LinearLayout = findViewById(R.id.extensionsHeader)
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
@@ -133,17 +99,11 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextChange(newText: String?): Boolean {
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
if (newText.isNullOrEmpty()) {
|
val currentFragment = supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}")
|
||||||
allExtensionsAdapter.filter("") // Reset the filter
|
if (currentFragment is SearchQueryHandler) {
|
||||||
allextenstionsRecyclerView.visibility = View.VISIBLE
|
currentFragment.updateContentBasedOnQuery(newText)
|
||||||
extensionsHeader.visibility = View.VISIBLE
|
|
||||||
extensionsRecyclerView.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
allExtensionsAdapter.filter(newText)
|
|
||||||
allextenstionsRecyclerView.visibility = View.VISIBLE
|
|
||||||
extensionsRecyclerView.visibility = View.GONE
|
|
||||||
extensionsHeader.visibility = View.GONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -164,104 +124,11 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ExtensionsAdapter(private val onUninstallClicked: (String) -> Unit) : RecyclerView.Adapter<ExtensionsAdapter.ViewHolder>() {
|
}
|
||||||
|
|
||||||
private var extensions: List<AnimeExtension.Installed> = emptyList()
|
interface SearchQueryHandler {
|
||||||
|
fun updateContentBasedOnQuery(query: String?)
|
||||||
fun updateData(newExtensions: List<AnimeExtension.Installed>) {
|
}
|
||||||
extensions = newExtensions
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_extension, parent, false)
|
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val extension = extensions[position]
|
|
||||||
holder.extensionNameTextView.text = extension.name
|
|
||||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
|
||||||
holder.closeTextView.text = "Uninstall"
|
|
||||||
holder.closeTextView.setOnClickListener {
|
|
||||||
onUninstallClicked(extension.pkgName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = extensions.size
|
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
|
||||||
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AllExtensionsAdapter(private val coroutineScope: CoroutineScope,
|
|
||||||
private val onButtonClicked: (AnimeExtension.Available) -> Unit) : RecyclerView.Adapter<AllExtensionsAdapter.ViewHolder>() {
|
|
||||||
private var extensions: List<AnimeExtension.Available> = emptyList()
|
|
||||||
|
|
||||||
fun updateData(newExtensions: List<AnimeExtension.Available>, installedExtensions: List<AnimeExtension.Installed> = emptyList()) {
|
|
||||||
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
|
|
||||||
extensions = newExtensions.filter { it.pkgName !in installedPkgNames }
|
|
||||||
filteredExtensions = extensions
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AllExtensionsAdapter.ViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.item_extension_all, parent, false)
|
|
||||||
return ViewHolder(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
||||||
val extension = filteredExtensions[position]
|
|
||||||
holder.extensionNameTextView.text = extension.name
|
|
||||||
coroutineScope.launch {
|
|
||||||
val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl)
|
|
||||||
holder.extensionIconImageView.setImageDrawable(drawable)
|
|
||||||
}
|
|
||||||
holder.closeTextView.text = "Install"
|
|
||||||
holder.closeTextView.setOnClickListener {
|
|
||||||
onButtonClicked(extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = filteredExtensions.size
|
|
||||||
|
|
||||||
private var filteredExtensions: List<AnimeExtension.Available> = emptyList()
|
|
||||||
|
|
||||||
fun filter(query: String) {
|
|
||||||
filteredExtensions = if (query.isEmpty()) {
|
|
||||||
extensions
|
|
||||||
} else {
|
|
||||||
extensions.filter { it.name.contains(query, ignoreCase = true) }
|
|
||||||
}
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
|
||||||
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun urlToDrawable(context: Context, url: String): Drawable? {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
return@withContext Glide.with(context)
|
|
||||||
.load(url)
|
|
||||||
.submit()
|
|
||||||
.get()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return@withContext null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,33 +7,105 @@ import ani.dantotsu.R
|
|||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.ActivityFaqBinding
|
import ani.dantotsu.databinding.ActivityFaqBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class FAQActivity : AppCompatActivity() {
|
class FAQActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityFaqBinding
|
private lateinit var binding: ActivityFaqBinding
|
||||||
|
|
||||||
private val faqs = listOf(
|
private val faqs by lazy {
|
||||||
|
listOf(
|
||||||
|
|
||||||
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_1), currContext()!!.getString(R.string.answer_1)),
|
Triple(
|
||||||
Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_2), currContext()!!.getString(R.string.answer_2)),
|
R.drawable.ic_round_help_24,
|
||||||
Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_17), currContext()!!.getString(R.string.answer_17)),
|
currContext()!!.getString(R.string.question_1),
|
||||||
Triple(R.drawable.ic_round_download_24, currContext()!!.getString(R.string.question_3), currContext()!!.getString(R.string.answer_3)),
|
currContext()!!.getString(R.string.answer_1)
|
||||||
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_16), currContext()!!.getString(R.string.answer_16)),
|
),
|
||||||
Triple(R.drawable.ic_round_dns_24, currContext()!!.getString(R.string.question_4), currContext()!!.getString(R.string.answer_4)),
|
Triple(
|
||||||
Triple(R.drawable.ic_baseline_screen_lock_portrait_24, currContext()!!.getString(R.string.question_5), currContext()!!.getString(R.string.answer_5)),
|
R.drawable.ic_round_auto_awesome_24,
|
||||||
Triple(R.drawable.ic_anilist, currContext()!!.getString(R.string.question_6), currContext()!!.getString(R.string.answer_6)),
|
currContext()!!.getString(R.string.question_2),
|
||||||
Triple(R.drawable.ic_round_movie_filter_24, currContext()!!.getString(R.string.question_7), currContext()!!.getString(R.string.answer_7)),
|
currContext()!!.getString(R.string.answer_2)
|
||||||
Triple(R.drawable.ic_round_menu_book_24, currContext()!!.getString(R.string.question_8), currContext()!!.getString(R.string.answer_8)),
|
),
|
||||||
Triple(R.drawable.ic_round_lock_open_24, currContext()!!.getString(R.string.question_9), currContext()!!.getString(R.string.answer_9)),
|
Triple(
|
||||||
Triple(R.drawable.ic_round_smart_button_24, currContext()!!.getString(R.string.question_10), currContext()!!.getString(R.string.answer_10)),
|
R.drawable.ic_round_auto_awesome_24,
|
||||||
Triple(R.drawable.ic_round_smart_button_24, currContext()!!.getString(R.string.question_11), currContext()!!.getString(R.string.answer_11)),
|
currContext()!!.getString(R.string.question_17),
|
||||||
Triple(R.drawable.ic_round_info_24, currContext()!!.getString(R.string.question_12), currContext()!!.getString(R.string.answer_12)),
|
currContext()!!.getString(R.string.answer_17)
|
||||||
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_13), currContext()!!.getString(R.string.answer_13)),
|
),
|
||||||
Triple(R.drawable.ic_round_art_track_24, currContext()!!.getString(R.string.question_14), currContext()!!.getString(R.string.answer_14)),
|
Triple(
|
||||||
Triple(R.drawable.ic_round_video_settings_24, currContext()!!.getString(R.string.question_15), currContext()!!.getString(R.string.answer_15))
|
R.drawable.ic_round_download_24,
|
||||||
|
currContext()!!.getString(R.string.question_3),
|
||||||
|
currContext()!!.getString(R.string.answer_3)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_help_24,
|
||||||
|
currContext()!!.getString(R.string.question_16),
|
||||||
|
currContext()!!.getString(R.string.answer_16)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_dns_24,
|
||||||
|
currContext()!!.getString(R.string.question_4),
|
||||||
|
currContext()!!.getString(R.string.answer_4)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_baseline_screen_lock_portrait_24,
|
||||||
|
currContext()!!.getString(R.string.question_5),
|
||||||
|
currContext()!!.getString(R.string.answer_5)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_anilist,
|
||||||
|
currContext()!!.getString(R.string.question_6),
|
||||||
|
currContext()!!.getString(R.string.answer_6)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_movie_filter_24,
|
||||||
|
currContext()!!.getString(R.string.question_7),
|
||||||
|
currContext()!!.getString(R.string.answer_7)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_menu_book_24,
|
||||||
|
currContext()!!.getString(R.string.question_8),
|
||||||
|
currContext()!!.getString(R.string.answer_8)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_lock_open_24,
|
||||||
|
currContext()!!.getString(R.string.question_9),
|
||||||
|
currContext()!!.getString(R.string.answer_9)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_smart_button_24,
|
||||||
|
currContext()!!.getString(R.string.question_10),
|
||||||
|
currContext()!!.getString(R.string.answer_10)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_smart_button_24,
|
||||||
|
currContext()!!.getString(R.string.question_11),
|
||||||
|
currContext()!!.getString(R.string.answer_11)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_info_24,
|
||||||
|
currContext()!!.getString(R.string.question_12),
|
||||||
|
currContext()!!.getString(R.string.answer_12)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_help_24,
|
||||||
|
currContext()!!.getString(R.string.question_13),
|
||||||
|
currContext()!!.getString(R.string.answer_13)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_art_track_24,
|
||||||
|
currContext()!!.getString(R.string.question_14),
|
||||||
|
currContext()!!.getString(R.string.answer_14)
|
||||||
|
),
|
||||||
|
Triple(
|
||||||
|
R.drawable.ic_round_video_settings_24,
|
||||||
|
currContext()!!.getString(R.string.question_15),
|
||||||
|
currContext()!!.getString(R.string.answer_15)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityFaqBinding.inflate(layoutInflater)
|
binding = ActivityFaqBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,363 @@
|
|||||||
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.databinding.FragmentMangaBinding
|
||||||
|
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
||||||
|
import ani.dantotsu.loadData
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class MangaExtensionsFragment : Fragment(),
|
||||||
|
SearchQueryHandler {
|
||||||
|
private var _binding: FragmentMangaExtensionsBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
val skipIcons = loadData("skip_extension_icons") ?: false
|
||||||
|
|
||||||
|
private lateinit var extensionsRecyclerView: RecyclerView
|
||||||
|
private lateinit var allextenstionsRecyclerView: RecyclerView
|
||||||
|
private val mangaExtensionManager: MangaExtensionManager = Injekt.get<MangaExtensionManager>()
|
||||||
|
private val extensionsAdapter = MangaExtensionsAdapter({ pkg ->
|
||||||
|
if (isAdded) { // Check if the fragment is currently added to its activity
|
||||||
|
val context = requireContext() // Store context in a variable
|
||||||
|
val notificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
||||||
|
|
||||||
|
if (pkg.hasUpdate) {
|
||||||
|
mangaExtensionManager.updateExtension(pkg)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
||||||
|
.subscribe(
|
||||||
|
{ installStep ->
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_sync_24)
|
||||||
|
.setContentTitle("Updating extension")
|
||||||
|
.setContentText("Step: $installStep")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(error)
|
||||||
|
Log.e("MangaExtensionsAdapter", "Error: ", error) // Log the error
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_info_24)
|
||||||
|
.setContentTitle("Update failed")
|
||||||
|
.setContentText("Error: ${error.message}")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
||||||
|
.setContentTitle("Update complete")
|
||||||
|
.setContentText("The extension has been successfully updated.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mangaExtensionManager.uninstallExtension(pkg.pkgName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, skipIcons)
|
||||||
|
|
||||||
|
private val allExtensionsAdapter =
|
||||||
|
AllMangaExtensionsAdapter(lifecycleScope, { pkgName ->
|
||||||
|
if (isAdded) { // Check if the fragment is currently added to its activity
|
||||||
|
val context = requireContext()
|
||||||
|
val notificationManager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
// Start the installation process
|
||||||
|
mangaExtensionManager.installExtension(pkgName)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ installStep ->
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_sync_24)
|
||||||
|
.setContentTitle("Installing extension")
|
||||||
|
.setContentText("Step: $installStep")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{ error ->
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(error)
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_ERROR
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_round_info_24)
|
||||||
|
.setContentTitle("Installation failed: ${error.message}")
|
||||||
|
.setContentText("Error: ${error.message}")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val builder = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
)
|
||||||
|
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
|
||||||
|
.setContentTitle("Installation complete")
|
||||||
|
.setContentText("The extension has been successfully installed.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
notificationManager.notify(1, builder.build())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, skipIcons)
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
extensionsRecyclerView = binding.mangaExtensionsRecyclerView
|
||||||
|
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
extensionsRecyclerView.adapter = extensionsAdapter
|
||||||
|
|
||||||
|
allextenstionsRecyclerView = binding.allMangaExtensionsRecyclerView
|
||||||
|
allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
allextenstionsRecyclerView.adapter = allExtensionsAdapter
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
mangaExtensionManager.installedExtensionsFlow.collect { extensions ->
|
||||||
|
extensionsAdapter.updateData(extensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
combine(
|
||||||
|
mangaExtensionManager.availableExtensionsFlow,
|
||||||
|
mangaExtensionManager.installedExtensionsFlow
|
||||||
|
) { availableExtensions, installedExtensions ->
|
||||||
|
// Pair of available and installed extensions
|
||||||
|
Pair(availableExtensions, installedExtensions)
|
||||||
|
}.collect { pair ->
|
||||||
|
val (availableExtensions, installedExtensions) = pair
|
||||||
|
allExtensionsAdapter.updateData(availableExtensions, installedExtensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val extensionsRecyclerView: RecyclerView = binding.mangaExtensionsRecyclerView
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateContentBasedOnQuery(query: String?) {
|
||||||
|
if (query.isNullOrEmpty()) {
|
||||||
|
allExtensionsAdapter.filter("") // Reset the filter
|
||||||
|
allextenstionsRecyclerView.visibility = View.VISIBLE
|
||||||
|
extensionsRecyclerView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
allExtensionsAdapter.filter(query)
|
||||||
|
allextenstionsRecyclerView.visibility = View.VISIBLE
|
||||||
|
extensionsRecyclerView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView();_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MangaExtensionsAdapter(
|
||||||
|
private val onUninstallClicked: (MangaExtension.Installed) -> Unit,
|
||||||
|
skipIcons: Boolean
|
||||||
|
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
|
||||||
|
DIFF_CALLBACK_INSTALLED
|
||||||
|
) {
|
||||||
|
|
||||||
|
val skipIcons = skipIcons
|
||||||
|
|
||||||
|
// Use submitList to update data
|
||||||
|
fun updateData(newExtensions: List<MangaExtension.Installed>) {
|
||||||
|
submitList(newExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_extension, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val extension = getItem(position) // Use getItem from ListAdapter
|
||||||
|
|
||||||
|
holder.extensionNameTextView.text = extension.name
|
||||||
|
if (!skipIcons) {
|
||||||
|
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension.hasUpdate) {
|
||||||
|
holder.closeTextView.text = "Update"
|
||||||
|
holder.closeTextView.setTextColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
holder.itemView.context,
|
||||||
|
R.color.warning
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
holder.closeTextView.text = "Uninstall"
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.closeTextView.setOnClickListener {
|
||||||
|
onUninstallClicked(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||||
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
|
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DIFF_CALLBACK_INSTALLED =
|
||||||
|
object : DiffUtil.ItemCallback<MangaExtension.Installed>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: MangaExtension.Installed,
|
||||||
|
newItem: MangaExtension.Installed
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.pkgName == newItem.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: MangaExtension.Installed,
|
||||||
|
newItem: MangaExtension.Installed
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class AllMangaExtensionsAdapter(
|
||||||
|
private val coroutineScope: CoroutineScope,
|
||||||
|
private val onButtonClicked: (MangaExtension.Available) -> Unit,
|
||||||
|
skipIcons: Boolean
|
||||||
|
) : ListAdapter<MangaExtension.Available, AllMangaExtensionsAdapter.ViewHolder>(
|
||||||
|
DIFF_CALLBACK_AVAILABLE
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
setHasStableIds(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val skipIcons = skipIcons
|
||||||
|
|
||||||
|
// Use submitList to update the data
|
||||||
|
fun updateData(
|
||||||
|
newExtensions: List<MangaExtension.Available>,
|
||||||
|
installedExtensions: List<MangaExtension.Installed> = emptyList()
|
||||||
|
) {
|
||||||
|
coroutineScope.launch(Dispatchers.Default) {
|
||||||
|
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
|
||||||
|
val filteredExtensions = newExtensions.filter { it.pkgName !in installedPkgNames }
|
||||||
|
|
||||||
|
// Switch back to main thread to update UI
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
submitList(filteredExtensions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.item_extension_all, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val extension = getItem(position) // Use getItem from ListAdapter
|
||||||
|
|
||||||
|
holder.extensionNameTextView.text = extension.name
|
||||||
|
if (!skipIcons) {
|
||||||
|
Glide.with(holder.itemView.context)
|
||||||
|
.load(extension.iconUrl)
|
||||||
|
.into(holder.extensionIconImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.closeTextView.text = "Install"
|
||||||
|
holder.closeTextView.setOnClickListener {
|
||||||
|
onButtonClicked(extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtering function
|
||||||
|
fun filter(query: String) {
|
||||||
|
val filteredExtensions = if (query.isEmpty()) {
|
||||||
|
currentList
|
||||||
|
} else {
|
||||||
|
currentList.filter { it.name.contains(query, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
submitList(filteredExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||||
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
|
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DIFF_CALLBACK_AVAILABLE =
|
||||||
|
object : DiffUtil.ItemCallback<MangaExtension.Available>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: MangaExtension.Available,
|
||||||
|
newItem: MangaExtension.Available
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.pkgName == newItem.pkgName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: MangaExtension.Available,
|
||||||
|
newItem: MangaExtension.Available
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import ani.dantotsu.databinding.ActivityPlayerSettingsBinding
|
|||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.parsers.Subtitle
|
import ani.dantotsu.parsers.Subtitle
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityPlayerSettingsBinding.inflate(layoutInflater)
|
binding = ActivityPlayerSettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ import ani.dantotsu.navBarHeight
|
|||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.saveData
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class ReaderSettingsActivity : AppCompatActivity() {
|
class ReaderSettingsActivity : AppCompatActivity() {
|
||||||
lateinit var binding: ActivityReaderSettingsBinding
|
lateinit var binding: ActivityReaderSettingsBinding
|
||||||
private val reader = "reader_settings"
|
private val reader = "reader_settings"
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityReaderSettingsBinding.inflate(layoutInflater)
|
binding = ActivityReaderSettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ani.dantotsu.settings
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.os.Build.*
|
import android.os.Build.*
|
||||||
@@ -11,6 +12,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@@ -30,11 +32,16 @@ import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
|||||||
import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime
|
import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
|
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes
|
import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import eu.kanade.domain.base.BasePreferences
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
@@ -43,10 +50,13 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
override fun handleOnBackPressed() = startMainActivity(this@SettingsActivity)
|
override fun handleOnBackPressed() = startMainActivity(this@SettingsActivity)
|
||||||
}
|
}
|
||||||
lateinit var binding: ActivitySettingsBinding
|
lateinit var binding: ActivitySettingsBinding
|
||||||
|
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
||||||
|
private val networkPreferences = Injekt.get<NetworkPreferences>()
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -88,14 +98,40 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
val animeSource = loadData<Int>("settings_def_anime_source")?.let { if (it >= AnimeSources.names.size) 0 else it } ?: 0
|
binding.settingsUseMaterialYou.isChecked = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_material_you", false)
|
||||||
if (MangaSources.names.isNotEmpty() && animeSource in 0 until MangaSources.names.size) {
|
binding.settingsUseMaterialYou.setOnCheckedChangeListener { _, isChecked ->
|
||||||
binding.mangaSource.setText(MangaSources.names[animeSource], false)
|
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_material_you", isChecked).apply()
|
||||||
|
Toast.makeText(this, "Restart app to apply changes", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
val themeString = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getString("theme", "PURPLE")!!
|
||||||
|
binding.themeSwitcher.setText(themeString.substring(0, 1) + themeString.substring(1).lowercase())
|
||||||
|
|
||||||
|
binding.themeSwitcher.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, ThemeManager.Companion.Theme.values().map { it.theme.substring(0, 1) + it.theme.substring(1).lowercase() }))
|
||||||
|
|
||||||
|
binding.themeSwitcher.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putString("theme", ThemeManager.Companion.Theme.values()[i].theme).apply()
|
||||||
|
ActivityHelper.shouldRefreshMainActivity = true
|
||||||
|
binding.themeSwitcher.clearFocus()
|
||||||
|
Refresh.all()
|
||||||
|
finish()
|
||||||
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
|
initActivity(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//val animeSource = loadData<Int>("settings_def_anime_source_s")?.let { if (it >= AnimeSources.names.size) 0 else it } ?: 0
|
||||||
|
val animeSource = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("settings_def_anime_source_s_r", 0)
|
||||||
|
if (AnimeSources.names.isNotEmpty() && animeSource in 0 until AnimeSources.names.size) {
|
||||||
|
binding.animeSource.setText(AnimeSources.names[animeSource], false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, AnimeSources.names))
|
binding.animeSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, AnimeSources.names))
|
||||||
|
|
||||||
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
||||||
saveData("settings_def_anime_source", i)
|
//saveData("settings_def_anime_source_s", i)
|
||||||
|
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putInt("settings_def_anime_source_s_r", i).apply()
|
||||||
binding.animeSource.clearFocus()
|
binding.animeSource.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +150,35 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.settingsForceLegacyInstall.isChecked = extensionInstaller.get() == BasePreferences.ExtensionInstaller.LEGACY
|
||||||
|
binding.settingsForceLegacyInstall.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
if (isChecked) {
|
||||||
|
extensionInstaller.set(BasePreferences.ExtensionInstaller.LEGACY)
|
||||||
|
}else{
|
||||||
|
extensionInstaller.set(BasePreferences.ExtensionInstaller.PACKAGEINSTALLER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.skipExtensionIcons.isChecked = loadData("skip_extension_icons") ?: false
|
||||||
|
binding.skipExtensionIcons.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
saveData("skip_extension_icons", isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.userAgent.setText(networkPreferences.defaultUserAgent().get())
|
||||||
|
binding.userAgent.setOnEditorActionListener { _, _, _ ->
|
||||||
|
networkPreferences.defaultUserAgent().set(binding.userAgent.text.toString())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val exDns = listOf("None", "Cloudflare", "Google", "AdGuard", "Quad9", "AliDNS", "DNSPod", "360", "Quad101", "Mullvad", "Controld", "Njalla", "Shecan")
|
||||||
|
binding.settingsExtensionDns.setText(exDns[networkPreferences.dohProvider().get()], false)
|
||||||
|
binding.settingsExtensionDns.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, exDns))
|
||||||
|
binding.settingsExtensionDns.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
networkPreferences.dohProvider().set(i)
|
||||||
|
binding.settingsExtensionDns.clearFocus()
|
||||||
|
Toast.makeText(this, "Restart app to apply changes", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
binding.settingsDownloadInSd.isChecked = loadData("sd_dl") ?: false
|
binding.settingsDownloadInSd.isChecked = loadData("sd_dl") ?: false
|
||||||
binding.settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked ->
|
binding.settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
@@ -152,14 +217,19 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
saveData("settings_prefer_dub", isChecked)
|
saveData("settings_prefer_dub", isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
val mangaSource = loadData<Int>("settings_def_manga_source")?.let { if (it >= MangaSources.names.size) 0 else it } ?: 0
|
//val mangaSource = loadData<Int>("settings_def_manga_source_s")?.let { if (it >= MangaSources.names.size) 0 else it } ?: 0
|
||||||
if (MangaSources.names.isNotEmpty() && mangaSource in 0 until MangaSources.names.size) {
|
val mangaSource = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("settings_def_manga_source_s_r", 0)
|
||||||
binding.mangaSource.setText(MangaSources.names[mangaSource], false)
|
if (MangaSources.names.isNotEmpty() && mangaSource in 0 until MangaSources.names.size) {
|
||||||
|
binding.mangaSource.setText(MangaSources.names[mangaSource], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up the dropdown adapter.
|
||||||
binding.mangaSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, MangaSources.names))
|
binding.mangaSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, MangaSources.names))
|
||||||
|
|
||||||
|
// Set up the item click listener for the dropdown.
|
||||||
binding.mangaSource.setOnItemClickListener { _, _, i, _ ->
|
binding.mangaSource.setOnItemClickListener { _, _, i, _ ->
|
||||||
saveData("settings_def_manga_source", i)
|
//saveData("settings_def_manga_source_s", i)
|
||||||
|
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putInt("settings_def_manga_source_s_r", i).apply()
|
||||||
binding.mangaSource.clearFocus()
|
binding.mangaSource.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +410,7 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var curTime = loadData<Int>("subscriptions_time") ?: defaultTime
|
var curTime = loadData<Int>("subscriptions_time_r") ?: defaultTime
|
||||||
val timeNames = timeMinutes.map {
|
val timeNames = timeMinutes.map {
|
||||||
val mins = it % 60
|
val mins = it % 60
|
||||||
val hours = it / 60
|
val hours = it / 60
|
||||||
@@ -353,7 +423,7 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
speedDialog.setSingleChoiceItems(timeNames, curTime) { dialog, i ->
|
speedDialog.setSingleChoiceItems(timeNames, curTime) { dialog, i ->
|
||||||
curTime = i
|
curTime = i
|
||||||
binding.settingsSubscriptionsTime.text = getString(R.string.subscriptions_checking_time_s, timeNames[i])
|
binding.settingsSubscriptionsTime.text = getString(R.string.subscriptions_checking_time_s, timeNames[i])
|
||||||
saveData("subscriptions_time", curTime)
|
saveData("subscriptions_time_r", curTime)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
startSubscription(true)
|
startSubscription(true)
|
||||||
}.show()
|
}.show()
|
||||||
@@ -497,7 +567,7 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
title = "Enjoying the App?"
|
title = "Enjoying the App?"
|
||||||
addView(TextView(this@SettingsActivity).apply {
|
addView(TextView(this@SettingsActivity).apply {
|
||||||
text =
|
text =
|
||||||
"Consider donating!\nOnce we reach the goal of $1000 (60%+ already reached!), Get ready to get an Offline Player & Manga Downloads!"
|
"Consider donating!"
|
||||||
})
|
})
|
||||||
|
|
||||||
setNegativeButton("no moners :(") {
|
setNegativeButton("no moners :(") {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.ActivityUserInterfaceSettingsBinding
|
import ani.dantotsu.databinding.ActivityUserInterfaceSettingsBinding
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
class UserInterfaceSettingsActivity : AppCompatActivity() {
|
class UserInterfaceSettingsActivity : AppCompatActivity() {
|
||||||
@@ -15,6 +16,7 @@ class UserInterfaceSettingsActivity : AppCompatActivity() {
|
|||||||
private val ui = "ui_settings"
|
private val ui = "ui_settings"
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityUserInterfaceSettingsBinding.inflate(layoutInflater)
|
binding = ActivityUserInterfaceSettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class AlarmReceiver : BroadcastReceiver() {
|
|||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
val curTime = loadData<Int>("subscriptions_time", context) ?: defaultTime
|
val curTime = loadData<Int>("subscriptions_time_r", context) ?: defaultTime
|
||||||
|
|
||||||
if (timeMinutes[curTime] > 0)
|
if (timeMinutes[curTime] > 0)
|
||||||
alarmManager.setRepeating(
|
alarmManager.setRepeating(
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import kotlinx.coroutines.launch
|
|||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
class Subscription {
|
class Subscription {
|
||||||
companion object {
|
companion object {
|
||||||
const val defaultTime = 8
|
const val defaultTime = 3
|
||||||
val timeMinutes = arrayOf(0L, 5, 10, 15, 30, 45, 60, 90, 120, 180, 240, 360, 480, 720, 1440)
|
val timeMinutes = arrayOf(0L, 120, 180, 240, 360, 480, 720, 1440)
|
||||||
|
|
||||||
private var alreadyStarted = false
|
private var alreadyStarted = false
|
||||||
fun Context.startSubscription(force: Boolean = false) {
|
fun Context.startSubscription(force: Boolean = false) {
|
||||||
|
|||||||
@@ -9,19 +9,22 @@ import ani.dantotsu.parsers.*
|
|||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.saveData
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
|
||||||
class SubscriptionHelper {
|
class SubscriptionHelper {
|
||||||
companion object {
|
companion object {
|
||||||
private fun loadSelected(context: Context, mediaId: Int, isAdult: Boolean, isAnime: Boolean): Selected {
|
private fun loadSelected(context: Context, mediaId: Int, isAdult: Boolean, isAnime: Boolean): Selected {
|
||||||
return loadData<Selected>("${mediaId}-select", context) ?: Selected().let {
|
val sharedPreferences = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
it.source =
|
val data = loadData<Selected>("${mediaId}-select", context) ?: Selected().let {
|
||||||
|
it.sourceIndex =
|
||||||
if (isAdult) 0
|
if (isAdult) 0
|
||||||
else if (isAnime) loadData("settings_def_anime_source", context) ?: 0
|
else if (isAnime) {sharedPreferences.getInt("settings_def_anime_source_s_r",0)}
|
||||||
else loadData("settings_def_manga_source", context) ?: 0
|
else {sharedPreferences.getInt("settings_def_manga_source_s_r",0)}
|
||||||
it.preferDub = loadData("settings_prefer_dub", context) ?: false
|
it.preferDub = loadData("settings_prefer_dub", context) ?: false
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveSelected(context: Context, mediaId: Int, data: Selected) {
|
private fun saveSelected(context: Context, mediaId: Int, data: Selected) {
|
||||||
@@ -31,7 +34,7 @@ class SubscriptionHelper {
|
|||||||
fun getAnimeParser(context: Context, isAdult: Boolean, id: Int): AnimeParser {
|
fun getAnimeParser(context: Context, isAdult: Boolean, id: Int): AnimeParser {
|
||||||
val sources = if (isAdult) HAnimeSources else AnimeSources
|
val sources = if (isAdult) HAnimeSources else AnimeSources
|
||||||
val selected = loadSelected(context, id, isAdult, true)
|
val selected = loadSelected(context, id, isAdult, true)
|
||||||
val parser = sources[selected.source]
|
val parser = sources[selected.sourceIndex]
|
||||||
parser.selectDub = selected.preferDub
|
parser.selectDub = selected.preferDub
|
||||||
return parser
|
return parser
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,7 @@ class SubscriptionHelper {
|
|||||||
fun getMangaParser(context: Context, isAdult: Boolean, id: Int): MangaParser {
|
fun getMangaParser(context: Context, isAdult: Boolean, id: Int): MangaParser {
|
||||||
val sources = if (isAdult) HMangaSources else MangaSources
|
val sources = if (isAdult) HMangaSources else MangaSources
|
||||||
val selected = loadSelected(context, id, isAdult, false)
|
val selected = loadSelected(context, id, isAdult, false)
|
||||||
return sources[selected.source]
|
return sources[selected.sourceIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getChapter(context: Context, parser: MangaParser, id: Int, isAdult: Boolean): MangaChapter? {
|
suspend fun getChapter(context: Context, parser: MangaParser, id: Int, isAdult: Boolean): MangaChapter? {
|
||||||
@@ -66,12 +69,15 @@ class SubscriptionHelper {
|
|||||||
val chp = withTimeoutOrNull(10 * 1000) {
|
val chp = withTimeoutOrNull(10 * 1000) {
|
||||||
tryWithSuspend {
|
tryWithSuspend {
|
||||||
val show = parser.loadSavedShowResponse(id) ?: throw Exception(currContext()?.getString(R.string.failed_to_load_data, id))
|
val show = parser.loadSavedShowResponse(id) ?: throw Exception(currContext()?.getString(R.string.failed_to_load_data, id))
|
||||||
parser.getLatestChapter(show.link, show.extra, selected.latest)
|
show.sManga?.let {
|
||||||
|
parser.getLatestChapter(show.link, show.extra,
|
||||||
|
it, selected.latest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return chp?.apply {
|
return chp?.apply {
|
||||||
selected.latest = number.toFloat()
|
selected.latest = MangaNameAdapter.findChapterNumber(number) ?: 0f
|
||||||
saveSelected(context, id, selected)
|
saveSelected(context, id, selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class SubscriptionWorker(val context: Context, params: WorkerParameters) : Corou
|
|||||||
|
|
||||||
private const val SUBSCRIPTION_WORK_NAME = "work_subscription"
|
private const val SUBSCRIPTION_WORK_NAME = "work_subscription"
|
||||||
fun enqueue(context: Context) {
|
fun enqueue(context: Context) {
|
||||||
val curTime = loadData<Int>("subscriptions_time") ?: defaultTime
|
val curTime = loadData<Int>("subscriptions_time_r") ?: defaultTime
|
||||||
if(timeMinutes[curTime]>0L) {
|
if(timeMinutes[curTime]>0L) {
|
||||||
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
|
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
|
||||||
val periodicSyncDataWork = PeriodicWorkRequest.Builder(
|
val periodicSyncDataWork = PeriodicWorkRequest.Builder(
|
||||||
|
|||||||
48
app/src/main/java/ani/dantotsu/themes/ThemeManager.kt
Normal file
48
app/src/main/java/ani/dantotsu/themes/ThemeManager.kt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package ani.dantotsu.themes
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import ani.dantotsu.R
|
||||||
|
|
||||||
|
class ThemeManager(private val context: Context) {
|
||||||
|
fun applyTheme() {
|
||||||
|
if(context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_material_you", false)){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
when (context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getString("theme", "PURPLE")!!) {
|
||||||
|
"PURPLE" -> {
|
||||||
|
context.setTheme(R.style.Theme_Dantotsu_Purple)
|
||||||
|
}
|
||||||
|
//"MONOCHROME" -> {
|
||||||
|
// context.setTheme(R.style.Theme_Dantotsu_Monochrome)
|
||||||
|
//}
|
||||||
|
"BLUE" -> {
|
||||||
|
context.setTheme(R.style.Theme_Dantotsu_Blue)
|
||||||
|
}
|
||||||
|
"GREEN" -> {
|
||||||
|
context.setTheme(R.style.Theme_Dantotsu_Green)
|
||||||
|
}
|
||||||
|
"PINK" -> {
|
||||||
|
context.setTheme(R.style.Theme_Dantotsu_Pink)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
context.setTheme(R.style.Theme_Dantotsu_Purple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object{
|
||||||
|
enum class Theme(val theme: String) {
|
||||||
|
PURPLE("PURPLE"),
|
||||||
|
BLUE("BLUE"),
|
||||||
|
GREEN("GREEN"),
|
||||||
|
PINK("PINK");
|
||||||
|
//MONOCHROME("MONOCHROME");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(value: String): Theme {
|
||||||
|
return values().find { it.theme == value } ?: PURPLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package eu.kanade.core.preference
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
|
|
||||||
|
class PreferenceMutableState<T>(
|
||||||
|
private val preference: Preference<T>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
) : MutableState<T> {
|
||||||
|
|
||||||
|
private val state = mutableStateOf(preference.get())
|
||||||
|
|
||||||
|
init {
|
||||||
|
preference.changes()
|
||||||
|
.onEach { state.value = it }
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override var value: T
|
||||||
|
get() = state.value
|
||||||
|
set(value) {
|
||||||
|
preference.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component1(): T {
|
||||||
|
return state.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun component2(): (T) -> Unit {
|
||||||
|
return { preference.set(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Preference<T>.asState(scope: CoroutineScope) = PreferenceMutableState(this, scope)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package ani.dantotsu.aniyomi.domain.base
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import ani.dantotsu.aniyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class BasePreferences(
|
class BasePreferences(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package ani.dantotsu.aniyomi.domain.base
|
package eu.kanade.domain.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import ani.dantotsu.aniyomi.util.system.hasMiuiPackageInstaller
|
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||||
import ani.dantotsu.aniyomi.domain.base.BasePreferences.ExtensionInstaller
|
import eu.kanade.domain.base.BasePreferences.ExtensionInstaller
|
||||||
import ani.dantotsu.aniyomi.util.system.isShizukuInstalled
|
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import ani.dantotsu.aniyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
import ani.dantotsu.aniyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import ani.dantotsu.aniyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
|
|
||||||
class ExtensionInstallerPreference(
|
class ExtensionInstallerPreference(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ani.dantotsu.aniyomi.domain.source.service
|
package eu.kanade.domain.source.service
|
||||||
|
|
||||||
class SetMigrateSorting(
|
class SetMigrateSorting(
|
||||||
private val preferences: SourcePreferences,
|
private val preferences: SourcePreferences,
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package ani.dantotsu.aniyomi.domain.source.service
|
package eu.kanade.domain.source.service
|
||||||
|
|
||||||
import ani.dantotsu.aniyomi.core.preference.PreferenceStore
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import ani.dantotsu.aniyomi.util.system.LocaleHelper
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import ani.dantotsu.aniyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
import ani.dantotsu.aniyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
|
||||||
class SourcePreferences(
|
class SourcePreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package eu.kanade.domain.source.service
|
||||||
|
|
||||||
|
import tachiyomi.core.preference.getAndSet
|
||||||
|
|
||||||
|
class ToggleLanguage(
|
||||||
|
val preferences: SourcePreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun await(language: String) {
|
||||||
|
val isEnabled = language in preferences.enabledLanguages().get()
|
||||||
|
preferences.enabledLanguages().getAndSet { enabled ->
|
||||||
|
if (isEnabled) enabled.minus(language) else enabled.plus(language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
package ani.dantotsu.aniyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
typealias PreferenceScreen = androidx.preference.PreferenceScreen
|
typealias PreferenceScreen = androidx.preference.PreferenceScreen
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ani.dantotsu.aniyomi.animesource
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
package ani.dantotsu.aniyomi.animesource
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
//import ani.dantotsu.aniyomi.util.awaitSingle
|
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
||||||
import ani.dantotsu.aniyomi.util.lang.awaitSingle
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ani.dantotsu.aniyomi.animesource
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for creating sources at runtime.
|
* A factory for creating sources at runtime.
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animesource
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
import ani.dantotsu.aniyomi.animesource.AnimeSource
|
import eu.kanade.tachiyomi.PreferenceScreen
|
||||||
import ani.dantotsu.aniyomi.PreferenceScreen
|
|
||||||
|
|
||||||
interface ConfigurableAnimeSource : AnimeSource {
|
interface ConfigurableAnimeSource : AnimeSource {
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
import ani.dantotsu.aniyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
interface SAnime : Serializable {
|
interface SAnime : Serializable {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
import ani.dantotsu.aniyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
|
|
||||||
class SAnimeImpl : SAnime {
|
class SAnimeImpl : SAnime {
|
||||||
|
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import ani.dantotsu.aniyomi.util.network.ProgressListener
|
import eu.kanade.tachiyomi.network.ProgressListener
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import rx.subjects.Subject
|
import rx.subjects.Subject
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
import java.io.ObjectOutputStream
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
data class Track(val url: String, val lang: String)
|
data class Track(val url: String, val lang: String)
|
||||||
|
|
||||||
@@ -13,11 +17,14 @@ open class Video(
|
|||||||
val url: String = "",
|
val url: String = "",
|
||||||
val quality: String = "",
|
val quality: String = "",
|
||||||
var videoUrl: String? = null,
|
var videoUrl: String? = null,
|
||||||
val headers: Headers? = null,
|
headers: Headers? = null,
|
||||||
// "url", "language-label-2", "url2", "language-label-2"
|
// "url", "language-label-2", "url2", "language-label-2"
|
||||||
val subtitleTracks: List<Track> = emptyList(),
|
val subtitleTracks: List<Track> = emptyList(),
|
||||||
val audioTracks: List<Track> = emptyList(),
|
val audioTracks: List<Track> = emptyList(),
|
||||||
) : ProgressListener {
|
) : Serializable, ProgressListener {
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
var headers: Headers? = headers
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
constructor(
|
constructor(
|
||||||
@@ -89,4 +96,27 @@ open class Video(
|
|||||||
READY,
|
READY,
|
||||||
ERROR,
|
ERROR,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun writeObject(out: ObjectOutputStream) {
|
||||||
|
out.defaultWriteObject()
|
||||||
|
val headersMap: Map<String, List<String>> = headers?.toMultimap() ?: emptyMap()
|
||||||
|
out.writeObject(headersMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, ClassNotFoundException::class)
|
||||||
|
private fun readObject(input: ObjectInputStream) {
|
||||||
|
input.defaultReadObject()
|
||||||
|
val headersMap = input.readObject() as? Map<String, List<String>>
|
||||||
|
headers = headersMap?.let { map ->
|
||||||
|
val builder = Headers.Builder()
|
||||||
|
for ((key, values) in map) {
|
||||||
|
for (value in values) {
|
||||||
|
builder.add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.online
|
package eu.kanade.tachiyomi.animesource.online
|
||||||
|
|
||||||
import ani.dantotsu.aniyomi.animesource.AnimeCatalogueSource
|
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package ani.dantotsu.aniyomi.core
|
package eu.kanade.tachiyomi.core
|
||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
const val URL_HELP = "https://aniyomi.org/help/"
|
const val URL_HELP = "https://aniyomi.org/help/"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package ani.dantotsu.aniyomi.core.preference
|
package eu.kanade.tachiyomi.core.preference
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.SharedPreferences.Editor
|
import android.content.SharedPreferences.Editor
|
||||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.filter
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
|
|
||||||
sealed class AndroidPreference<T>(
|
sealed class AndroidPreference<T>(
|
||||||
private val preferences: SharedPreferences,
|
private val preferences: SharedPreferences,
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
package ani.dantotsu.aniyomi.core.preference
|
package eu.kanade.tachiyomi.core.preference
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import ani.dantotsu.aniyomi.core.preference.AndroidPreference.BooleanPrimitive
|
import eu.kanade.tachiyomi.core.preference.AndroidPreference.BooleanPrimitive
|
||||||
import ani.dantotsu.aniyomi.core.preference.AndroidPreference.FloatPrimitive
|
import eu.kanade.tachiyomi.core.preference.AndroidPreference.FloatPrimitive
|
||||||
import ani.dantotsu.aniyomi.core.preference.AndroidPreference.IntPrimitive
|
import eu.kanade.tachiyomi.core.preference.AndroidPreference.IntPrimitive
|
||||||
import ani.dantotsu.aniyomi.core.preference.AndroidPreference.LongPrimitive
|
import eu.kanade.tachiyomi.core.preference.AndroidPreference.LongPrimitive
|
||||||
import ani.dantotsu.aniyomi.core.preference.AndroidPreference.StringPrimitive
|
import eu.kanade.tachiyomi.core.preference.AndroidPreference.Object
|
||||||
import ani.dantotsu.aniyomi.core.preference.AndroidPreference.StringSetPrimitive
|
import eu.kanade.tachiyomi.core.preference.AndroidPreference.StringPrimitive
|
||||||
import ani.dantotsu.aniyomi.core.preference.AndroidPreference.Object
|
import eu.kanade.tachiyomi.core.preference.AndroidPreference.StringSetPrimitive
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class AndroidPreferenceStore(
|
class AndroidPreferenceStore(
|
||||||
context: Context,
|
context: Context,
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package ani.dantotsu.aniyomi.data
|
package eu.kanade.tachiyomi.data.notification
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import ani.dantotsu.MainActivity
|
import ani.dantotsu.MainActivity
|
||||||
import ani.dantotsu.aniyomi.core.Constants
|
import eu.kanade.tachiyomi.core.Constants
|
||||||
/**
|
/**
|
||||||
* Global [BroadcastReceiver] that runs on UI thread
|
* Global [BroadcastReceiver] that runs on UI thread
|
||||||
* Pending Broadcasts should be made from here.
|
* Pending Broadcasts should be made from here.
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package ani.dantotsu.aniyomi.data
|
package eu.kanade.tachiyomi.data.notification
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT
|
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT
|
||||||
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_HIGH
|
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_HIGH
|
||||||
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_LOW
|
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_LOW
|
||||||
import ani.dantotsu.aniyomi.util.system.buildNotificationChannel
|
import eu.kanade.tachiyomi.util.system.buildNotificationChannel
|
||||||
import ani.dantotsu.aniyomi.util.system.buildNotificationChannelGroup
|
import eu.kanade.tachiyomi.util.system.buildNotificationChannelGroup
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to manage the basic information of all the notifications used in the app.
|
* Class to manage the basic information of all the notifications used in the app.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user