Compare commits

...

19 Commits

Author SHA1 Message Date
Finnley Somdahl
9c0ef7a788 bugfixes and themes 2023-10-27 00:47:44 -05:00
Finnley Somdahl
960c2b4113 Update stable.md 2023-10-26 02:11:43 -05:00
Finnley Somdahl
1eb85d4419 new themes 2023-10-26 02:08:35 -05:00
Finnley Somdahl
20bea76e6c subtitle, image, and extension page fixes 2023-10-26 00:40:57 -05:00
Finnley Somdahl
866bd3b3a9 theme cleanup 2023-10-25 15:55:29 -05:00
Finnley Somdahl
3567b8dced hotfix 2023-10-25 01:22:40 -05:00
Finnley Somdahl
d109914537 Update stable.md 2023-10-25 00:36:37 -05:00
Finnley Somdahl
da4d55a9a8 final fixes before update 2023-10-25 00:35:09 -05:00
Finnley Somdahl
63526c6ed3 themes and various bugs 2023-10-24 23:38:46 -05:00
Finnley Somdahl
dc165fa6bc update preparation 2023-10-22 02:33:06 -05:00
Finnley Somdahl
dc959796e6 various bugfixes 2023-10-22 02:28:39 -05:00
Finnley Somdahl
0b9f2bb019 update preparation 2023-10-20 21:47:28 -05:00
Finnley Somdahl
6ddbd4760c Merge branch 'main' of https://github.com/rebelonion/Dantotsu 2023-10-20 21:39:01 -05:00
Finnley Somdahl
d1270c7c83 various fixes and updates 2023-10-20 21:38:40 -05:00
Finnley Somdahl
79618e1963 Update stable.md 2023-10-20 02:16:40 -05:00
Finnley Somdahl
da81646297 update stable 2023-10-20 02:14:04 -05:00
Finnley Somdahl
41b90e3a39 manga "working" :D 2023-10-20 01:44:36 -05:00
Finnley Somdahl
57a584a820 lots of background work for manga extensions 2023-10-18 23:52:03 -05:00
Finnley Somdahl
dbe573131e extension fix 2023-10-18 15:43:17 -05:00
278 changed files with 7487 additions and 1772 deletions

View File

@@ -1,6 +1,4 @@
# **Dantotsu** (🚧 ALPHA 🚧) # **Dantotsu**
> ⚠️ **WARNING**: This project is in alpha stage. Things may not work as expected.
<p align="center"> <p align="center">
<a href="https://discord.gg/4HPZ5nAWwM"><img src="https://img.shields.io/badge/Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white"></a> <a href="https://discord.gg/4HPZ5nAWwM"><img src="https://img.shields.io/badge/Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white"></a>
@@ -26,14 +24,14 @@ 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 |
## APP FEATURES ## APP FEATURES
- Easy and functional way to both, watch anime and read manga, ad-free. - Easy and functional way to both, watch anime and read manga.
- A completely open source app with a nice UI & Animations :) - A completely open source app with a nice UI & Animations :)
@@ -59,14 +57,16 @@ add your own extensions in the settings menu (Dantotsu has no affiliation with a
## Planned Stuff ## Planned Stuff
- get app out of alpha - TV Support
- Accent Color Change (RIP Hot Pink Supremacy.) - LN Support
- Offline Viewing
## Rejected Stuff (still rejected) ## Rejected Stuff (still rejected)
- Sources of any language except English - Official support of any language except English
- News Section in the App - News Section in the App

View File

@@ -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 "1.0.0"
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
@@ -96,6 +96,10 @@ dependencies {
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
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'
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
// 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'

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Dantotsu α</string> <string name="app_name">Dantotsu</string>
</resources> </resources>

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -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)

View File

@@ -188,6 +188,9 @@ fun Activity.hideStatusBar() {
open class BottomSheetDialogFragment : BottomSheetDialogFragment() { open class BottomSheetDialogFragment : BottomSheetDialogFragment() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val window = dialog?.window
val decorView: View = window?.decorView ?: return
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
if (this.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) { if (this.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) {
val behavior = BottomSheetBehavior.from(requireView().parent as View) val behavior = BottomSheetBehavior.from(requireView().parent as View)
behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.state = BottomSheetBehavior.STATE_EXPANDED

View File

@@ -1,22 +1,34 @@
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.Color
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle 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
import android.widget.TextView import android.widget.TextView
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.RequiresApi
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 +36,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,16 +49,23 @@ 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
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.Serializable import java.io.Serializable
@@ -57,19 +76,37 @@ class MainActivity : AppCompatActivity() {
private var load = false private var load = false
private var uiSettings = UserInterfaceSettings() private var uiSettings = UserInterfaceSettings()
private val animeExtensionManager: AnimeExtensionManager by injectLazy() private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
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)
myScope.launch {
animeExtensionManager.findAvailableExtensions()
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
val backgroundDrawable = bottomBar.background as GradientDrawable
val currentColor = backgroundDrawable.color?.defaultColor ?: 0
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xE8000000.toInt()
backgroundDrawable.setColor(semiTransparentColor)
bottomBar.background = backgroundDrawable
}
val animeScope = CoroutineScope(Dispatchers.Default)
animeScope.launch {
animeExtensionManager.findAvailableExtensions()
logger("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
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
@@ -88,24 +125,40 @@ class MainActivity : AppCompatActivity() {
binding.root.isMotionEventSplittingEnabled = false binding.root.isMotionEventSplittingEnabled = false
lifecycleScope.launch { lifecycleScope.launch {
val splash = SplashScreenBinding.inflate(layoutInflater) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
binding.root.addView(splash.root) val splash = SplashScreenBinding.inflate(layoutInflater)
(splash.splashImage.drawable as Animatable).start() binding.root.addView(splash.root)
(splash.splashImage.drawable as Animatable).start()
// Wait for 2 seconds (2000 milliseconds) delay(1200)
delay(2000)
// Now perform the animation ObjectAnimator.ofFloat(
ObjectAnimator.ofFloat( splash.root,
splash.root, View.TRANSLATION_Y,
View.TRANSLATION_Y, 0f,
0f, -splash.root.height.toFloat()
-splash.root.height.toFloat() ).apply {
).apply { interpolator = AnticipateInterpolator()
interpolator = AnticipateInterpolator() duration = 200L
duration = 200L doOnEnd { binding.root.removeView(splash.root) }
doOnEnd { binding.root.removeView(splash.root) } start()
start() }
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
splashScreen.setOnExitAnimationListener { splashScreenView ->
ObjectAnimator.ofFloat(
splashScreenView,
View.TRANSLATION_Y,
0f,
-splashScreenView.height.toFloat()
).apply {
interpolator = AnticipateInterpolator()
duration = 200L
doOnEnd { splashScreenView.remove() }
start()
}
} }
} }
@@ -212,8 +265,14 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }
} }
override fun onResume() {
super.onResume()
}
//ViewPager //ViewPager
private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
FragmentStateAdapter(fragmentManager, lifecycle) { FragmentStateAdapter(fragmentManager, lifecycle) {
@@ -230,4 +289,4 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }

View File

@@ -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

View File

@@ -1,3 +0,0 @@
NOTICE
This software includes code modified from Aniyomi, available at https://github.com/aniyomiorg/aniyomi/.

View File

@@ -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" }
}
}
}*/

View File

@@ -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())
} }

View File

@@ -1,3 +0,0 @@
package ani.dantotsu.aniyomi.util.srcapi
//actual suspend fun <T> Observable<T>.awaitSingle(): T = awaitSingle()

View File

@@ -16,7 +16,7 @@ fun updateProgress(media: Media, number: String) {
if (Anilist.userid != null) { if (Anilist.userid != null) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val a = number.toFloatOrNull()?.roundToInt() val a = number.toFloatOrNull()?.roundToInt()
if (a != media.userProgress) { if ((a?:0) > (media.userProgress?:0)) {
Anilist.mutation.editList( Anilist.mutation.editList(
media.id, media.id,
a, a,

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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()

View File

@@ -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.ItemAnimePageBinding import ani.dantotsu.databinding.ItemAnimePageBinding
import ani.dantotsu.loadData import ani.dantotsu.loadData
@@ -31,6 +32,8 @@ import ani.dantotsu.setSlideUp
import ani.dantotsu.settings.SettingsDialogFragment import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import com.google.android.material.card.MaterialCardView
import com.google.android.material.textfield.TextInputLayout
class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHolder>() { class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHolder>() {
val ready = MutableLiveData(false) val ready = MutableLiveData(false)
@@ -49,6 +52,13 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
binding = holder.binding binding = holder.binding
trendingViewPager = binding.animeTrendingViewPager trendingViewPager = binding.animeTrendingViewPager
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.animeSearchBar)
val currentColor = textInputLayout.boxBackgroundColor
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
textInputLayout.boxBackgroundColor = semiTransparentColor
val materialCardView = holder.itemView.findViewById<MaterialCardView>(R.id.animeUserAvatarContainer)
materialCardView.setCardBackgroundColor(semiTransparentColor)
binding.animeTitleContainer.updatePadding(top = statusBarHeight) binding.animeTitleContainer.updatePadding(top = statusBarHeight)
if (uiSettings.smallView) binding.animeTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { if (uiSettings.smallView) binding.animeTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
@@ -163,6 +173,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
} }
} }

View File

@@ -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
@@ -30,6 +31,8 @@ import ani.dantotsu.setSlideUp
import ani.dantotsu.settings.SettingsDialogFragment import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.UserInterfaceSettings import ani.dantotsu.settings.UserInterfaceSettings
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import com.google.android.material.card.MaterialCardView
import com.google.android.material.textfield.TextInputLayout
class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHolder>() { class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHolder>() {
val ready = MutableLiveData(false) val ready = MutableLiveData(false)
@@ -48,6 +51,13 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
binding = holder.binding binding = holder.binding
trendingViewPager = binding.mangaTrendingViewPager trendingViewPager = binding.mangaTrendingViewPager
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.mangaSearchBar)
val currentColor = textInputLayout.boxBackgroundColor
val semiTransparentColor= (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
textInputLayout.boxBackgroundColor = semiTransparentColor
val materialCardView = holder.itemView.findViewById<MaterialCardView>(R.id.mangaUserAvatarContainer)
materialCardView.setCardBackgroundColor(semiTransparentColor)
binding.mangaTitleContainer.updatePadding(top = statusBarHeight) binding.mangaTitleContainer.updatePadding(top = statusBarHeight)
if (uiSettings.smallView) binding.mangaTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { if (uiSettings.smallView) binding.mangaTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
@@ -153,6 +163,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
} }
} }

View File

@@ -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)

View File

@@ -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)

View File

@@ -2,7 +2,10 @@ 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 android.view.Window
import android.view.WindowManager
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -11,7 +14,10 @@ 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.loadData
import ani.dantotsu.media.user.ListViewPagerAdapter import ani.dantotsu.media.user.ListViewPagerAdapter
import ani.dantotsu.settings.UserInterfaceSettings
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 +33,40 @@ 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)
val typedValue = TypedValue()
theme.resolveAttribute(com.google.android.material.R.attr.colorOnPrimary, typedValue, true)
val primaryColor = typedValue.data
val typedValue2 = TypedValue()
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue2, true)
val primaryTextColor = typedValue2.data
val typedValue3 = TypedValue()
theme.resolveAttribute(com.google.android.material.R.attr.colorSecondary, 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, primaryTextColor)
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
if (!uiSettings.immersiveMode) {
this.window.statusBarColor =
ContextCompat.getColor(this, R.color.nav_bg_inv)
binding.root.fitsSystemWindows = true
}else{
binding.root.fitsSystemWindows = false
requestWindowFeature(Window.FEATURE_NO_TITLE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
setContentView(binding.root) setContentView(binding.root)
window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg)
binding.listTitle.setText(R.string.release_calendar) binding.listTitle.setText(R.string.release_calendar)
binding.listSort.visibility = View.GONE binding.listSort.visibility = View.GONE

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
@@ -162,13 +169,28 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
R.drawable.ic_round_favorite_24 R.drawable.ic_round_favorite_24
) )
) )
val typedValue = TypedValue()
this.theme.resolveAttribute(
com.google.android.material.R.attr.colorSecondary,
typedValue,
true
)
val color = typedValue.data
val typedValue2 = TypedValue()
this.theme.resolveAttribute(
com.google.android.material.R.attr.colorSecondary,
typedValue2,
true
)
val color2 = typedValue.data
PopImageButton( PopImageButton(
scope, scope,
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, R.color.bg_opp,
R.color.fav, R.color.violet_400,//TODO: Change to colorSecondary
media.isFav media.isFav
) { ) {
media.isFav = it media.isFav = it
@@ -180,17 +202,36 @@ 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 +247,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 +279,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 +303,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 +353,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 +380,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 +394,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 +416,45 @@ 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)
} }
@@ -425,8 +496,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
ObjectAnimator.ofFloat(image, "scaleX", 1f, 0f).setDuration(69).start() ObjectAnimator.ofFloat(image, "scaleX", 1f, 0f).setDuration(69).start()
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)
@@ -439,17 +512,19 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
ObjectAnimator.ofFloat(image, "scaleX", 1.5f, 1f).setDuration(100).start() ObjectAnimator.ofFloat(image, "scaleX", 1.5f, 1f).setDuration(100).start()
ObjectAnimator.ofFloat(image, "scaleY", 1.5f, 1f).setDuration(100).start() ObjectAnimator.ofFloat(image, "scaleY", 1.5f, 1f).setDuration(100).start()
delay(200) delay(200)
if (clicked) ObjectAnimator.ofArgb( if (clicked) {
image, ObjectAnimator.ofArgb(
"ColorFilter", image,
ContextCompat.getColor(context, c2), "ColorFilter",
ContextCompat.getColor(context, c1) ContextCompat.getColor(context, c2),
).setDuration(200).start() ContextCompat.getColor(context, c1)
).setDuration(200).start()
}
} }
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
} }
} }
} }

View File

@@ -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))

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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!!]

View File

@@ -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)

View 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
}
}
}
}

View File

@@ -3,12 +3,14 @@ package ani.dantotsu.media.anime
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.TypedValue
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.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.*
@@ -68,7 +70,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 {
@@ -176,6 +178,7 @@ class AnimeWatchAdapter(
binding.animeWatchChipScroll.smoothScrollTo((chip.left - screenWidth / 2) + (chip.width / 2), 0) binding.animeWatchChipScroll.smoothScrollTo((chip.left - screenWidth / 2) + (chip.width / 2), 0)
} }
chip.text = "${names[limit * (position)]} - ${names[last - 1]}" chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
chip.setTextColor(ContextCompat.getColorStateList(fragment.requireContext(), R.color.chip_text_color))
chip.setOnClickListener { chip.setOnClickListener {
selected() selected()

View File

@@ -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) {

View File

@@ -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("69")
.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("69")
.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,23 @@ 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(
// TrackSelectionOverride(trackSelector, 2))
) )
if (playbackPosition != 0L && !changingServer && !settings.alwaysContinue) { if (playbackPosition != 0L && !changingServer && !settings.alwaysContinue) {
@@ -1181,7 +1319,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()
@@ -1214,6 +1353,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
} }
playerView.player = exoPlayer playerView.player = exoPlayer
try { try {
mediaSession = MediaSession.Builder(this, exoPlayer).build() mediaSession = MediaSession.Builder(this, exoPlayer).build()
} catch (e: Exception) { } catch (e: Exception) {
@@ -1221,8 +1361,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 +1432,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 +1474,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 +1524,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 +1554,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)
} }
@@ -1400,6 +1574,27 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
} }
override fun onTracksChanged(tracks: Tracks) { override fun onTracksChanged(tracks: Tracks) {
tracks.groups.forEach {
println("Track__: $it")
println("Track__: ${it.length}")
println("Track__: ${it.isSelected}")
println("Track__: ${it.type}")
println("Track__: ${it.mediaTrackGroup.id}")
if (it.type == 3 && it.mediaTrackGroup.id == "1:"){
playerView.player?.trackSelectionParameters =
playerView.player?.trackSelectionParameters?.buildUpon()
?.setOverrideForType(
TrackSelectionOverride(it.mediaTrackGroup, it.length - 1))
?.build()!!
}else if(it.type == 3){
playerView.player?.trackSelectionParameters =
playerView.player?.trackSelectionParameters?.buildUpon()
?.addOverride(
TrackSelectionOverride(it.mediaTrackGroup, listOf()))
?.build()!!
}
}
println("Track: ${tracks.groups.size}")
if (tracks.groups.size <= 2) exoQuality.visibility = View.GONE if (tracks.groups.size <= 2) exoQuality.visibility = View.GONE
else { else {
exoQuality.visibility = View.VISIBLE exoQuality.visibility = View.VISIBLE
@@ -1426,6 +1621,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 +1649,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 +1707,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 +1780,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)
} }

View File

@@ -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
} }

View 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()
}

View File

@@ -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

View File

@@ -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,
@@ -61,14 +63,15 @@ class MangaChapterAdapter(
val binding = holder.binding val binding = holder.binding
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings) setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
val ep = arr[position] val ep = arr[position]
binding.itemEpisodeNumber.text = ep.number val parsedNumber = MangaNameAdapter.findChapterNumber(ep.number)?.toInt()
binding.itemEpisodeNumber.text = parsedNumber?.toString() ?: 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 +94,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 +116,6 @@ class MangaChapterAdapter(
fun updateType(t: Int) { fun updateType(t: Int) {
type = t type = t
} }
} }

View File

@@ -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
}
}
}
}

View File

@@ -7,6 +7,7 @@ import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.*
@@ -49,7 +50,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])
@@ -145,6 +146,7 @@ class MangaReadAdapter(
binding.animeWatchChipScroll.smoothScrollTo((chip.left - screenWidth / 2) + (chip.width / 2), 0) binding.animeWatchChipScroll.smoothScrollTo((chip.left - screenWidth / 2) + (chip.width / 2), 0)
} }
chip.text = "${names[limit * (position)]} - ${names[last - 1]}" chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
chip.setTextColor(ContextCompat.getColorStateList(fragment.requireContext(), R.color.chip_text_color))
chip.setOnClickListener { chip.setOnClickListener {
selected() selected()
@@ -174,7 +176,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,

View File

@@ -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

View File

@@ -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,39 @@ 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 {
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 +195,8 @@ abstract class BaseImageAdapter(
} }
} }
}
interface ImageFetcher {
suspend fun fetchImage(page: Page): Bitmap?
} }

View File

@@ -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)
} }
} }

View File

@@ -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(

View File

@@ -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

View File

@@ -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)
} }

View File

@@ -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())

View File

@@ -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)

View File

@@ -2,7 +2,10 @@ 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 android.view.Window
import android.view.WindowManager
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
@@ -12,6 +15,9 @@ 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.loadData
import ani.dantotsu.settings.UserInterfaceSettings
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 +32,39 @@ 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)
val typedValue = TypedValue()
theme.resolveAttribute(com.google.android.material.R.attr.colorOnPrimary, typedValue, true)
val primaryColor = typedValue.data
val typedValue2 = TypedValue()
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue2, true)
val primaryTextColor = typedValue2.data
val typedValue3 = TypedValue()
theme.resolveAttribute(com.google.android.material.R.attr.colorSecondary, 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, primaryTextColor)
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
if (!uiSettings.immersiveMode) {
this.window.statusBarColor =
ContextCompat.getColor(this, R.color.nav_bg_inv)
binding.root.fitsSystemWindows = true
}else{
binding.root.fitsSystemWindows = false
requestWindowFeature(Window.FEATURE_NO_TITLE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
setContentView(binding.root) setContentView(binding.root)
window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg)
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"

View File

@@ -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

View File

@@ -1,6 +1,8 @@
package ani.dantotsu.others package ani.dantotsu.others
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -46,6 +48,12 @@ open class CustomBottomDialog : BottomSheetDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = BottomSheetCustomBinding.inflate(inflater, container, false) _binding = BottomSheetCustomBinding.inflate(inflater, container, false)
val window = dialog?.window
window?.statusBarColor = Color.TRANSPARENT
val typedValue = TypedValue()
val theme = requireContext().theme
theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true)
window?.navigationBarColor = typedValue.data
return binding.root return binding.root
} }

View File

@@ -12,12 +12,14 @@ 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
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
@@ -72,12 +74,17 @@ class ImageViewDialog : BottomSheetDialogFragment() {
if (image2 != null) openLinkInBrowser(image2.url) if (image2 != null) openLinkInBrowser(image2.url)
true true
} }
val context = requireContext()
lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
val binding = _binding ?: return@launch val binding = _binding ?: return@launch
var bitmap = requireContext().loadBitmap(image, trans1 ?: listOf()) var bitmap = context.loadBitmap_old(image, trans1 ?: listOf())
val bitmap2 = if (image2 != null) requireContext().loadBitmap(image2, trans2 ?: listOf()) else null var bitmap2 = if (image2 != null) context.loadBitmap_old(image2, trans2 ?: listOf()) else null
if (bitmap == null) {
bitmap = context.loadBitmap(image, trans1 ?: listOf())
bitmap2 = if (image2 != null) context.loadBitmap(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

View File

@@ -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

View File

@@ -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)

View File

@@ -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
* **/ * **/

View File

@@ -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()

View File

@@ -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
} }
} }

View File

@@ -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.lowercase(), mediaObj.mainName().lowercase()) }
} else { } else {
emptyList() emptyList()
} }
response = sortedResults.firstOrNull() response = sortedResults.firstOrNull()
if (response == null) { if (response == null || FuzzySearch.ratio(response.name.lowercase(), mediaObj.mainName().lowercase()) < 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.lowercase(), mediaObj.nameRomaji.lowercase()) }
} 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?.lowercase() ?: "", mediaObj.nameRomaji.lowercase())
val mainNameRatio = FuzzySearch.ratio(response.name.lowercase(), mediaObj.mainName().lowercase())
logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name.lowercase()}")
logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name?.lowercase() ?: "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)
} }

View File

@@ -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
} }
} }

View File

@@ -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)
} }

View File

@@ -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()
} }

View File

@@ -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)
} }

View File

@@ -159,5 +159,5 @@ enum class VideoType{
} }
enum class SubtitleType{ enum class SubtitleType{
VTT, ASS, SRT VTT, ASS, SRT, UNKNOWN
} }

View File

@@ -0,0 +1,126 @@
package ani.dantotsu.settings
import android.app.NotificationManager
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.app.NotificationCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
import ani.dantotsu.settings.paging.AnimeExtensionAdapter
import ani.dantotsu.settings.paging.AnimeExtensionsViewModel
import ani.dantotsu.settings.paging.AnimeExtensionsViewModelFactory
import ani.dantotsu.settings.paging.OnAnimeInstallClickListener
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.flow.collectLatest
import kotlinx.coroutines.launch
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class AnimeExtensionsFragment : Fragment(),
SearchQueryHandler, OnAnimeInstallClickListener {
private var _binding: FragmentAnimeExtensionsBinding? = null
private val binding get() = _binding!!
private val viewModel: AnimeExtensionsViewModel by viewModels {
AnimeExtensionsViewModelFactory(animeExtensionManager)
}
private val adapter by lazy {
AnimeExtensionAdapter(this)
}
private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
binding.allAnimeExtensionsRecyclerView.isNestedScrollingEnabled = false
binding.allAnimeExtensionsRecyclerView.adapter = adapter
binding.allAnimeExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
(binding.allAnimeExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = true
lifecycleScope.launch {
viewModel.pagerFlow.collectLatest {
adapter.submitData(it)
}
}
viewModel.invalidatePager() // Force a refresh of the pager
return binding.root
}
override fun updateContentBasedOnQuery(query: String?) {
viewModel.setSearchQuery(query ?: "")
}
override fun onInstallClick(pkg: AnimeExtension.Available) {
val context = requireContext()
if (isAdded) {
val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Start the installation process
animeExtensionManager.installExtension(pkg)
.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(R.drawable.ic_round_download_24)
.setContentTitle("Installation complete")
.setContentText("The extension has been successfully installed.")
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
viewModel.invalidatePager()
}
)
}
}
override fun onDestroyView() {
super.onDestroyView();_binding = null
}
}

View File

@@ -13,26 +13,8 @@ class DevelopersDialogFragment : BottomSheetDialogFragment() {
private val binding get() = _binding!! private val binding get() = _binding!!
private val developers = arrayOf( private val developers = arrayOf(
Developer("vorobyovgabriel","https://avatars.githubusercontent.com/u/99561687?s=120&v=4","Owner","https://github.com/vorobyovgabriel"), Developer("rebelonion","https://avatars.githubusercontent.com/u/87634197?v=4","Owner and Maintainer","https://github.com/rebelonion"),
Developer("brahmkshtriya","https://avatars.githubusercontent.com/u/69040506?s=120&v=4","Maintainer","https://github.com/brahmkshatriya"), Developer("Wai What", "https://cdn.discordapp.com/avatars/928202695611908126/aeac4c867acbb8c3783356497055a426.webp?size=80", "Icon Designer", ""),
Developer("jeelpatel231","https://avatars.githubusercontent.com/u/33726155?s=120&v=4","Contributor","https://github.com/jeelpatel231"),
Developer("blatzar","https://avatars.githubusercontent.com/u/46196380?s=120&v=4","Contributor","https://github.com/Blatzar"),
Developer("bilibox","https://avatars.githubusercontent.com/u/1800580?s=120&v=4","Contributor","https://github.com/Bilibox"),
Developer("sutslec","https://avatars.githubusercontent.com/u/27722281?s=120&v=4","Contributor","https://github.com/Sutslec"),
Developer("4jx","https://avatars.githubusercontent.com/u/79868816?s=120&v=4","Contributor","https://github.com/4JX"),
Developer("xtrm-en","https://avatars.githubusercontent.com/u/26600206?s=120&v=4","Contributor","https://github.com/xtrm-en"),
Developer("scrazzz","https://avatars.githubusercontent.com/u/70033559?s=120&v=4","Contributor","https://github.com/scrazzz"),
Developer("defcoding","https://avatars.githubusercontent.com/u/39608887?s=120&v=4","Contributor","https://github.com/defcoding"),
Developer("adolar0042","https://avatars.githubusercontent.com/u/39769465?s=120&v=4","Contributor","https://github.com/adolar0042"),
Developer("diegopyl1209","https://avatars.githubusercontent.com/u/80992641?s=120&v=4","Contributor","https://github.com/diegopyl1209"),
Developer("sreekrishna2001","https://avatars.githubusercontent.com/u/67505103?s=120&v=4","Contributor","https://github.com/Sreekrishna2001"),
Developer("riimuru","https://avatars.githubusercontent.com/u/57333995?s=120&v=4","Contributor","https://github.com/riimuru"),
Developer("vu nguyen","https://avatars.githubusercontent.com/u/68330291?s=120&v=4","Contributor","https://github.com/hoangvu12"),
Developer("animejeff","https://avatars.githubusercontent.com/u/101831300?s=120&v=4","Contributor","https://github.com/AnimeJeff"),
Developer("antonydp","https://avatars.githubusercontent.com/u/38143733?s=120&v=4","Contributor","https://github.com/antonydp"),
Developer("tobybridle","https://avatars.githubusercontent.com/u/52335751?s=120&v=4","Contributor","https://github.com/TobyBridle"),
Developer("enimax","https://avatars.githubusercontent.com/u/107899019?s=120&v=4","Contributor","https://github.com/enimax-anime"),
Developer("vipulog","https://avatars.githubusercontent.com/u/90324465?s=120&v=4","Contributor","https://github.com/VipulOG")
) )
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {

View File

@@ -1,150 +1,84 @@
package ani.dantotsu.settings package ani.dantotsu.settings
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
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.text.Editable
import android.view.LayoutInflater import android.text.TextWatcher
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.AutoCompleteTextView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.SearchView import android.widget.SearchView
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.NotificationCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.* import ani.dantotsu.*
import ani.dantotsu.aniyomi.anime.AnimeExtensionManager
import ani.dantotsu.aniyomi.anime.model.AnimeExtension
import ani.dantotsu.databinding.ActivityExtensionsBinding import ani.dantotsu.databinding.ActivityExtensionsBinding
import com.bumptech.glide.Glide import ani.dantotsu.themes.ThemeManager
import kotlinx.coroutines.CoroutineScope import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.Dispatchers import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.injectLazy
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 = 4
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)
}
}
override fun createFragment(position: Int): Fragment {
val searchView: SearchView = findViewById(R.id.searchView) return when (position) {
val extensionsRecyclerView: RecyclerView = findViewById(R.id.extensionsRecyclerView) 0 -> InstalledAnimeExtensionsFragment()
val extensionsHeader: LinearLayout = findViewById(R.id.extensionsHeader) 1 -> AnimeExtensionsFragment()
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { 2 -> InstalledMangaExtensionsFragment()
override fun onQueryTextSubmit(query: String?): Boolean { 3 -> MangaExtensionsFragment()
return false else -> AnimeExtensionsFragment()
} }
}
override fun onQueryTextChange(newText: String?): Boolean { }
if (newText.isNullOrEmpty()) {
allExtensionsAdapter.filter("") // Reset the filter TabLayoutMediator(tabLayout, viewPager) { tab, position ->
allextenstionsRecyclerView.visibility = View.VISIBLE tab.text = when (position) {
extensionsHeader.visibility = View.VISIBLE 0 -> "Installed Anime"
extensionsRecyclerView.visibility = View.VISIBLE 1 -> "Available Anime"
} else { 2 -> "Installed Manga"
allExtensionsAdapter.filter(newText) 3 -> "Available Manga"
allextenstionsRecyclerView.visibility = View.VISIBLE else -> null
extensionsRecyclerView.visibility = View.GONE }
extensionsHeader.visibility = View.GONE }.attach()
val searchView: AutoCompleteTextView = findViewById(R.id.searchViewText)
searchView.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val currentFragment = supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}")
if (currentFragment is SearchQueryHandler) {
currentFragment.updateContentBasedOnQuery(s?.toString()?.trim())
} }
return true
} }
}) })
@@ -164,104 +98,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
}
}
}
}
}

View File

@@ -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)

View File

@@ -0,0 +1,184 @@
package ani.dantotsu.settings
import android.app.NotificationManager
import android.content.Context
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.FragmentAnimeExtensionsBinding
import ani.dantotsu.loadData
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.launch
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class InstalledAnimeExtensionsFragment : Fragment() {
private var _binding: FragmentAnimeExtensionsBinding? = null
private val binding get() = _binding!!
private lateinit var extensionsRecyclerView: RecyclerView
val skipIcons = loadData("skip_extension_icons") ?: false
private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
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)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
extensionsRecyclerView = binding.allAnimeExtensionsRecyclerView
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
extensionsRecyclerView.adapter = extensionsAdapter
lifecycleScope.launch {
animeExtensionManager.installedExtensionsFlow.collect { extensions ->
extensionsAdapter.updateData(extensions)
}
}
val extensionsRecyclerView: RecyclerView = binding.allAnimeExtensionsRecyclerView
return binding.root
}
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
}
}
}
}
}

View File

@@ -0,0 +1,185 @@
package ani.dantotsu.settings
import android.app.NotificationManager
import android.content.Context
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.FragmentMangaExtensionsBinding
import ani.dantotsu.loadData
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.launch
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class InstalledMangaExtensionsFragment : Fragment() {
private var _binding: FragmentMangaExtensionsBinding? = null
private val binding get() = _binding!!
private lateinit var extensionsRecyclerView: RecyclerView
val skipIcons = loadData("skip_extension_icons") ?: false
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
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: ${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 {
mangaExtensionManager.uninstallExtension(pkg.pkgName)
}
}
}, skipIcons)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
extensionsRecyclerView = binding.allMangaExtensionsRecyclerView
extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
extensionsRecyclerView.adapter = extensionsAdapter
lifecycleScope.launch {
mangaExtensionManager.installedExtensionsFlow.collect { extensions ->
extensionsAdapter.updateData(extensions)
}
}
val extensionsRecyclerView: RecyclerView = binding.allMangaExtensionsRecyclerView
return binding.root
}
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
fun updateData(newExtensions: List<MangaExtension.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<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
}
}
}
}
}

View File

@@ -0,0 +1,130 @@
package ani.dantotsu.settings
import android.app.NotificationManager
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.app.NotificationCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.paging.PagingData
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
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.launch
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import ani.dantotsu.settings.paging.MangaExtensionAdapter
import ani.dantotsu.settings.paging.MangaExtensionsViewModel
import ani.dantotsu.settings.paging.MangaExtensionsViewModelFactory
import ani.dantotsu.settings.paging.OnMangaInstallClickListener
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
class MangaExtensionsFragment : Fragment(),
SearchQueryHandler, OnMangaInstallClickListener {
private var _binding: FragmentMangaExtensionsBinding? = null
private val binding get() = _binding!!
private val viewModel: MangaExtensionsViewModel by viewModels {
MangaExtensionsViewModelFactory(mangaExtensionManager)
}
private val adapter by lazy {
MangaExtensionAdapter(this)
}
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
binding.allMangaExtensionsRecyclerView.isNestedScrollingEnabled = false
binding.allMangaExtensionsRecyclerView.adapter = adapter
binding.allMangaExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
(binding.allMangaExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = true
lifecycleScope.launch {
viewModel.pagerFlow.collectLatest { pagingData ->
adapter.submitData(pagingData)
}
}
viewModel.invalidatePager() // Force a refresh of the pager
return binding.root
}
override fun updateContentBasedOnQuery(query: String?) {
viewModel.setSearchQuery(query ?: "")
}
override fun onInstallClick(pkg: MangaExtension.Available) {
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(pkg)
.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(R.drawable.ic_round_download_24)
.setContentTitle("Installation complete")
.setContentText("The extension has been successfully installed.")
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
viewModel.invalidatePager()
}
)
}
}
override fun onDestroyView() {
super.onDestroyView();_binding = null
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,17 @@ 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 com.google.android.material.snackbar.Snackbar
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 +51,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 +99,43 @@ 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()
restartApp()
}
binding.settingsUseOLED.isChecked = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_oled", false)
binding.settingsUseOLED.setOnCheckedChangeListener { _, isChecked ->
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_oled", isChecked).apply()
restartApp()
}
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()
restartApp()
}
//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 +154,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 +221,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 +414,7 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
} }
} }
var curTime = loadData<Int>("subscriptions_time") ?: defaultTime var curTime = loadData<Int>("subscriptions_time_s") ?: 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 +427,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_s", curTime)
dialog.dismiss() dialog.dismiss()
startSubscription(true) startSubscription(true)
}.show() }.show()
@@ -497,7 +571,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 :(") {
@@ -516,4 +590,18 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
} }
} }
} }
private fun restartApp() {
Snackbar.make(
binding.root,
R.string.restart_app, Snackbar.LENGTH_SHORT
).apply {
val mainIntent =
Intent.makeRestartActivityTask(context.packageManager.getLaunchIntentForPackage(context.packageName)!!.component)
setAction("Do it!") {
context.startActivity(mainIntent)
Runtime.getRuntime().exit(0)
}
show()
}
}
} }

View File

@@ -3,8 +3,10 @@ package ani.dantotsu.settings
import android.app.DownloadManager import android.app.DownloadManager
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -26,6 +28,12 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val window = dialog?.window
window?.statusBarColor = Color.CYAN
val typedValue = TypedValue()
val theme = requireContext().theme
theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true)
window?.navigationBarColor = typedValue.data
if (Anilist.token != null) { if (Anilist.token != null) {
binding.settingsLogin.setText(R.string.logout) binding.settingsLogin.setText(R.string.logout)

View File

@@ -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)

View File

@@ -0,0 +1,162 @@
package ani.dantotsu.settings.paging
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingDataAdapter
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.databinding.ItemExtensionAllBinding
import ani.dantotsu.loadData
import com.bumptech.glide.Glide
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
class AnimeExtensionsViewModelFactory(
private val animeExtensionManager: AnimeExtensionManager
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return AnimeExtensionsViewModel(animeExtensionManager) as T
}
}
class AnimeExtensionsViewModel(
private val animeExtensionManager: AnimeExtensionManager
) : ViewModel() {
private val searchQuery = MutableStateFlow("")
private var currentPagingSource: AnimeExtensionPagingSource? = null
fun setSearchQuery(query: String) {
searchQuery.value = query
}
fun invalidatePager() {
currentPagingSource?.invalidate()
}
@OptIn(ExperimentalCoroutinesApi::class)
val pagerFlow: Flow<PagingData<AnimeExtension.Available>> = searchQuery.flatMapLatest { query ->
Pager(
PagingConfig(
pageSize = 15,
initialLoadSize = 15,
prefetchDistance = 15
)
) {
AnimeExtensionPagingSource(
animeExtensionManager.availableExtensionsFlow,
animeExtensionManager.installedExtensionsFlow,
searchQuery
).also { currentPagingSource = it }
}.flow
}.cachedIn(viewModelScope)
}
class AnimeExtensionPagingSource(
private val availableExtensionsFlow: StateFlow<List<AnimeExtension.Available>>,
private val installedExtensionsFlow: StateFlow<List<AnimeExtension.Installed>>,
private val searchQuery: StateFlow<String>
) : PagingSource<Int, AnimeExtension.Available>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AnimeExtension.Available> {
val position = params.key ?: 0
val installedExtensions = installedExtensionsFlow.first().map { it.pkgName }.toSet()
val availableExtensions = availableExtensionsFlow.first().filterNot { it.pkgName in installedExtensions }
val query = searchQuery.first()
val filteredExtensions = if (query.isEmpty()) {
availableExtensions
} else {
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
}
return try {
val sublist = filteredExtensions.subList(
fromIndex = position,
toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size)
)
LoadResult.Page(
data = sublist,
prevKey = if (position == 0) null else position - params.loadSize,
nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, AnimeExtension.Available>): Int? {
return null
}
}
class AnimeExtensionAdapter(private val clickListener: OnAnimeInstallClickListener) :
PagingDataAdapter<AnimeExtension.Available, AnimeExtensionAdapter.AnimeExtensionViewHolder>(
DIFF_CALLBACK
) {
private val skipIcons = loadData("skip_extension_icons") ?: false
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<AnimeExtension.Available>() {
override fun areItemsTheSame(oldItem: AnimeExtension.Available, newItem: AnimeExtension.Available): Boolean {
// Your logic here
return oldItem.pkgName == newItem.pkgName
}
override fun areContentsTheSame(oldItem: AnimeExtension.Available, newItem: AnimeExtension.Available): Boolean {
// Your logic here
return oldItem == newItem
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimeExtensionViewHolder {
val binding = ItemExtensionAllBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return AnimeExtensionViewHolder(binding)
}
override fun onBindViewHolder(holder: AnimeExtensionViewHolder, position: Int) {
val extension = getItem(position)
if (extension != null) {
if (!skipIcons) {
Glide.with(holder.itemView.context)
.load(extension.iconUrl)
.into(holder.extensionIconImageView)
}
holder.bind(extension)
}
}
inner class AnimeExtensionViewHolder(private val binding: ItemExtensionAllBinding) : RecyclerView.ViewHolder(binding.root) {
init {
binding.closeTextView.setOnClickListener {
val extension = getItem(bindingAdapterPosition)
if (extension != null) {
clickListener.onInstallClick(extension)
}
}
}
val extensionIconImageView: ImageView = binding.extensionIconImageView
fun bind(extension: AnimeExtension.Available) {
binding.extensionNameTextView.text = extension.name
}
}
}
interface OnAnimeInstallClickListener {
fun onInstallClick(pkg: AnimeExtension.Available)
}

View File

@@ -0,0 +1,164 @@
package ani.dantotsu.settings.paging
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingDataAdapter
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.databinding.ItemExtensionAllBinding
import ani.dantotsu.loadData
import com.bumptech.glide.Glide
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import java.lang.Math.min
class MangaExtensionsViewModelFactory(
private val mangaExtensionManager: MangaExtensionManager
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MangaExtensionsViewModel(mangaExtensionManager) as T
}
}
class MangaExtensionsViewModel(
private val mangaExtensionManager: MangaExtensionManager
) : ViewModel() {
private val searchQuery = MutableStateFlow("")
private var currentPagingSource: MangaExtensionPagingSource? = null
fun setSearchQuery(query: String) {
searchQuery.value = query
}
fun invalidatePager() {
currentPagingSource?.invalidate()
}
@OptIn(ExperimentalCoroutinesApi::class)
val pagerFlow: Flow<PagingData<MangaExtension.Available>> = searchQuery.flatMapLatest { query ->
Pager(
PagingConfig(
pageSize = 15,
initialLoadSize = 15,
prefetchDistance = 15
)
) {
MangaExtensionPagingSource(
mangaExtensionManager.availableExtensionsFlow,
mangaExtensionManager.installedExtensionsFlow,
searchQuery
).also { currentPagingSource = it }
}.flow
}.cachedIn(viewModelScope)
}
class MangaExtensionPagingSource(
private val availableExtensionsFlow: StateFlow<List<MangaExtension.Available>>,
private val installedExtensionsFlow: StateFlow<List<MangaExtension.Installed>>,
private val searchQuery: StateFlow<String>
) : PagingSource<Int, MangaExtension.Available>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MangaExtension.Available> {
val position = params.key ?: 0
val installedExtensions = installedExtensionsFlow.first().map { it.pkgName }.toSet()
val availableExtensions = availableExtensionsFlow.first().filterNot { it.pkgName in installedExtensions }
val query = searchQuery.first()
val filteredExtensions = if (query.isEmpty()) {
availableExtensions
} else {
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
}
return try {
val sublist = filteredExtensions.subList(
fromIndex = position,
toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size)
)
LoadResult.Page(
data = sublist,
prevKey = if (position == 0) null else position - params.loadSize,
nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, MangaExtension.Available>): Int? {
return null
}
}
class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListener) :
PagingDataAdapter<MangaExtension.Available, MangaExtensionAdapter.MangaExtensionViewHolder>(
DIFF_CALLBACK
) {
private val skipIcons = loadData("skip_extension_icons") ?: false
companion object {
private val DIFF_CALLBACK = 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
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaExtensionViewHolder {
val binding = ItemExtensionAllBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MangaExtensionViewHolder(binding)
}
override fun onBindViewHolder(holder: MangaExtensionViewHolder, position: Int) {
val extension = getItem(position)
if (extension != null) {
if (!skipIcons) {
Glide.with(holder.itemView.context)
.load(extension.iconUrl)
.into(holder.extensionIconImageView)
}
holder.bind(extension)
}
}
inner class MangaExtensionViewHolder(private val binding: ItemExtensionAllBinding) : RecyclerView.ViewHolder(binding.root) {
init {
binding.closeTextView.setOnClickListener {
val extension = getItem(bindingAdapterPosition)
if (extension != null) {
clickListener.onInstallClick(extension)
}
}
}
val extensionIconImageView: ImageView = binding.extensionIconImageView
fun bind(extension: MangaExtension.Available) {
binding.extensionNameTextView.text = extension.name
}
}
}
interface OnMangaInstallClickListener {
fun onInstallClick(pkg: MangaExtension.Available)
}

View File

@@ -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_s", context) ?: defaultTime
if (timeMinutes[curTime] > 0) if (timeMinutes[curTime] > 0)
alarmManager.setRepeating( alarmManager.setRepeating(

View File

@@ -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 = 1
val timeMinutes = arrayOf(0L, 5, 10, 15, 30, 45, 60, 90, 120, 180, 240, 360, 480, 720, 1440) val timeMinutes = arrayOf(0L, 720, 1440)
private var alreadyStarted = false private var alreadyStarted = false
fun Context.startSubscription(force: Boolean = false) { fun Context.startSubscription(force: Boolean = false) {

View File

@@ -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)
} }
} }

View File

@@ -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_s") ?: 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(

View File

@@ -0,0 +1,56 @@
package ani.dantotsu.themes
import android.content.Context
import android.content.res.Configuration
import ani.dantotsu.R
class ThemeManager(private val context: Context) {
fun applyTheme() {
val useOLED = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_oled", false) && isDarkThemeActive(context)
if(context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_material_you", false)){
return
}
val theme = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getString("theme", "PURPLE")!!
val themeToApply = when (theme) {
"PURPLE" -> if (useOLED) R.style.Theme_Dantotsu_PurpleOLED else R.style.Theme_Dantotsu_Purple
"BLUE" -> if (useOLED) R.style.Theme_Dantotsu_BlueOLED else R.style.Theme_Dantotsu_Blue
"GREEN" -> if (useOLED) R.style.Theme_Dantotsu_GreenOLED else R.style.Theme_Dantotsu_Green
"PINK" -> if (useOLED) R.style.Theme_Dantotsu_PinkOLED else R.style.Theme_Dantotsu_Pink
"RED" -> if (useOLED) R.style.Theme_Dantotsu_RedOLED else R.style.Theme_Dantotsu_Red
"LAVENDER" -> if (useOLED) R.style.Theme_Dantotsu_LavenderOLED else R.style.Theme_Dantotsu_Lavender
"MONOCHROME (BETA)" -> if (useOLED) R.style.Theme_Dantotsu_MonochromeOLED else R.style.Theme_Dantotsu_Monochrome
else -> if (useOLED) R.style.Theme_Dantotsu_PurpleOLED else R.style.Theme_Dantotsu_Purple
}
context.setTheme(themeToApply)
}
private fun isDarkThemeActive(context: Context): Boolean {
return when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> true
Configuration.UI_MODE_NIGHT_NO -> false
Configuration.UI_MODE_NIGHT_UNDEFINED -> false
else -> false
}
}
companion object{
enum class Theme(val theme: String) {
PURPLE("PURPLE"),
BLUE("BLUE"),
GREEN("GREEN"),
PINK("PINK"),
RED("RED"),
LAVENDER("LAVENDER"),
MONOCHROME("MONOCHROME (BETA)");
companion object {
fun fromString(value: String): Theme {
return values().find { it.theme == value } ?: PURPLE
}
}
}
}
}

View File

@@ -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)

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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)
}
}
}

View File

@@ -1,3 +1,3 @@
package ani.dantotsu.aniyomi package eu.kanade.tachiyomi
typealias PreferenceScreen = androidx.preference.PreferenceScreen typealias PreferenceScreen = androidx.preference.PreferenceScreen

View File

@@ -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

View File

@@ -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
/** /**

View File

@@ -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.

View File

@@ -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 {

Some files were not shown because too many files have changed in this diff Show More