mirror of
https://github.com/rebelonion/Dantotsu.git
synced 2026-01-29 14:11:07 +00:00
Compare commits
58 Commits
v0.1.5
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d9254140d | ||
|
|
533aa9f56e | ||
|
|
c310bea0e9 | ||
|
|
813f7a0992 | ||
|
|
4c0f56d3e3 | ||
|
|
1f44d32f35 | ||
|
|
d937f447ef | ||
|
|
187262a266 | ||
|
|
f40ebc9d09 | ||
|
|
3998d88297 | ||
|
|
4db301ca7a | ||
|
|
3dfcc9fc31 | ||
|
|
df63586c02 | ||
|
|
d7372d4dbb | ||
|
|
f4266d0da3 | ||
|
|
736b06bdbe | ||
|
|
5543d29317 | ||
|
|
2fc351f57a | ||
|
|
eee1242964 | ||
|
|
a58e9a523a | ||
|
|
cd3aad1c33 | ||
|
|
5a482d8307 | ||
|
|
91d869005c | ||
|
|
05e73269d3 | ||
|
|
3dac48ced8 | ||
|
|
8c5726ab8a | ||
|
|
076516be23 | ||
|
|
1059a3c17e | ||
|
|
c75df942f2 | ||
|
|
cfe7be5cdb | ||
|
|
390fc18c4c | ||
|
|
4c82c56828 | ||
|
|
da5c480ba7 | ||
|
|
20acd71b1a | ||
|
|
231c9c5b98 | ||
|
|
4cfdcdb23c | ||
|
|
acb0225699 | ||
|
|
ebffaaa742 | ||
|
|
1760064555 | ||
|
|
44a6db3fc2 | ||
|
|
aab25d157e | ||
|
|
8a4be86ddc | ||
|
|
31baf729be | ||
|
|
b98e3dc780 | ||
|
|
878d58679e | ||
|
|
f500ba6cf0 | ||
|
|
d124736556 | ||
|
|
1a825e2509 | ||
|
|
6e14c2221d | ||
|
|
5b6e351a56 | ||
|
|
c310708401 | ||
|
|
f0093b903a | ||
|
|
d33568f0ad | ||
|
|
26f9f40042 | ||
|
|
7545870f38 | ||
|
|
3368a1bc8d | ||
|
|
9c0ef7a788 | ||
|
|
960c2b4113 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -28,3 +28,6 @@ google-services.json
|
|||||||
|
|
||||||
# Android Profiling
|
# Android Profiling
|
||||||
*.hprof
|
*.hprof
|
||||||
|
|
||||||
|
#other
|
||||||
|
scripts/
|
||||||
|
|||||||
113
README.md
113
README.md
@@ -1,109 +1,36 @@
|
|||||||
# **Dantotsu** (🚧 ALPHA 🚧)
|
|
||||||
|
|
||||||
> ⚠️ **WARNING**: This project is in alpha stage. Things may not work as expected.
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
<img src="https://pbxt.replicate.delivery/2PX94viD6lJSDVayQrGyDH7CGu7IjQ6e8HEtOGDeelefXRdOC/out.png" alt="Dantotsu Banner" width=100% >
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://img.shields.io/badge/platforms-android-blueviolet?style=for-the-badge"/>
|
||||||
<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>
|
||||||
<a href="https://github.com/rebelonion/Dantotsu/releases"><img src="https://img.shields.io/github/downloads/rebelonion/Dantotsu/total?color=%233DDC84&logo=android&logoColor=%23fff&style=for-the-badge"></a>
|
<a href="https://github.com/rebelonion/Dantotsu/releases"><img src="https://img.shields.io/github/downloads/rebelonion/Dantotsu/total?color=%233DDC84&logo=android&logoColor=%23fff&style=for-the-badge"></a>
|
||||||
</p>
|
</p>
|
||||||
Dantotsu is crafted from the ashes of Saikou and based on simplistic yet state-of-the-art elegance. It is an <a href="https://anilist.co/">Anilist</a> only client, which also lets you stream-download Anime & Manga through extensions.
|
|
||||||
<br><br>
|
# **Dantotsu** 🌟
|
||||||
<i>Dantotsu (断トツ; Dan-totsu) literally means the best of the best in Japanese. Well, we would like to say this is the best open source app for anime and manga on Android, but hey, try it out yourself & judge!
|
|
||||||
</i>
|
Dantotsu is an [Anilist](https://anilist.co/) only client.
|
||||||
<br>
|
|
||||||
<br>
|
> **Dantotsu (断トツ; Dan-totsu)** literally means "the best of the best" in Japanese. Try it out for yourself and be the judge!
|
||||||
|
|
||||||
<a href="https://www.buymeacoffee.com/rebelonion"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=rebelonion&button_colour=FFDD00&font_colour=000000&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff" /></a>
|
<a href="https://www.buymeacoffee.com/rebelonion"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=rebelonion&button_colour=FFDD00&font_colour=000000&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff" /></a>
|
||||||
<br>
|
|
||||||
|
|
||||||
### 🌟STAR THIS REPOSITORY TO SUPPORT THE DEVELOPER AND ENCOURAGE THE DEVELOPMENT OF THE APPLICATION!
|
### 🚀 STAR THIS REPOSITORY TO SUPPORT THE DEVELOPER AND ENCOURAGE THE DEVELOPMENT OF THE APPLICATION!
|
||||||
|
|
||||||
> **Warning**
|
## WANT TO CONTRIBUTE? 🤝
|
||||||
>
|
|
||||||
> Please do not attempt to upload Dantotsu or any of it's forks on Playstore or any other Android appstores on the internet. Doing so, may infringe their terms and conditions. This may result to legal action or immediate take-down of the app.
|
|
||||||
|
|
||||||
## Extension Status
|
All contributions are welcome, from code to documentation to graphics to design suggestions to bug reports. Please use GitHub to its fullest; contribute Pull Requests, contribute tutorials or other content - whatever you have to offer, we can use!
|
||||||
|
|
||||||
| Type | Status |
|
You can come hang out with our awesome community, request new features, and report any bugs or issues at our Discord server too. 📣
|
||||||
| ---------------- | ------- |
|
|
||||||
| Anime Extensions | Working |
|
|
||||||
| Manga Extensions | "Working" |
|
|
||||||
| Light Novel Extensions | Not Working |
|
|
||||||
|
|
||||||
|
### OFFICIAL DISCORD SERVER 🚀
|
||||||
|
|
||||||
|
|
||||||
## APP FEATURES
|
|
||||||
|
|
||||||
- Easy and functional way to both, watch anime and read manga, ad-free.
|
|
||||||
|
|
||||||
- A completely open source app with a nice UI & Animations :)
|
|
||||||
|
|
||||||
- Aniyomi extension support built right into the app.
|
|
||||||
|
|
||||||
- Synchronize anime and manga real-time with AniList and MyAnimeList. Easily categorise anime and manga based on your current status. (Powered by AniList)
|
|
||||||
|
|
||||||
- Find all shows using thoroughly and frequently updated list of all trending, popular and ongoing anime based on scores.
|
|
||||||
|
|
||||||
- View extensive details about anime shows, movies and manga titles. It also features ability to countdown to the next episode of airing anime. (Powered by AniList & MyAnimeList)
|
|
||||||
|
|
||||||
- Get notified when new episodes/chapters are released!
|
|
||||||
|
|
||||||
|
|
||||||
* **Available Anime sources:-**
|
|
||||||
NONE BUILT IN!
|
|
||||||
add your own extensions in the settings menu (Dantotsu has no affiliation with any of the extensions)
|
|
||||||
|
|
||||||
|
|
||||||
* **Available Manga sources:-**
|
|
||||||
NONE BUILT IN!
|
|
||||||
add your own extensions in the settings menu (Dantotsu has no affiliation with any of the extensions)
|
|
||||||
|
|
||||||
## Planned Stuff
|
|
||||||
|
|
||||||
- get app out of alpha
|
|
||||||
|
|
||||||
- Accent Color Change (RIP Hot Pink Supremacy.)
|
|
||||||
|
|
||||||
|
|
||||||
## Rejected Stuff (still rejected)
|
|
||||||
|
|
||||||
- Sources of any language except English
|
|
||||||
|
|
||||||
- News Section in the App
|
|
||||||
|
|
||||||
- Comment Section
|
|
||||||
|
|
||||||
|
|
||||||
## WANT TO CONTRIBUTE?
|
|
||||||
|
|
||||||
- All contributions are welcome, from code to documentation to graphics to design suggestions to bug reports. Please use GitHub to its fullest; contribute Pull Requests, contribute tutorials or other content- whatever you have to offer, we can use it!
|
|
||||||
|
|
||||||
- You can come hang out with our awesome community and request new features and report any bugs or issue at our discord server too.
|
|
||||||
|
|
||||||
### Official Discord Server
|
|
||||||
|
|
||||||
<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://invidget.switchblade.xyz/4HPZ5nAWwM">
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
## LICENSE 📜
|
||||||
### VISIT FOR MORE INFORMATION:-
|
|
||||||
|
|
||||||
no website yet :(
|
|
||||||
|
|
||||||
## DISCLAIMER
|
|
||||||
|
|
||||||
* Dantotsu by itself only provides an anime and manga tracker and does not provide any anime or manga streaming or downloading capabilities.
|
|
||||||
|
|
||||||
* Dantotsu or any of its developer/staff don't host any of the content found inside Dantotsu. Any and all images and anime/manga information found in the app are taken from various public APIs (AniList, MyAnimeList, Kitsu).
|
|
||||||
|
|
||||||
* Furthermore, all of the anime/manga links found in Dantotsu are taken from various 3rd party plugins and have no affiliation with Dantotsu or its staff.
|
|
||||||
|
|
||||||
* Dantotsu or it's owners aren't liable for any misuse of any of the contents found inside or outside of the app and cannot be held accountable for the distribution of any of the contents found inside the app.
|
|
||||||
|
|
||||||
* By using Dantotsu, you comply to the fact that the developer of the app is not responsible for any of the contents found in the app. You also agree to the fact that you may not use Dantotsu to download or stream any copyrighted content.
|
|
||||||
|
|
||||||
* If the internet infringement issues are involved, please contact the source website. The developer does not assume any legal responsibility.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Dantotsu is licensed under the [GNU General Public License v3.0](LICENSE.md)
|
Dantotsu is licensed under the [GNU General Public License v3.0](LICENSE.md)
|
||||||
|
|||||||
@@ -21,15 +21,14 @@ android {
|
|||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode ((System.currentTimeMillis() / 60000).toInteger())
|
versionCode ((System.currentTimeMillis() / 60000).toInteger())
|
||||||
versionName "0.1.5"
|
versionName "1.0.0-beta03"
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
//applicationIdSuffix ".beta"
|
applicationIdSuffix ".beta"
|
||||||
debuggable true
|
debuggable true
|
||||||
versionNameSuffix "." + gitCommitHash
|
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
debuggable false
|
debuggable false
|
||||||
@@ -62,9 +61,10 @@ dependencies {
|
|||||||
implementation "androidx.work:work-runtime-ktx:2.8.1"
|
implementation "androidx.work:work-runtime-ktx:2.8.1"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.9'
|
||||||
implementation 'com.github.Blatzar:NiceHttp:0.4.3'
|
implementation 'com.github.Blatzar:NiceHttp:0.4.3'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0'
|
||||||
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
ext.glide_version = '4.16.0'
|
ext.glide_version = '4.16.0'
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -221,6 +221,7 @@
|
|||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".download.DownloadContainerActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity"
|
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity"
|
||||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
@@ -263,10 +264,16 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallService"
|
<service android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallService"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service android:name="eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallService"
|
<service android:name="eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallService"
|
||||||
|
android:foregroundServiceType="dataSync"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<service android:name=".download.manga.MangaDownloaderService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="dataSync" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
BIN
app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -11,16 +11,28 @@ import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
|
|||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import ani.dantotsu.others.DisabledReports
|
import ani.dantotsu.others.DisabledReports
|
||||||
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import com.google.android.material.color.DynamicColors
|
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 eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import logcat.AndroidLogcatLogger
|
import logcat.AndroidLogcatLogger
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import logcat.LogcatLogger
|
import logcat.LogcatLogger
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
class App : MultiDexApplication() {
|
class App : MultiDexApplication() {
|
||||||
|
private lateinit var animeExtensionManager: AnimeExtensionManager
|
||||||
|
private lateinit var mangaExtensionManager: MangaExtensionManager
|
||||||
override fun attachBaseContext(base: Context?) {
|
override fun attachBaseContext(base: Context?) {
|
||||||
super.attachBaseContext(base)
|
super.attachBaseContext(base)
|
||||||
MultiDex.install(this)
|
MultiDex.install(this)
|
||||||
@@ -52,6 +64,22 @@ class App : MultiDexApplication() {
|
|||||||
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
animeExtensionManager = Injekt.get()
|
||||||
|
mangaExtensionManager = Injekt.get()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupNotificationChannels() {
|
private fun setupNotificationChannels() {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -204,23 +207,21 @@ open class BottomSheetDialogFragment : BottomSheetDialogFragment() {
|
|||||||
fun isOnline(context: Context): Boolean {
|
fun isOnline(context: Context): Boolean {
|
||||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
return tryWith {
|
return tryWith {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val cap = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||||
val cap = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
return@tryWith if (cap != null) {
|
||||||
return@tryWith if (cap != null) {
|
when {
|
||||||
when {
|
cap.hasTransport(TRANSPORT_BLUETOOTH) ||
|
||||||
cap.hasTransport(TRANSPORT_BLUETOOTH) ||
|
cap.hasTransport(TRANSPORT_CELLULAR) ||
|
||||||
cap.hasTransport(TRANSPORT_CELLULAR) ||
|
cap.hasTransport(TRANSPORT_ETHERNET) ||
|
||||||
cap.hasTransport(TRANSPORT_ETHERNET) ||
|
cap.hasTransport(TRANSPORT_LOWPAN) ||
|
||||||
cap.hasTransport(TRANSPORT_LOWPAN) ||
|
cap.hasTransport(TRANSPORT_USB) ||
|
||||||
cap.hasTransport(TRANSPORT_USB) ||
|
cap.hasTransport(TRANSPORT_VPN) ||
|
||||||
cap.hasTransport(TRANSPORT_VPN) ||
|
cap.hasTransport(TRANSPORT_WIFI) ||
|
||||||
cap.hasTransport(TRANSPORT_WIFI) ||
|
cap.hasTransport(TRANSPORT_WIFI_AWARE) -> true
|
||||||
cap.hasTransport(TRANSPORT_WIFI_AWARE) -> true
|
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} else false
|
} else false
|
||||||
} else true
|
|
||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +420,13 @@ fun String.findBetween(a: String, b: String): String? {
|
|||||||
|
|
||||||
fun ImageView.loadImage(url: String?, size: Int = 0) {
|
fun ImageView.loadImage(url: String?, size: Int = 0) {
|
||||||
if (!url.isNullOrEmpty()) {
|
if (!url.isNullOrEmpty()) {
|
||||||
loadImage(FileUrl(url), size)
|
val localFile = File(url)
|
||||||
|
if (localFile.exists()) {
|
||||||
|
loadLocalImage(localFile, size)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadImage(FileUrl(url), size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,6 +439,14 @@ fun ImageView.loadImage(file: FileUrl?, size: Int = 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ImageView.loadLocalImage(file: File?, size: Int = 0) {
|
||||||
|
if (file?.exists() == true) {
|
||||||
|
tryWith {
|
||||||
|
Glide.with(this.context).load(file).transition(withCrossFade()).override(size).into(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SafeClickListener(
|
class SafeClickListener(
|
||||||
private var defaultInterval: Int = 1000,
|
private var defaultInterval: Int = 1000,
|
||||||
private val onSafeCLick: (View) -> Unit
|
private val onSafeCLick: (View) -> Unit
|
||||||
@@ -729,7 +744,7 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul
|
|||||||
if (s != null) {
|
if (s != null) {
|
||||||
(activity ?: currActivity())?.apply {
|
(activity ?: currActivity())?.apply {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
val snackBar = Snackbar.make(window.decorView.findViewById(android.R.id.content), s, Snackbar.LENGTH_LONG)
|
val snackBar = Snackbar.make(window.decorView.findViewById(android.R.id.content), s, Snackbar.LENGTH_SHORT)
|
||||||
snackBar.view.apply {
|
snackBar.view.apply {
|
||||||
updateLayoutParams<FrameLayout.LayoutParams> {
|
updateLayoutParams<FrameLayout.LayoutParams> {
|
||||||
gravity = (Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM)
|
gravity = (Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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.FrameLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
@@ -40,7 +41,9 @@ 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
|
||||||
|
import ani.dantotsu.databinding.ItemNavbarBinding
|
||||||
import ani.dantotsu.databinding.SplashScreenBinding
|
import ani.dantotsu.databinding.SplashScreenBinding
|
||||||
|
import ani.dantotsu.download.manga.OfflineMangaFragment
|
||||||
import ani.dantotsu.home.AnimeFragment
|
import ani.dantotsu.home.AnimeFragment
|
||||||
import ani.dantotsu.home.HomeFragment
|
import ani.dantotsu.home.HomeFragment
|
||||||
import ani.dantotsu.home.LoginFragment
|
import ani.dantotsu.home.LoginFragment
|
||||||
@@ -54,6 +57,8 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
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
|
||||||
@@ -76,39 +81,32 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private var load = false
|
private var load = false
|
||||||
|
|
||||||
private var uiSettings = UserInterfaceSettings()
|
private var uiSettings = UserInterfaceSettings()
|
||||||
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)
|
||||||
|
LangSet.setLocale(this)
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
val _bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
val bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
|
||||||
val backgroundDrawable = bottomBar.background as GradientDrawable
|
val backgroundDrawable = _bottomBar.background as GradientDrawable
|
||||||
val currentColor = backgroundDrawable.color?.defaultColor ?: 0
|
val currentColor = backgroundDrawable.color?.defaultColor ?: 0
|
||||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0x80000000.toInt()
|
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xE8000000.toInt()
|
||||||
backgroundDrawable.setColor(semiTransparentColor)
|
backgroundDrawable.setColor(semiTransparentColor)
|
||||||
bottomBar.background = backgroundDrawable
|
_bottomBar.background = backgroundDrawable
|
||||||
|
}
|
||||||
|
val colorOverflow = this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("colorOverflow", false)
|
||||||
|
if (!colorOverflow) {
|
||||||
|
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
onBackPressedDispatcher.addCallback(this) {
|
onBackPressedDispatcher.addCallback(this) {
|
||||||
if (doubleBackToExitPressedOnce) {
|
if (doubleBackToExitPressedOnce) {
|
||||||
@@ -125,24 +123,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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +165,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
initActivity(this)
|
initActivity(this)
|
||||||
uiSettings = loadData("ui_settings") ?: uiSettings
|
uiSettings = loadData("ui_settings") ?: uiSettings
|
||||||
selectedOption = uiSettings.defaultStartUpTab
|
selectedOption = uiSettings.defaultStartUpTab
|
||||||
binding.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = navBarHeight
|
bottomMargin = navBarHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +178,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
model.genres.observe(this) {
|
model.genres.observe(this) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
if (it) {
|
if (it) {
|
||||||
val navbar = binding.navbar
|
val navbar = binding.includedNavbar.navbar
|
||||||
bottomBar = navbar
|
bottomBar = navbar
|
||||||
navbar.visibility = View.VISIBLE
|
navbar.visibility = View.VISIBLE
|
||||||
binding.mainProgressBar.visibility = View.GONE
|
binding.mainProgressBar.visibility = View.GONE
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ani.dantotsu.aniyomi.anime.custom
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import ani.dantotsu.media.manga.MangaCache
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
@@ -12,23 +13,32 @@ import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
|
|||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
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 eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
|
import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager
|
||||||
|
import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||||
|
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
import uy.kohesive.injekt.api.addSingleton
|
import uy.kohesive.injekt.api.addSingleton
|
||||||
import uy.kohesive.injekt.api.addSingletonFactory
|
import uy.kohesive.injekt.api.addSingletonFactory
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
|
||||||
class AppModule(val app: Application) : InjektModule {
|
class AppModule(val app: Application) : InjektModule {
|
||||||
override fun InjektRegistrar.registerInjectables() {
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
addSingleton(app)
|
addSingleton(app)
|
||||||
|
|
||||||
|
addSingletonFactory { DownloadsManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { NetworkHelper(app, get()) }
|
addSingletonFactory { NetworkHelper(app, get()) }
|
||||||
|
|
||||||
addSingletonFactory { AnimeExtensionManager(app) }
|
addSingletonFactory { AnimeExtensionManager(app) }
|
||||||
|
|
||||||
addSingletonFactory { MangaExtensionManager(app) }
|
addSingletonFactory { MangaExtensionManager(app) }
|
||||||
|
|
||||||
|
addSingletonFactory<AnimeSourceManager> { AndroidAnimeSourceManager(app, get()) }
|
||||||
|
addSingletonFactory<MangaSourceManager> { AndroidMangaSourceManager(app, get()) }
|
||||||
|
|
||||||
val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
addSingleton(sharedPreferences)
|
addSingleton(sharedPreferences)
|
||||||
|
|
||||||
@@ -40,6 +50,11 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addSingletonFactory { MangaCache() }
|
addSingletonFactory { MangaCache() }
|
||||||
|
|
||||||
|
ContextCompat.getMainExecutor(app).execute {
|
||||||
|
get<AnimeSourceManager>()
|
||||||
|
get<MangaSourceManager>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ 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
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
|
||||||
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()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
val data: Uri? = intent?.data
|
val data: Uri? = intent?.data
|
||||||
logger(data.toString())
|
logger(data.toString())
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ 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
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
|
||||||
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()
|
LangSet.setLocale(this)
|
||||||
|
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
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ 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
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
|
||||||
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()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
val process = getProcessName()
|
val process = getProcessName()
|
||||||
if (packageName != process) WebView.setDataDirectorySuffix(process)
|
if (packageName != process) WebView.setDataDirectorySuffix(process)
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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()
|
LangSet.setLocale(this)
|
||||||
|
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))
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ani.dantotsu.download
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
|
||||||
|
class DownloadContainerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
setContentView(R.layout.activity_container)
|
||||||
|
|
||||||
|
val fragmentClassName = intent.getStringExtra("FRAGMENT_CLASS_NAME")
|
||||||
|
val fragment = Class.forName(fragmentClassName).newInstance() as Fragment
|
||||||
|
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.fragment_container, fragment)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
115
app/src/main/java/ani/dantotsu/download/DownloadsManager.kt
Normal file
115
app/src/main/java/ani/dantotsu/download/DownloadsManager.kt
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package ani.dantotsu.download
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Environment
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import java.io.File
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
class DownloadsManager(private val context: Context) {
|
||||||
|
private val prefs: SharedPreferences = context.getSharedPreferences("downloads_pref", Context.MODE_PRIVATE)
|
||||||
|
private val gson = Gson()
|
||||||
|
private val downloadsList = loadDownloads().toMutableList()
|
||||||
|
|
||||||
|
val mangaDownloads: List<Download>
|
||||||
|
get() = downloadsList.filter { it.type == Download.Type.MANGA }
|
||||||
|
val animeDownloads: List<Download>
|
||||||
|
get() = downloadsList.filter { it.type == Download.Type.ANIME }
|
||||||
|
|
||||||
|
private fun saveDownloads() {
|
||||||
|
val jsonString = gson.toJson(downloadsList)
|
||||||
|
prefs.edit().putString("downloads_key", jsonString).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadDownloads(): List<Download> {
|
||||||
|
val jsonString = prefs.getString("downloads_key", null)
|
||||||
|
return if (jsonString != null) {
|
||||||
|
val type = object : TypeToken<List<Download>>() {}.type
|
||||||
|
gson.fromJson(jsonString, type)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDownload(download: Download) {
|
||||||
|
downloadsList.add(download)
|
||||||
|
saveDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeDownload(download: Download) {
|
||||||
|
downloadsList.remove(download)
|
||||||
|
removeDirectory(download)
|
||||||
|
saveDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeDirectory(download: Download) {
|
||||||
|
val directory = if (download.type == Download.Type.MANGA){
|
||||||
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga/${download.title}/${download.chapter}")
|
||||||
|
} else {
|
||||||
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime/${download.title}/${download.chapter}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the directory exists and delete it recursively
|
||||||
|
if (directory.exists()) {
|
||||||
|
val deleted = directory.deleteRecursively()
|
||||||
|
if (deleted) {
|
||||||
|
Toast.makeText(context, "Successfully deleted", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Failed to delete directory", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Directory does not exist", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportDownloads(download: Download) { //copies to the downloads folder available to the user
|
||||||
|
val directory = if (download.type == Download.Type.MANGA){
|
||||||
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga/${download.title}/${download.chapter}")
|
||||||
|
} else {
|
||||||
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime/${download.title}/${download.chapter}")
|
||||||
|
}
|
||||||
|
val destination = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/${download.title}/${download.chapter}")
|
||||||
|
if (directory.exists()) {
|
||||||
|
val copied = directory.copyRecursively(destination, true)
|
||||||
|
if (copied) {
|
||||||
|
Toast.makeText(context, "Successfully copied", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Failed to copy directory", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Directory does not exist", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun purgeDownloads(type: Download.Type){
|
||||||
|
val directory = if (type == Download.Type.MANGA){
|
||||||
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga")
|
||||||
|
} else {
|
||||||
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime")
|
||||||
|
}
|
||||||
|
if (directory.exists()) {
|
||||||
|
val deleted = directory.deleteRecursively()
|
||||||
|
if (deleted) {
|
||||||
|
Toast.makeText(context, "Successfully deleted", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Failed to delete directory", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Directory does not exist", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadsList.removeAll { it.type == type }
|
||||||
|
saveDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Download(val title: String, val chapter: String, val type: Type) : Serializable {
|
||||||
|
enum class Type {
|
||||||
|
MANGA,
|
||||||
|
ANIME
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
package ani.dantotsu.download.manga
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.download.Download
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.manga.ImageData
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED
|
||||||
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FINISHED
|
||||||
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROGRESS
|
||||||
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STARTED
|
||||||
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.InstanceCreator
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import java.util.Queue
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
class MangaDownloaderService : Service() {
|
||||||
|
|
||||||
|
private lateinit var notificationManager: NotificationManagerCompat
|
||||||
|
private lateinit var builder: NotificationCompat.Builder
|
||||||
|
private val downloadsManager: DownloadsManager = Injekt.get<DownloadsManager>()
|
||||||
|
|
||||||
|
private val downloadJobs = mutableMapOf<String, Job>()
|
||||||
|
private val mutex = Mutex()
|
||||||
|
private var isCurrentlyProcessing = false
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
|
// This is only required for bound services.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
notificationManager = NotificationManagerCompat.from(this)
|
||||||
|
builder = NotificationCompat.Builder(this, CHANNEL_DOWNLOADER_PROGRESS).apply {
|
||||||
|
setContentTitle("Manga Download Progress")
|
||||||
|
setSmallIcon(R.drawable.ic_round_download_24)
|
||||||
|
priority = NotificationCompat.PRIORITY_DEFAULT
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
setProgress(0, 0, false)
|
||||||
|
}
|
||||||
|
startForeground(NOTIFICATION_ID, builder.build())
|
||||||
|
ContextCompat.registerReceiver(this, cancelReceiver, IntentFilter(ACTION_CANCEL_DOWNLOAD), ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
ServiceDataSingleton.downloadQueue.clear()
|
||||||
|
downloadJobs.clear()
|
||||||
|
ServiceDataSingleton.isServiceRunning = false
|
||||||
|
unregisterReceiver(cancelReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
snackString("Download started")
|
||||||
|
val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
|
serviceScope.launch {
|
||||||
|
mutex.withLock {
|
||||||
|
if (!isCurrentlyProcessing) {
|
||||||
|
isCurrentlyProcessing = true
|
||||||
|
processQueue()
|
||||||
|
isCurrentlyProcessing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processQueue() {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
while (ServiceDataSingleton.downloadQueue.isNotEmpty()) {
|
||||||
|
val task = ServiceDataSingleton.downloadQueue.poll()
|
||||||
|
if (task != null) {
|
||||||
|
val job = launch { download(task) }
|
||||||
|
mutex.withLock {
|
||||||
|
downloadJobs[task.chapter] = job
|
||||||
|
}
|
||||||
|
job.join() // Wait for the job to complete before continuing to the next task
|
||||||
|
mutex.withLock {
|
||||||
|
downloadJobs.remove(task.chapter)
|
||||||
|
}
|
||||||
|
updateNotification() // Update the notification after each task is completed
|
||||||
|
}
|
||||||
|
if (ServiceDataSingleton.downloadQueue.isEmpty()) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
stopSelf() // Stop the service when the queue is empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelDownload(chapter: String) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
mutex.withLock {
|
||||||
|
downloadJobs[chapter]?.cancel()
|
||||||
|
downloadJobs.remove(chapter)
|
||||||
|
ServiceDataSingleton.downloadQueue.removeAll { it.chapter == chapter }
|
||||||
|
updateNotification() // Update the notification after cancellation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNotification() {
|
||||||
|
// Update the notification to reflect the current state of the queue
|
||||||
|
val pendingDownloads = ServiceDataSingleton.downloadQueue.size
|
||||||
|
val text = if (pendingDownloads > 0) {
|
||||||
|
"Pending downloads: $pendingDownloads"
|
||||||
|
} else {
|
||||||
|
"All downloads completed"
|
||||||
|
}
|
||||||
|
builder.setContentText(text)
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun download(task: DownloadTask) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ContextCompat.checkSelfPermission(
|
||||||
|
this@MangaDownloaderService,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val deferredList = mutableListOf<Deferred<Bitmap?>>()
|
||||||
|
builder.setContentText("Downloading ${task.title} - ${task.chapter}")
|
||||||
|
if (notifi) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through each ImageData object from the task
|
||||||
|
var farthest = 0
|
||||||
|
for ((index, image) in task.imageData.withIndex()) {
|
||||||
|
// Limit the number of simultaneous downloads from the task
|
||||||
|
if (deferredList.size >= task.simultaneousDownloads) {
|
||||||
|
// Wait for all deferred to complete and clear the list
|
||||||
|
deferredList.awaitAll()
|
||||||
|
deferredList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download the image and add to deferred list
|
||||||
|
val deferred = async(Dispatchers.IO) {
|
||||||
|
var bitmap: Bitmap? = null
|
||||||
|
var retryCount = 0
|
||||||
|
|
||||||
|
while (bitmap == null && retryCount < task.retries) {
|
||||||
|
bitmap = image.fetchAndProcessImage(
|
||||||
|
image.page,
|
||||||
|
image.source,
|
||||||
|
this@MangaDownloaderService
|
||||||
|
)
|
||||||
|
retryCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the image if successful
|
||||||
|
if (bitmap != null) {
|
||||||
|
saveToDisk("$index.jpg", bitmap, task.title, task.chapter)
|
||||||
|
}
|
||||||
|
farthest++
|
||||||
|
builder.setProgress(task.imageData.size, farthest, false)
|
||||||
|
broadcastDownloadProgress(task.chapter, farthest * 100 / task.imageData.size)
|
||||||
|
if (notifi) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
deferredList.add(deferred)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for any remaining deferred to complete
|
||||||
|
deferredList.awaitAll()
|
||||||
|
|
||||||
|
builder.setContentText("${task.title} - ${task.chapter} Download complete")
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
|
||||||
|
saveMediaInfo(task)
|
||||||
|
downloadsManager.addDownload(Download(task.title, task.chapter, Download.Type.MANGA))
|
||||||
|
broadcastDownloadFinished(task.chapter)
|
||||||
|
snackString("${task.title} - ${task.chapter} Download finished")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun saveToDisk(fileName: String, bitmap: Bitmap, title: String, chapter: String) {
|
||||||
|
try {
|
||||||
|
// Define the directory within the private external storage space
|
||||||
|
val directory = File(
|
||||||
|
this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/$title/$chapter"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!directory.exists()) {
|
||||||
|
directory.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a file reference within that directory for your image
|
||||||
|
val file = File(directory, fileName)
|
||||||
|
|
||||||
|
// Use a FileOutputStream to write the bitmap to the file
|
||||||
|
FileOutputStream(file).use { outputStream ->
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Exception while saving image: ${e.message}")
|
||||||
|
snackString("Exception while saving image: ${e.message}")
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMediaInfo(task: DownloadTask) {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val directory = File(
|
||||||
|
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/${task.title}"
|
||||||
|
)
|
||||||
|
if (!directory.exists()) directory.mkdirs()
|
||||||
|
|
||||||
|
val file = File(directory, "media.json")
|
||||||
|
val gson = GsonBuilder()
|
||||||
|
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||||
|
SChapterImpl() // Provide an instance of SChapterImpl
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
val mediaJson = gson.toJson(task.sourceMedia)
|
||||||
|
val media = gson.fromJson(mediaJson, Media::class.java)
|
||||||
|
if (media != null) {
|
||||||
|
media.cover = media.cover?.let { downloadImage(it, directory, "cover.jpg") }
|
||||||
|
media.banner = media.banner?.let { downloadImage(it, directory, "banner.jpg") }
|
||||||
|
|
||||||
|
val jsonString = gson.toJson(media)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
file.writeText(jsonString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private suspend fun downloadImage(url: String, directory: File, name: String): String? = withContext(Dispatchers.IO) {
|
||||||
|
var connection: HttpURLConnection? = null
|
||||||
|
println("Downloading url $url")
|
||||||
|
try {
|
||||||
|
connection = URL(url).openConnection() as HttpURLConnection
|
||||||
|
connection.connect()
|
||||||
|
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
throw Exception("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(directory, name)
|
||||||
|
FileOutputStream(file).use { output ->
|
||||||
|
connection.inputStream.use { input ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@withContext file.absolutePath
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(this@MangaDownloaderService, "Exception while saving ${name}: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
null
|
||||||
|
} finally {
|
||||||
|
connection?.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadStarted(chapterNumber: String) {
|
||||||
|
val intent = Intent(ACTION_DOWNLOAD_STARTED).apply {
|
||||||
|
putExtra(EXTRA_CHAPTER_NUMBER, chapterNumber)
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadFinished(chapterNumber: String) {
|
||||||
|
val intent = Intent(ACTION_DOWNLOAD_FINISHED).apply {
|
||||||
|
putExtra(EXTRA_CHAPTER_NUMBER, chapterNumber)
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadFailed(chapterNumber: String) {
|
||||||
|
val intent = Intent(ACTION_DOWNLOAD_FAILED).apply {
|
||||||
|
putExtra(EXTRA_CHAPTER_NUMBER, chapterNumber)
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadProgress(chapterNumber: String, progress: Int) {
|
||||||
|
val intent = Intent(ACTION_DOWNLOAD_PROGRESS).apply {
|
||||||
|
putExtra(EXTRA_CHAPTER_NUMBER, chapterNumber)
|
||||||
|
putExtra("progress", progress)
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cancelReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action == ACTION_CANCEL_DOWNLOAD) {
|
||||||
|
val chapter = intent.getStringExtra(EXTRA_CHAPTER)
|
||||||
|
chapter?.let {
|
||||||
|
cancelDownload(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class DownloadTask(
|
||||||
|
val title: String,
|
||||||
|
val chapter: String,
|
||||||
|
val imageData: List<ImageData>,
|
||||||
|
val sourceMedia: Media? = null,
|
||||||
|
val retries: Int = 2,
|
||||||
|
val simultaneousDownloads: Int = 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val NOTIFICATION_ID = 1103
|
||||||
|
const val ACTION_CANCEL_DOWNLOAD = "action_cancel_download"
|
||||||
|
const val EXTRA_CHAPTER = "extra_chapter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ServiceDataSingleton {
|
||||||
|
var imageData: List<ImageData> = listOf()
|
||||||
|
var sourceMedia: Media? = null
|
||||||
|
var downloadQueue: Queue<MangaDownloaderService.DownloadTask> = ConcurrentLinkedQueue()
|
||||||
|
@Volatile
|
||||||
|
var isServiceRunning: Boolean = false
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package ani.dantotsu.download.manga
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.BaseAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
|
import ani.dantotsu.R
|
||||||
|
|
||||||
|
|
||||||
|
class OfflineMangaAdapter(private val context: Context, private val items: List<OfflineMangaModel>) : BaseAdapter() {
|
||||||
|
private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return items.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItem(position: Int): Any {
|
||||||
|
return items[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return position.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||||
|
var view = convertView
|
||||||
|
if (view == null) {
|
||||||
|
view = inflater.inflate(R.layout.item_media_compact, parent, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val item = getItem(position) as OfflineMangaModel
|
||||||
|
val imageView = view!!.findViewById<ImageView>(R.id.itemCompactImage)
|
||||||
|
val titleTextView = view.findViewById<TextView>(R.id.itemCompactTitle)
|
||||||
|
val itemScore = view.findViewById<TextView>(R.id.itemCompactScore)
|
||||||
|
val itemScoreBG = view.findViewById<View>(R.id.itemCompactScoreBG)
|
||||||
|
val ongoing = view.findViewById<CardView>(R.id.itemCompactOngoing)
|
||||||
|
// Bind item data to the views
|
||||||
|
// For example:
|
||||||
|
imageView.setImageURI(item.image)
|
||||||
|
titleTextView.text = item.title
|
||||||
|
itemScore.text = item.score
|
||||||
|
if (item.isOngoing) {
|
||||||
|
ongoing.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
ongoing.visibility = View.GONE
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
package ani.dantotsu.download.manga
|
||||||
|
|
||||||
|
import android.animation.ObjectAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.OvershootInterpolator
|
||||||
|
import android.widget.GridView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.core.view.updatePaddingRelative
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.Refresh
|
||||||
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.databinding.FragmentMangaBinding
|
||||||
|
import ani.dantotsu.download.Download
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.logger
|
||||||
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
|
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.px
|
||||||
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
|
import ani.dantotsu.settings.SettingsDialogFragment
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import com.google.android.material.imageview.ShapeableImageView
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.InstanceCreator
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class OfflineMangaFragment: Fragment() {
|
||||||
|
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||||
|
private var downloads: List<OfflineMangaModel> = listOf()
|
||||||
|
private lateinit var gridView: GridView
|
||||||
|
private lateinit var adapter: OfflineMangaAdapter
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = inflater.inflate(R.layout.fragment_manga_offline, container, false)
|
||||||
|
|
||||||
|
val textInputLayout = view.findViewById<TextInputLayout>(R.id.offlineMangaSearchBar)
|
||||||
|
val currentColor = textInputLayout.boxBackgroundColor
|
||||||
|
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
||||||
|
textInputLayout.boxBackgroundColor = semiTransparentColor
|
||||||
|
val materialCardView = view.findViewById<MaterialCardView>(R.id.offlineMangaAvatarContainer)
|
||||||
|
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
requireContext().theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||||
|
val color = typedValue.data
|
||||||
|
|
||||||
|
val animeUserAvatar= view.findViewById<ShapeableImageView>(R.id.offlineMangaUserAvatar)
|
||||||
|
animeUserAvatar.setSafeOnClickListener {
|
||||||
|
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.HOME).show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean("colorOverflow", false) ?: false
|
||||||
|
if (!colorOverflow) {
|
||||||
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||||
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
gridView = view.findViewById(R.id.gridView)
|
||||||
|
getDownloads()
|
||||||
|
adapter = OfflineMangaAdapter(requireContext(), downloads)
|
||||||
|
gridView.adapter = adapter
|
||||||
|
gridView.setOnItemClickListener { parent, view, position, id ->
|
||||||
|
// Get the OfflineMangaModel that was clicked
|
||||||
|
val item = adapter.getItem(position) as OfflineMangaModel
|
||||||
|
val media = downloadManager.mangaDownloads.filter { it.title == item.title }.first()
|
||||||
|
startActivity(
|
||||||
|
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||||
|
.putExtra("media", getMedia(media))
|
||||||
|
.putExtra("download", true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
var height = statusBarHeight
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
val displayCutout = activity?.window?.decorView?.rootWindowInsets?.displayCutout
|
||||||
|
if (displayCutout != null) {
|
||||||
|
if (displayCutout.boundingRects.size > 0) {
|
||||||
|
height = max(
|
||||||
|
statusBarHeight,
|
||||||
|
min(
|
||||||
|
displayCutout.boundingRects[0].width(),
|
||||||
|
displayCutout.boundingRects[0].height()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
|
||||||
|
var visible = false
|
||||||
|
fun animate() {
|
||||||
|
val start = if (visible) 0f else 1f
|
||||||
|
val end = if (!visible) 0f else 1f
|
||||||
|
ObjectAnimator.ofFloat(scrollTop, "scaleX", start, end).apply {
|
||||||
|
duration = 300
|
||||||
|
interpolator = OvershootInterpolator(2f)
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
ObjectAnimator.ofFloat(scrollTop, "scaleY", start, end).apply {
|
||||||
|
duration = 300
|
||||||
|
interpolator = OvershootInterpolator(2f)
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollTop.setOnClickListener {
|
||||||
|
//TODO: scroll to top
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
getDownloads()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
downloads = listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
downloads = listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
downloads = listOf()
|
||||||
|
}
|
||||||
|
private fun getDownloads() {
|
||||||
|
val titles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
||||||
|
val newDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
|
for (title in titles) {
|
||||||
|
val _downloads = downloadManager.mangaDownloads.filter { it.title == title }
|
||||||
|
val download = _downloads.first()
|
||||||
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
|
newDownloads += offlineMangaModel
|
||||||
|
}
|
||||||
|
downloads = newDownloads
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMedia(download: Download): Media? {
|
||||||
|
val directory = File(
|
||||||
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/${download.title}"
|
||||||
|
)
|
||||||
|
//load media.json and convert to media class with gson
|
||||||
|
try {
|
||||||
|
val gson = GsonBuilder()
|
||||||
|
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||||
|
SChapterImpl() // Provide an instance of SChapterImpl
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
val media = File(directory, "media.json")
|
||||||
|
val mediaJson = media.readText()
|
||||||
|
return gson.fromJson(mediaJson, Media::class.java)
|
||||||
|
}
|
||||||
|
catch (e: Exception){
|
||||||
|
logger("Error loading media.json: ${e.message}")
|
||||||
|
logger(e.printStackTrace())
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadOfflineMangaModel(download: Download): OfflineMangaModel{
|
||||||
|
val directory = File(
|
||||||
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/${download.title}"
|
||||||
|
)
|
||||||
|
//load media.json and convert to media class with gson
|
||||||
|
try {
|
||||||
|
val media = File(directory, "media.json")
|
||||||
|
val mediaJson = media.readText()
|
||||||
|
val mediaModel = getMedia(download)!!
|
||||||
|
val cover = File(directory, "cover.jpg")
|
||||||
|
val coverUri: Uri? = if (cover.exists()) {
|
||||||
|
Uri.fromFile(cover)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val title = mediaModel.nameMAL?:"unknown"
|
||||||
|
val score = if (mediaModel.userScore != 0) mediaModel.userScore.toString() else
|
||||||
|
if (mediaModel.meanScore == null) "?" else mediaModel.meanScore.toString()
|
||||||
|
val isOngoing = false
|
||||||
|
val isUserScored = mediaModel.userScore != 0
|
||||||
|
return OfflineMangaModel(title, score, isOngoing, isUserScored, coverUri)
|
||||||
|
}
|
||||||
|
catch (e: Exception){
|
||||||
|
logger("Error loading media.json: ${e.message}")
|
||||||
|
logger(e.printStackTrace())
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
return OfflineMangaModel("unknown", "0", false, false, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ani.dantotsu.download.manga
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
|
data class OfflineMangaModel(val title: String, val score: String, val isOngoing: Boolean, val isUserScored: Boolean, val image: Uri?) {
|
||||||
|
}
|
||||||
@@ -28,6 +28,9 @@ import ani.dantotsu.parsers.Subtitle
|
|||||||
import ani.dantotsu.parsers.SubtitleType
|
import ani.dantotsu.parsers.SubtitleType
|
||||||
import ani.dantotsu.parsers.Video
|
import ani.dantotsu.parsers.Video
|
||||||
import ani.dantotsu.parsers.VideoType
|
import ani.dantotsu.parsers.VideoType
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
@@ -118,6 +121,9 @@ object Helper {
|
|||||||
val database = StandaloneDatabaseProvider(context)
|
val database = StandaloneDatabaseProvider(context)
|
||||||
val downloadDirectory = File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY)
|
val downloadDirectory = File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY)
|
||||||
val dataSourceFactory = DataSource.Factory {
|
val dataSourceFactory = DataSource.Factory {
|
||||||
|
//val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
||||||
|
val networkHelper = Injekt.get<NetworkHelper>()
|
||||||
|
val okHttpClient = networkHelper.client
|
||||||
val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
||||||
defaultHeaders.forEach {
|
defaultHeaders.forEach {
|
||||||
dataSource.setRequestProperty(it.key, it.value)
|
dataSource.setRequestProperty(it.key, it.value)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class MyDownloadService : DownloadService(1, 1, "download_service", R.string.dow
|
|||||||
override fun getForegroundNotification(downloads: MutableList<Download>, notMetRequirements: Int): Notification =
|
override fun getForegroundNotification(downloads: MutableList<Download>, notMetRequirements: Int): Notification =
|
||||||
DownloadNotificationHelper(this, "download_service").buildProgressNotification(
|
DownloadNotificationHelper(this, "download_service").buildProgressNotification(
|
||||||
this,
|
this,
|
||||||
R.drawable.monochrome,
|
R.drawable.mono,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
downloads,
|
downloads,
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
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
|
||||||
@@ -19,6 +22,7 @@ import ani.dantotsu.media.GenreActivity
|
|||||||
import ani.dantotsu.MediaPageTransformer
|
import ani.dantotsu.MediaPageTransformer
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.ItemAnimePageBinding
|
import ani.dantotsu.databinding.ItemAnimePageBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
@@ -54,10 +58,20 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
|
|
||||||
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.animeSearchBar)
|
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.animeSearchBar)
|
||||||
val currentColor = textInputLayout.boxBackgroundColor
|
val currentColor = textInputLayout.boxBackgroundColor
|
||||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0x80000000.toInt()
|
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
||||||
textInputLayout.boxBackgroundColor = semiTransparentColor
|
textInputLayout.boxBackgroundColor = semiTransparentColor
|
||||||
val materialCardView = holder.itemView.findViewById<MaterialCardView>(R.id.animeUserAvatarContainer)
|
val materialCardView = holder.itemView.findViewById<MaterialCardView>(R.id.animeUserAvatarContainer)
|
||||||
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||||
|
val color = typedValue.data
|
||||||
|
|
||||||
|
|
||||||
|
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean("colorOverflow", false) ?: false
|
||||||
|
if (!colorOverflow) {
|
||||||
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||||
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
binding.animeTitleContainer.updatePadding(top = statusBarHeight)
|
binding.animeTitleContainer.updatePadding(top = statusBarHeight)
|
||||||
|
|
||||||
@@ -81,7 +95,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.animeUserAvatar.setSafeOnClickListener {
|
binding.animeUserAvatar.setSafeOnClickListener {
|
||||||
SettingsDialogFragment().show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.ANIME).show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class HomeFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.homeUserAvatarContainer.setSafeOnClickListener {
|
binding.homeUserAvatarContainer.setSafeOnClickListener {
|
||||||
SettingsDialogFragment().show(parentFragmentManager, "dialog")
|
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.HOME).show(parentFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.homeContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.homeContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
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
|
||||||
@@ -19,6 +21,7 @@ import ani.dantotsu.media.GenreActivity
|
|||||||
import ani.dantotsu.MediaPageTransformer
|
import ani.dantotsu.MediaPageTransformer
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.ItemMangaPageBinding
|
import ani.dantotsu.databinding.ItemMangaPageBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
@@ -53,10 +56,20 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
|
|
||||||
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.mangaSearchBar)
|
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.mangaSearchBar)
|
||||||
val currentColor = textInputLayout.boxBackgroundColor
|
val currentColor = textInputLayout.boxBackgroundColor
|
||||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0x80000000.toInt()
|
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
||||||
textInputLayout.boxBackgroundColor = semiTransparentColor
|
textInputLayout.boxBackgroundColor = semiTransparentColor
|
||||||
val materialCardView = holder.itemView.findViewById<MaterialCardView>(R.id.mangaUserAvatarContainer)
|
val materialCardView = holder.itemView.findViewById<MaterialCardView>(R.id.mangaUserAvatarContainer)
|
||||||
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||||
|
val color = typedValue.data
|
||||||
|
|
||||||
|
|
||||||
|
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean("colorOverflow", false) ?: false
|
||||||
|
if (!colorOverflow) {
|
||||||
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||||
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
binding.mangaTitleContainer.updatePadding(top = statusBarHeight)
|
binding.mangaTitleContainer.updatePadding(top = statusBarHeight)
|
||||||
|
|
||||||
@@ -76,7 +89,7 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.mangaUserAvatar.setSafeOnClickListener {
|
binding.mangaUserAvatar.setSafeOnClickListener {
|
||||||
SettingsDialogFragment().show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.MANGA).show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.mangaSearchBar.setEndIconOnClickListener {
|
binding.mangaSearchBar.setEndIconOnClickListener {
|
||||||
|
|||||||
@@ -1,32 +1,111 @@
|
|||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.doOnAttach
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.ZoomOutPageTransformer
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.databinding.ActivityNoInternetBinding
|
import ani.dantotsu.databinding.ActivityNoInternetBinding
|
||||||
|
import ani.dantotsu.download.manga.OfflineMangaFragment
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.isOnline
|
import ani.dantotsu.isOnline
|
||||||
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.offline.OfflineFragment
|
||||||
|
import ani.dantotsu.selectedOption
|
||||||
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
|
|
||||||
class NoInternet : AppCompatActivity() {
|
class NoInternet : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityNoInternetBinding
|
||||||
|
lateinit var bottomBar: AnimatedBottomBar
|
||||||
|
private var uiSettings = UserInterfaceSettings()
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
val binding = ActivityNoInternetBinding.inflate(layoutInflater)
|
binding = ActivityNoInternetBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
binding.refreshContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
val _bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
||||||
topMargin = statusBarHeight
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
bottomMargin = navBarHeight
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
binding.refreshButton.setOnClickListener {
|
val colorOverflow = this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
if (isOnline(this)) {
|
.getBoolean("colorOverflow", false)
|
||||||
startMainActivity(this)
|
if (!colorOverflow) {
|
||||||
|
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.root.doOnAttach {
|
||||||
|
initActivity(this)
|
||||||
|
uiSettings = loadData("ui_settings") ?: uiSettings
|
||||||
|
selectedOption = uiSettings.defaultStartUpTab
|
||||||
|
binding.includedNavbar.navbarContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = navBarHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val navbar = binding.includedNavbar.navbar
|
||||||
|
ani.dantotsu.bottomBar = navbar
|
||||||
|
navbar.visibility = View.VISIBLE
|
||||||
|
val mainViewPager = binding.viewpager
|
||||||
|
mainViewPager.isUserInputEnabled = false
|
||||||
|
mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle)
|
||||||
|
mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
||||||
|
navbar.setOnTabSelectListener(object :
|
||||||
|
AnimatedBottomBar.OnTabSelectListener {
|
||||||
|
override fun onTabSelected(
|
||||||
|
lastIndex: Int,
|
||||||
|
lastTab: AnimatedBottomBar.Tab?,
|
||||||
|
newIndex: Int,
|
||||||
|
newTab: AnimatedBottomBar.Tab
|
||||||
|
) {
|
||||||
|
navbar.animate().translationZ(12f).setDuration(200).start()
|
||||||
|
selectedOption = newIndex
|
||||||
|
mainViewPager.setCurrentItem(newIndex, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
navbar.selectTabAt(selectedOption)
|
||||||
|
|
||||||
|
//supportFragmentManager.beginTransaction().replace(binding.fragmentContainer.id, OfflineFragment()).commit()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
||||||
|
FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = 3
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
when (position) {
|
||||||
|
0 -> return OfflineFragment()
|
||||||
|
1 -> return OfflineFragment()
|
||||||
|
2 -> return OfflineMangaFragment()
|
||||||
|
}
|
||||||
|
return LoginFragment()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -29,7 +30,8 @@ class AuthorActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityAuthorBinding.inflate(layoutInflater)
|
binding = ActivityAuthorBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import ani.dantotsu.loadData
|
|||||||
import ani.dantotsu.media.user.ListViewPagerAdapter
|
import ani.dantotsu.media.user.ListViewPagerAdapter
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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
|
||||||
@@ -33,26 +34,30 @@ 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()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityListBinding.inflate(layoutInflater)
|
binding = ActivityListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorOnPrimary, typedValue, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true)
|
||||||
val primaryColor = typedValue.data
|
val primaryColor = typedValue.data
|
||||||
val typedValue2 = TypedValue()
|
val typedValue2 = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue2, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorOnBackground, typedValue2, true)
|
||||||
val primaryTextColor = typedValue2.data
|
val titleTextColor = typedValue2.data
|
||||||
val typedValue3 = TypedValue()
|
val typedValue3 = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorSecondary, typedValue3, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue3, true)
|
||||||
val secondaryColor = typedValue3.data
|
val primaryTextColor = typedValue3.data
|
||||||
|
val typedValue4 = TypedValue()
|
||||||
|
theme.resolveAttribute(com.google.android.material.R.attr.colorOutline, typedValue4, true)
|
||||||
|
val secondaryTextColor = typedValue4.data
|
||||||
|
|
||||||
window.statusBarColor = primaryColor
|
window.statusBarColor = primaryColor
|
||||||
window.navigationBarColor = primaryColor
|
window.navigationBarColor = primaryColor
|
||||||
binding.listTabLayout.setBackgroundColor(primaryColor)
|
binding.listTabLayout.setBackgroundColor(primaryColor)
|
||||||
binding.listAppBar.setBackgroundColor(primaryColor)
|
binding.listAppBar.setBackgroundColor(primaryColor)
|
||||||
binding.listTitle.setTextColor(primaryTextColor)
|
binding.listTitle.setTextColor(titleTextColor)
|
||||||
binding.listTabLayout.setTabTextColors(primaryTextColor, primaryTextColor)
|
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
|
||||||
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
||||||
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||||
if (!uiSettings.immersiveMode) {
|
if (!uiSettings.immersiveMode) {
|
||||||
@@ -103,4 +108,4 @@ class CalendarActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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
|
||||||
@@ -34,7 +35,8 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityCharacterBinding.inflate(layoutInflater)
|
binding = ActivityCharacterBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -26,7 +27,8 @@ class GenreActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityGenreBinding.inflate(layoutInflater)
|
binding = ActivityGenreBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ data class Media(
|
|||||||
val userPreferredName: String,
|
val userPreferredName: String,
|
||||||
|
|
||||||
var cover: String? = null,
|
var cover: String? = null,
|
||||||
val banner: String? = null,
|
var banner: String? = null,
|
||||||
var relation: String? = null,
|
var relation: String? = null,
|
||||||
var popularity: Int? = null,
|
var popularity: Int? = null,
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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
|
||||||
@@ -72,7 +73,8 @@ 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()
|
LangSet.setLocale(this)
|
||||||
|
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()
|
||||||
@@ -118,7 +120,8 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
||||||
|
|
||||||
var media: Media = intent.getSerialized("media") ?: return
|
var media: Media = intent.getSerialized("media") ?: return
|
||||||
media.selected = model.loadSelected(media)
|
val isDownload = intent.getBooleanExtra("download", false)
|
||||||
|
media.selected = model.loadSelected(media, isDownload)
|
||||||
|
|
||||||
binding.mediaCoverImage.loadImage(media.cover)
|
binding.mediaCoverImage.loadImage(media.cover)
|
||||||
binding.mediaCoverImage.setOnLongClickListener {
|
binding.mediaCoverImage.setOnLongClickListener {
|
||||||
@@ -324,7 +327,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||||||
tabLayout.setOnItemSelectedListener { item ->
|
tabLayout.setOnItemSelectedListener { item ->
|
||||||
selectFromID(item.itemId)
|
selectFromID(item.itemId)
|
||||||
viewPager.setCurrentItem(selected, false)
|
viewPager.setCurrentItem(selected, false)
|
||||||
val sel = model.loadSelected(media)
|
val sel = model.loadSelected(media, isDownload)
|
||||||
sel.window = selected
|
sel.window = selected
|
||||||
model.saveSelected(media.id, sel, this)
|
model.saveSelected(media.id, sel, this)
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package ani.dantotsu.media
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Environment
|
||||||
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
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.media.anime.Episode
|
import ani.dantotsu.media.anime.Episode
|
||||||
import ani.dantotsu.media.anime.SelectorDialogFragment
|
import ani.dantotsu.media.anime.SelectorDialogFragment
|
||||||
@@ -30,6 +32,8 @@ 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.download.Download
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.AniyomiAdapter
|
import ani.dantotsu.parsers.AniyomiAdapter
|
||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
@@ -44,6 +48,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class MediaDetailsViewModel : ViewModel() {
|
class MediaDetailsViewModel : ViewModel() {
|
||||||
val scrolledToTop = MutableLiveData(true)
|
val scrolledToTop = MutableLiveData(true)
|
||||||
@@ -53,17 +58,23 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun loadSelected(media: Media): Selected {
|
fun loadSelected(media: Media, isDownload: Boolean = false): Selected {
|
||||||
val sharedPreferences = Injekt.get<SharedPreferences>()
|
val sharedPreferences = Injekt.get<SharedPreferences>()
|
||||||
val data = loadData<Selected>("${media.id}-select") ?: Selected().let {
|
val data = loadData<Selected>("${media.id}-select") ?: Selected().let {
|
||||||
it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) {
|
it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) {
|
||||||
true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0)
|
true ->sharedPreferences.getInt("settings_def_anime_source_s_r", 0)
|
||||||
else -> sharedPreferences.getInt(("settings_def_manga_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
|
||||||
}
|
}
|
||||||
|
if (isDownload) {
|
||||||
|
data.sourceIndex = when (media.anime != null) {
|
||||||
|
true -> AnimeSources.list.size - 1
|
||||||
|
else -> MangaSources.list.size - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +131,8 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
private val episodes = MutableLiveData<MutableMap<Int, MutableMap<String, Episode>>>(null)
|
private val episodes = MutableLiveData<MutableMap<Int, MutableMap<String, Episode>>>(null)
|
||||||
private val epsLoaded = mutableMapOf<Int, MutableMap<String, Episode>>()
|
private val epsLoaded = mutableMapOf<Int, MutableMap<String, Episode>>()
|
||||||
fun getEpisodes(): LiveData<MutableMap<Int, MutableMap<String, Episode>>> = episodes
|
fun getEpisodes(): LiveData<MutableMap<Int, MutableMap<String, Episode>>> = episodes
|
||||||
suspend fun loadEpisodes(media: Media, i: Int) {
|
suspend fun loadEpisodes(media: Media, i: Int, invalidate: Boolean = false) {
|
||||||
if (!epsLoaded.containsKey(i)) {
|
if (!epsLoaded.containsKey(i) || invalidate) {
|
||||||
epsLoaded[i] = watchSources?.loadEpisodesFromMedia(i, media) ?: return
|
epsLoaded[i] = watchSources?.loadEpisodesFromMedia(i, media) ?: return
|
||||||
}
|
}
|
||||||
episodes.postValue(epsLoaded)
|
episodes.postValue(epsLoaded)
|
||||||
@@ -240,9 +251,9 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
private val mangaChapters = MutableLiveData<MutableMap<Int, MutableMap<String, MangaChapter>>>(null)
|
private val mangaChapters = MutableLiveData<MutableMap<Int, MutableMap<String, MangaChapter>>>(null)
|
||||||
private val mangaLoaded = mutableMapOf<Int, MutableMap<String, MangaChapter>>()
|
private val mangaLoaded = mutableMapOf<Int, MutableMap<String, MangaChapter>>()
|
||||||
fun getMangaChapters(): LiveData<MutableMap<Int, MutableMap<String, MangaChapter>>> = mangaChapters
|
fun getMangaChapters(): LiveData<MutableMap<Int, MutableMap<String, MangaChapter>>> = mangaChapters
|
||||||
suspend fun loadMangaChapters(media: Media, i: Int) {
|
suspend fun loadMangaChapters(media: Media, i: Int, invalidate: Boolean = false) {
|
||||||
logger("Loading Manga Chapters : $mangaLoaded")
|
logger("Loading Manga Chapters : $mangaLoaded")
|
||||||
if (!mangaLoaded.containsKey(i)) tryWithSuspend {
|
if (!mangaLoaded.containsKey(i) || invalidate) tryWithSuspend {
|
||||||
mangaLoaded[i] = mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend
|
mangaLoaded[i] = mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend
|
||||||
}
|
}
|
||||||
mangaChapters.postValue(mangaLoaded)
|
mangaChapters.postValue(mangaLoaded)
|
||||||
@@ -258,7 +269,28 @@ class MediaDetailsViewModel : ViewModel() {
|
|||||||
|
|
||||||
private val mangaChapter = MutableLiveData<MangaChapter?>(null)
|
private val mangaChapter = MutableLiveData<MangaChapter?>(null)
|
||||||
fun getMangaChapter(): LiveData<MangaChapter?> = mangaChapter
|
fun getMangaChapter(): LiveData<MangaChapter?> = mangaChapter
|
||||||
suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, post: Boolean = true): Boolean {
|
suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, series: String, post: Boolean = true): Boolean {
|
||||||
|
//check if the chapter has been downloaded already
|
||||||
|
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||||
|
if(downloadsManager.mangaDownloads.contains(Download(series, chapter.title!!, Download.Type.MANGA))) {
|
||||||
|
val download = downloadsManager.mangaDownloads.find { it.title == series && it.chapter == chapter.title!! } ?: return false
|
||||||
|
//look in the downloads folder for the chapter and add all the numerically named images to the chapter
|
||||||
|
val directory = File(
|
||||||
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/$series/${chapter.title!!}"
|
||||||
|
)
|
||||||
|
val images = mutableListOf<MangaImage>()
|
||||||
|
directory.listFiles()?.forEach {
|
||||||
|
if (it.nameWithoutExtension.toIntOrNull() != null) {
|
||||||
|
images.add(MangaImage(FileUrl(it.absolutePath), false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//sort the images by name
|
||||||
|
images.sortBy { it.url.url }
|
||||||
|
chapter.addImages(images)
|
||||||
|
if (post) mangaChapter.postValue(chapter)
|
||||||
|
return true
|
||||||
|
}
|
||||||
return tryWithSuspend(true) {
|
return tryWithSuspend(true) {
|
||||||
chapter.addImages(
|
chapter.addImages(
|
||||||
mangaReadSources?.get(selected.sourceIndex)?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false
|
mangaReadSources?.get(selected.sourceIndex)?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -38,7 +39,8 @@ class SearchActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivitySearchBinding.inflate(layoutInflater)
|
binding = ActivitySearchBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ data class Selected(
|
|||||||
var chip: Int = 0,
|
var chip: Int = 0,
|
||||||
//var source: String = "",
|
//var source: String = "",
|
||||||
var sourceIndex: Int = 0,
|
var sourceIndex: Int = 0,
|
||||||
|
var langIndex: 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,
|
||||||
var latest: Float = 0f,
|
var latest: Float = 0f,
|
||||||
|
var scanlators: List<String>? = null,
|
||||||
) : Serializable
|
) : Serializable
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -29,7 +30,8 @@ class StudioActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityStudioBinding.inflate(layoutInflater)
|
binding = ActivityStudioBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
@@ -8,22 +10,38 @@ 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.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.SourceSearchDialogFragment
|
import ani.dantotsu.media.SourceSearchDialogFragment
|
||||||
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
|
import ani.dantotsu.parsers.DynamicAnimeParser
|
||||||
import ani.dantotsu.parsers.WatchSources
|
import ani.dantotsu.parsers.WatchSources
|
||||||
|
import ani.dantotsu.settings.ExtensionsActivity
|
||||||
|
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||||
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.lang.IndexOutOfBoundsException
|
||||||
|
|
||||||
class AnimeWatchAdapter(
|
class AnimeWatchAdapter(
|
||||||
private val media: Media,
|
private val media: Media,
|
||||||
@@ -69,7 +87,8 @@ class AnimeWatchAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Source Selection
|
//Source Selection
|
||||||
val source = media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it }
|
var source = media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it }
|
||||||
|
setLanguageList(media.selected!!.langIndex,source)
|
||||||
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 {
|
||||||
@@ -91,11 +110,41 @@ class AnimeWatchAdapter(
|
|||||||
binding.animeSourceDubbed.isChecked = selectDub
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
changing = false
|
changing = false
|
||||||
binding.animeSourceDubbedCont.visibility = if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
binding.animeSourceDubbedCont.visibility = if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
||||||
|
source = i
|
||||||
|
setLanguageList(0,i)
|
||||||
}
|
}
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
fragment.loadEpisodes(i)
|
fragment.loadEpisodes(i, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
// Check if 'extension' and 'selected' properties exist and are accessible
|
||||||
|
(watchSources[source] as? DynamicAnimeParser)?.let { ext ->
|
||||||
|
ext.sourceLanguage = i
|
||||||
|
fragment.onLangChange(i)
|
||||||
|
fragment.onSourceChange(media.selected!!.sourceIndex).apply {
|
||||||
|
binding.animeSourceTitle.text = showUserText
|
||||||
|
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||||
|
changing = true
|
||||||
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
|
changing = false
|
||||||
|
binding.animeSourceDubbedCont.visibility = if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
||||||
|
setLanguageList(i, source)
|
||||||
|
}
|
||||||
|
subscribeButton(false)
|
||||||
|
fragment.loadEpisodes(media.selected!!.sourceIndex, true)
|
||||||
|
} ?: run {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//settings
|
||||||
|
binding.animeSourceSettings.setOnClickListener {
|
||||||
|
(watchSources[source] as? DynamicAnimeParser)?.let { ext ->
|
||||||
|
fragment.openSettings(ext.extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//Subscription
|
//Subscription
|
||||||
subscribe = MediaDetailsActivity.PopImageButton(
|
subscribe = MediaDetailsActivity.PopImageButton(
|
||||||
fragment.lifecycleScope,
|
fragment.lifecycleScope,
|
||||||
@@ -177,6 +226,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()
|
||||||
@@ -261,6 +311,25 @@ class AnimeWatchAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setLanguageList(lang: Int, source: Int) {
|
||||||
|
val binding = _binding
|
||||||
|
if (watchSources is AnimeSources) {
|
||||||
|
val parser = watchSources[source] as? DynamicAnimeParser
|
||||||
|
if (parser != null) {
|
||||||
|
(watchSources[source] as? DynamicAnimeParser)?.let { ext ->
|
||||||
|
ext.sourceLanguage = lang
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang)
|
||||||
|
}catch (e: IndexOutOfBoundsException) {
|
||||||
|
binding?.animeSourceLanguage?.setText(parser.extension.sources.firstOrNull()?.lang ?: "Unknown")
|
||||||
|
}
|
||||||
|
binding?.animeSourceLanguage?.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, parser.extension.sources.map { it.lang }))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ItemAnimeWatchBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class ViewHolder(val binding: ItemAnimeWatchBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
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.FrameLayout
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.math.MathUtils
|
import androidx.core.math.MathUtils
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -13,24 +19,38 @@ import androidx.fragment.app.activityViewModels
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.parsers.AnimeParser
|
import ani.dantotsu.parsers.AnimeParser
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.HAnimeSources
|
import ani.dantotsu.parsers.HAnimeSources
|
||||||
|
import ani.dantotsu.settings.ExtensionsActivity
|
||||||
|
import ani.dantotsu.settings.InstalledAnimeExtensionsFragment
|
||||||
import ani.dantotsu.settings.PlayerSettings
|
import ani.dantotsu.settings.PlayerSettings
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
|
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||||
import ani.dantotsu.subcriptions.Notifications
|
import ani.dantotsu.subcriptions.Notifications
|
||||||
import ani.dantotsu.subcriptions.Notifications.Group.ANIME_GROUP
|
import ani.dantotsu.subcriptions.Notifications.Group.ANIME_GROUP
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper
|
import ani.dantotsu.subcriptions.SubscriptionHelper
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
|
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.navigationrail.NavigationRailView
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -214,6 +234,13 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
return model.watchSources?.get(i)!!
|
return model.watchSources?.get(i)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onLangChange(i: Int) {
|
||||||
|
val selected = model.loadSelected(media)
|
||||||
|
selected.langIndex = i
|
||||||
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
|
media.selected = selected
|
||||||
|
}
|
||||||
|
|
||||||
fun onDubClicked(checked: Boolean) {
|
fun onDubClicked(checked: Boolean) {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
model.watchSources?.get(selected.sourceIndex)?.selectDub = checked
|
model.watchSources?.get(selected.sourceIndex)?.selectDub = checked
|
||||||
@@ -223,8 +250,8 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
lifecycleScope.launch(Dispatchers.IO) { model.forceLoadEpisode(media, selected.sourceIndex) }
|
lifecycleScope.launch(Dispatchers.IO) { model.forceLoadEpisode(media, selected.sourceIndex) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadEpisodes(i: Int) {
|
fun loadEpisodes(i: Int, invalidate: Boolean) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) { model.loadEpisodes(media, i) }
|
lifecycleScope.launch(Dispatchers.IO) { model.loadEpisodes(media, i, invalidate) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onIconPressed(viewType: Int, rev: Boolean) {
|
fun onIconPressed(viewType: Int, rev: Boolean) {
|
||||||
@@ -262,45 +289,115 @@ class AnimeWatchFragment : Fragment() {
|
|||||||
else getString(R.string.unsubscribed_notification)
|
else getString(R.string.unsubscribed_notification)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fun openSettings(pkg: AnimeExtension.Installed){
|
||||||
fun onEpisodeClick(i: String) {
|
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
||||||
model.continueMedia = false
|
val activity = requireActivity() as MediaDetailsActivity
|
||||||
model.saveSelected(media.id, media.selected!!, requireActivity())
|
val visibility = if (show) View.VISIBLE else View.GONE
|
||||||
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager)
|
activity.findViewById<AppBarLayout>(R.id.mediaAppBar).visibility = visibility
|
||||||
}
|
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility
|
||||||
|
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility
|
||||||
private fun reload() {
|
try{
|
||||||
val selected = model.loadSelected(media)
|
activity.findViewById<CustomBottomNavBar>(R.id.mediaTab).visibility = visibility
|
||||||
|
}catch (e: ClassCastException){
|
||||||
//Find latest episode for subscription
|
activity.findViewById<NavigationRailView>(R.id.mediaTab).visibility = visibility
|
||||||
selected.latest = media.anime?.episodes?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
}
|
||||||
selected.latest = media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||||
|
if (show) View.GONE else View.VISIBLE
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
}
|
||||||
headerAdapter.handleEpisodes()
|
val allSettings = pkg.sources.filterIsInstance<ConfigurableAnimeSource>()
|
||||||
episodeAdapter.notifyItemRangeRemoved(0, episodeAdapter.arr.size)
|
if (allSettings.isNotEmpty()) {
|
||||||
var arr: ArrayList<Episode> = arrayListOf()
|
var selectedSetting = allSettings[0]
|
||||||
if (media.anime!!.episodes != null) {
|
if (allSettings.size > 1) {
|
||||||
val end = if (end != null && end!! < media.anime!!.episodes!!.size) end else null
|
val names = allSettings.map { it.lang }.toTypedArray()
|
||||||
arr.addAll(
|
var selectedIndex = 0
|
||||||
media.anime!!.episodes!!.values.toList()
|
AlertDialog.Builder(requireContext())
|
||||||
.slice(start..(end ?: (media.anime!!.episodes!!.size - 1)))
|
.setTitle("Select a Source")
|
||||||
)
|
.setSingleChoiceItems(names, selectedIndex) { _, which ->
|
||||||
if (reverse)
|
selectedIndex = which
|
||||||
arr = (arr.reversed() as? ArrayList<Episode>) ?: arr
|
}
|
||||||
|
.setPositiveButton("OK") { dialog, _ ->
|
||||||
|
selectedSetting = allSettings[selectedIndex]
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
|
// Move the fragment transaction here
|
||||||
|
val fragment =
|
||||||
|
AnimeSourcePreferencesFragment().getInstance(selectedSetting.id){
|
||||||
|
changeUIVisibility(true)
|
||||||
|
loadEpisodes(media.selected!!.sourceIndex, true)
|
||||||
|
}
|
||||||
|
parentFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
changeUIVisibility(true)
|
||||||
|
return@setNegativeButton
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
// If there's only one setting, proceed with the fragment transaction
|
||||||
|
val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id){
|
||||||
|
changeUIVisibility(true)
|
||||||
|
loadEpisodes(media.selected!!.sourceIndex, true)
|
||||||
|
}
|
||||||
|
parentFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
changeUIVisibility(false)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
episodeAdapter.arr = arr
|
|
||||||
episodeAdapter.updateType(style ?: uiSettings.animeDefaultView)
|
|
||||||
episodeAdapter.notifyItemRangeInserted(0, arr.size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
fun onEpisodeClick(i: String) {
|
||||||
model.watchSources?.flushText()
|
model.continueMedia = false
|
||||||
super.onDestroy()
|
model.saveSelected(media.id, media.selected!!, requireActivity())
|
||||||
}
|
model.onEpisodeClick(media, i, requireActivity().supportFragmentManager)
|
||||||
|
}
|
||||||
|
|
||||||
var state: Parcelable? = null
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private fun reload() {
|
||||||
|
val selected = model.loadSelected(media)
|
||||||
|
|
||||||
|
//Find latest episode for subscription
|
||||||
|
selected.latest =
|
||||||
|
media.anime?.episodes?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
||||||
|
selected.latest =
|
||||||
|
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
||||||
|
|
||||||
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
|
headerAdapter.handleEpisodes()
|
||||||
|
episodeAdapter.notifyItemRangeRemoved(0, episodeAdapter.arr.size)
|
||||||
|
var arr: ArrayList<Episode> = arrayListOf()
|
||||||
|
if (media.anime!!.episodes != null) {
|
||||||
|
val end = if (end != null && end!! < media.anime!!.episodes!!.size) end else null
|
||||||
|
arr.addAll(
|
||||||
|
media.anime!!.episodes!!.values.toList()
|
||||||
|
.slice(start..(end ?: (media.anime!!.episodes!!.size - 1)))
|
||||||
|
)
|
||||||
|
if (reverse)
|
||||||
|
arr = (arr.reversed() as? ArrayList<Episode>) ?: arr
|
||||||
|
}
|
||||||
|
episodeAdapter.arr = arr
|
||||||
|
episodeAdapter.updateType(style ?: uiSettings.animeDefaultView)
|
||||||
|
episodeAdapter.notifyItemRangeInserted(0, arr.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
model.watchSources?.flushText()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
var state: Parcelable? = null
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
binding.mediaInfoProgressBar.visibility = progress
|
binding.mediaInfoProgressBar.visibility = progress
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import android.graphics.drawable.Animatable
|
|||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.AudioManager.*
|
import android.media.AudioManager.*
|
||||||
|
import android.media.PlaybackParams
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -77,8 +78,10 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import com.lagradost.nicehttp.ignoreAllSSLErrors
|
import com.lagradost.nicehttp.ignoreAllSSLErrors
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -317,7 +320,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityExoplayerBinding.inflate(layoutInflater)
|
binding = ActivityExoplayerBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -806,23 +810,24 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fastForward() {
|
fun fastForward() {
|
||||||
val newSpeed = playbackParameters.speed * 2f
|
|
||||||
exoPlayer.playbackParameters = playbackParameters.withSpeed(newSpeed)
|
|
||||||
isFastForwarding = true
|
isFastForwarding = true
|
||||||
snackString("Playing at ${newSpeed}x speed")
|
exoPlayer.setPlaybackSpeed(2f)
|
||||||
|
snackString("Playing at 2x speed")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopFastForward() {
|
fun stopFastForward() {
|
||||||
if (isFastForwarding) {
|
if (isFastForwarding) {
|
||||||
exoPlayer.playbackParameters = playbackParameters
|
|
||||||
isFastForwarding = false
|
isFastForwarding = false
|
||||||
|
exoPlayer.setPlaybackSpeed(1f)
|
||||||
snackString("Playing at normal speed")
|
snackString("Playing at normal speed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//FastRewind (Left Panel)
|
//FastRewind (Left Panel)
|
||||||
val fastRewindDetector = GestureDetector(this, object : GesturesListener() {
|
val fastRewindDetector = GestureDetector(this, object : GesturesListener() {
|
||||||
override fun onLongClick(event: MotionEvent) = fastForward()
|
override fun onLongClick(event: MotionEvent) {
|
||||||
|
if (settings.fastforward) fastForward()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDoubleClick(event: MotionEvent) {
|
override fun onDoubleClick(event: MotionEvent) {
|
||||||
doubleTap(false, event)
|
doubleTap(false, event)
|
||||||
@@ -852,8 +857,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
|
|
||||||
//FastForward (Right Panel)
|
//FastForward (Right Panel)
|
||||||
val fastForwardDetector = GestureDetector(this, object : GesturesListener() {
|
val fastForwardDetector = GestureDetector(this, object : GesturesListener() {
|
||||||
override fun onLongClick(event: MotionEvent) = fastForward()
|
override fun onLongClick(event: MotionEvent) {
|
||||||
|
if (settings.fastforward) fastForward()
|
||||||
|
}
|
||||||
override fun onDoubleClick(event: MotionEvent) {
|
override fun onDoubleClick(event: MotionEvent) {
|
||||||
doubleTap(true, event)
|
doubleTap(true, event)
|
||||||
}
|
}
|
||||||
@@ -1614,7 +1620,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
-> toast("Player Error ${error.errorCode} (${error.errorCodeName}) : ${error.message}")
|
-> {
|
||||||
|
toast("Player Error ${error.errorCode} (${error.errorCodeName}) : ${error.message}")
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package ani.dantotsu.media.anime
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
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.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -24,6 +26,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -53,6 +56,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
_binding = BottomSheetSelectorBinding.inflate(inflater, container, false)
|
_binding = BottomSheetSelectorBinding.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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +245,8 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||||||
if (video.format == VideoType.CONTAINER) {
|
if (video.format == VideoType.CONTAINER) {
|
||||||
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE
|
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE
|
||||||
binding.urlSize.text =
|
binding.urlSize.text =
|
||||||
(if (video.extraNote != null) " : " else "") + DecimalFormat("#.##").format(video.size ?: 0).toString() + " MB"
|
// if video size is null or 0, show "Unknown Size" else show the size in MB
|
||||||
|
(if (video.extraNote != null) " : " else "") + (if (video.size == 0.0) "Unknown Size" else (DecimalFormat("#.##").format(video.size ?: 0).toString()+ " MB"))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
binding.urlQuality.text = "Multi Quality"
|
binding.urlQuality.text = "Multi Quality"
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import android.os.Build
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
|
import android.widget.Toast
|
||||||
|
import ani.dantotsu.logger
|
||||||
|
import ani.dantotsu.snackString
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -26,22 +29,23 @@ data class ImageData(
|
|||||||
try {
|
try {
|
||||||
// Fetch the image
|
// Fetch the image
|
||||||
val response = httpSource.getImage(page)
|
val response = httpSource.getImage(page)
|
||||||
println("Response: ${response.code}")
|
logger("Response: ${response.code}")
|
||||||
println("Response: ${response.message}")
|
logger("Response: ${response.message}")
|
||||||
|
|
||||||
// Convert the Response to an InputStream
|
// Convert the Response to an InputStream
|
||||||
val inputStream = response.body?.byteStream()
|
val inputStream = response.body.byteStream()
|
||||||
|
|
||||||
// Convert InputStream to Bitmap
|
// Convert InputStream to Bitmap
|
||||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
|
||||||
inputStream?.close()
|
inputStream.close()
|
||||||
saveImage(bitmap, context.contentResolver, page.imageUrl!!, Bitmap.CompressFormat.JPEG, 100)
|
//saveImage(bitmap, context.contentResolver, page.imageUrl!!, Bitmap.CompressFormat.JPEG, 100)
|
||||||
|
|
||||||
return@withContext bitmap
|
return@withContext bitmap
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Handle any exceptions
|
// Handle any exceptions
|
||||||
println("An error occurred: ${e.message}")
|
logger("An error occurred: ${e.message}")
|
||||||
|
snackString("An error occurred: ${e.message}")
|
||||||
return@withContext null
|
return@withContext null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +61,7 @@ fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String
|
|||||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Manga")
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Manga")
|
||||||
}
|
}
|
||||||
|
|
||||||
val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
val uri: Uri? = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
|
||||||
uri?.let {
|
uri?.let {
|
||||||
contentResolver.openOutputStream(it)?.use { os ->
|
contentResolver.openOutputStream(it)?.use { os ->
|
||||||
@@ -65,7 +69,7 @@ fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Anime")
|
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Manga")
|
||||||
if (!directory.exists()) {
|
if (!directory.exists()) {
|
||||||
directory.mkdirs()
|
directory.mkdirs()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ 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
|
var sChapter: SChapter,
|
||||||
|
val scanlator: String? = null,
|
||||||
|
var progress: String? = ""
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description, chapter.sChapter)
|
constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description, chapter.sChapter, chapter.scanlator)
|
||||||
|
|
||||||
private val images = mutableListOf<MangaImage>()
|
private val images = mutableListOf<MangaImage>()
|
||||||
fun images(): List<MangaImage> = images
|
fun images(): List<MangaImage> = images
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.ItemChapterListBinding
|
import ani.dantotsu.databinding.ItemChapterListBinding
|
||||||
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
@@ -28,7 +29,15 @@ class MangaChapterAdapter(
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
0 -> ChapterListViewHolder(ItemChapterListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
|
||||||
|
0 -> ChapterListViewHolder(
|
||||||
|
ItemChapterListBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +48,8 @@ class MangaChapterAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int = arr.size
|
override fun getItemCount(): Int = arr.size
|
||||||
|
|
||||||
inner class ChapterCompactViewHolder(val binding: ItemEpisodeCompactBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class ChapterCompactViewHolder(val binding: ItemEpisodeCompactBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
||||||
@@ -48,12 +58,102 @@ class MangaChapterAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ChapterListViewHolder(val binding: ItemChapterListBinding) : RecyclerView.ViewHolder(binding.root) {
|
private val activeDownloads = mutableSetOf<String>()
|
||||||
|
private val downloadedChapters = mutableSetOf<String>()
|
||||||
|
|
||||||
|
fun startDownload(chapterNumber: String) {
|
||||||
|
activeDownloads.add(chapterNumber)
|
||||||
|
// Find the position of the chapter and notify only that item
|
||||||
|
val position = arr.indexOfFirst { it.number == chapterNumber }
|
||||||
|
if (position != -1) {
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopDownload(chapterNumber: String) {
|
||||||
|
activeDownloads.remove(chapterNumber)
|
||||||
|
downloadedChapters.add(chapterNumber)
|
||||||
|
// Find the position of the chapter and notify only that item
|
||||||
|
val position = arr.indexOfFirst { it.number == chapterNumber }
|
||||||
|
if (position != -1) {
|
||||||
|
arr[position].progress = "Downloaded"
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteDownload(chapterNumber: String) {
|
||||||
|
downloadedChapters.remove(chapterNumber)
|
||||||
|
// Find the position of the chapter and notify only that item
|
||||||
|
val position = arr.indexOfFirst { it.number == chapterNumber }
|
||||||
|
if (position != -1) {
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeDownload(chapterNumber: String) {
|
||||||
|
activeDownloads.remove(chapterNumber)
|
||||||
|
downloadedChapters.remove(chapterNumber)
|
||||||
|
// Find the position of the chapter and notify only that item
|
||||||
|
val position = arr.indexOfFirst { it.number == chapterNumber }
|
||||||
|
if (position != -1) {
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDownloadProgress(chapterNumber: String, progress: Int) {
|
||||||
|
// Find the position of the chapter and notify only that item
|
||||||
|
val position = arr.indexOfFirst { it.number == chapterNumber }
|
||||||
|
if (position != -1) {
|
||||||
|
arr[position].progress = "Downloading: ${progress}%"
|
||||||
|
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ChapterListViewHolder(val binding: ItemChapterListBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
fun bind(chapterNumber: String, progress: String?) {
|
||||||
|
if (progress != null) {
|
||||||
|
binding.itemChapterTitle.visibility = View.VISIBLE
|
||||||
|
binding.itemChapterTitle.text = "$progress"
|
||||||
|
}else{
|
||||||
|
binding.itemChapterTitle.visibility = View.GONE
|
||||||
|
binding.itemChapterTitle.text = ""
|
||||||
|
}
|
||||||
|
if (activeDownloads.contains(chapterNumber)) {
|
||||||
|
// Show spinner
|
||||||
|
binding.itemDownload.setImageResource(R.drawable.ic_round_refresh_24)
|
||||||
|
} else if (downloadedChapters.contains(chapterNumber)) {
|
||||||
|
// Show checkmark
|
||||||
|
binding.itemDownload.setImageResource(R.drawable.ic_check)
|
||||||
|
} else {
|
||||||
|
// Show download icon
|
||||||
|
binding.itemDownload.setImageResource(R.drawable.ic_round_download_24)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
||||||
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
||||||
}
|
}
|
||||||
|
binding.itemDownload.setOnClickListener {
|
||||||
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size) {
|
||||||
|
val chapterNumber = arr[bindingAdapterPosition].number
|
||||||
|
if (activeDownloads.contains(chapterNumber)) {
|
||||||
|
fragment.onMangaChapterStopDownloadClick(chapterNumber)
|
||||||
|
return@setOnClickListener
|
||||||
|
} else if (downloadedChapters.contains(chapterNumber)) {
|
||||||
|
fragment.onMangaChapterRemoveDownloadClick(chapterNumber)
|
||||||
|
return@setOnClickListener
|
||||||
|
} else {
|
||||||
|
fragment.onMangaChapterDownloadClick(chapterNumber)
|
||||||
|
startDownload(chapterNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,44 +163,50 @@ 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 ((MangaNameAdapter.findChapterNumber(ep.number) ?: 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, MangaNameAdapter.findChapterNumber(ep.number).toString())
|
updateProgress(
|
||||||
|
media,
|
||||||
|
MangaNameAdapter.findChapterNumber(ep.number).toString()
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ChapterListViewHolder -> {
|
|
||||||
|
is ChapterListViewHolder -> {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
val ep = arr[position]
|
val ep = arr[position]
|
||||||
|
holder.bind(ep.number, ep.progress)
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
||||||
binding.itemChapterNumber.text = ep.number
|
binding.itemChapterNumber.text = ep.number
|
||||||
if (!ep.title.isNullOrEmpty()) {
|
if (ep.progress.isNullOrEmpty()) {
|
||||||
binding.itemChapterTitle.text = ep.title
|
binding.itemChapterTitle.visibility = View.GONE
|
||||||
binding.itemChapterTitle.setOnLongClickListener {
|
} else binding.itemChapterTitle.visibility = View.VISIBLE
|
||||||
binding.itemChapterTitle.maxLines.apply {
|
|
||||||
binding.itemChapterTitle.maxLines = if (this == 1) 3 else 1
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
binding.itemChapterTitle.visibility = View.VISIBLE
|
|
||||||
} else binding.itemChapterTitle.visibility = View.GONE
|
|
||||||
|
|
||||||
if (media.userProgress != null) {
|
if (media.userProgress != null) {
|
||||||
if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 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, MangaNameAdapter.findChapterNumber(ep.number).toString())
|
updateProgress(
|
||||||
|
media,
|
||||||
|
MangaNameAdapter.findChapterNumber(ep.number).toString()
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,4 +223,4 @@ class MangaChapterAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,36 @@
|
|||||||
package ani.dantotsu.media.manga
|
package ani.dantotsu.media.manga
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
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.CheckBox
|
||||||
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.*
|
||||||
|
import ani.dantotsu.App.Companion.context
|
||||||
import ani.dantotsu.media.anime.handleProgress
|
import ani.dantotsu.media.anime.handleProgress
|
||||||
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.SourceSearchDialogFragment
|
import ani.dantotsu.media.SourceSearchDialogFragment
|
||||||
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
|
import ani.dantotsu.parsers.DynamicAnimeParser
|
||||||
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.MangaReadSources
|
import ani.dantotsu.parsers.MangaReadSources
|
||||||
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.lang.IndexOutOfBoundsException
|
||||||
|
|
||||||
class MangaReadAdapter(
|
class MangaReadAdapter(
|
||||||
private val media: Media,
|
private val media: Media,
|
||||||
@@ -31,6 +40,9 @@ class MangaReadAdapter(
|
|||||||
|
|
||||||
var subscribe: MediaDetailsActivity.PopImageButton? = null
|
var subscribe: MediaDetailsActivity.PopImageButton? = null
|
||||||
private var _binding: ItemAnimeWatchBinding? = null
|
private var _binding: ItemAnimeWatchBinding? = null
|
||||||
|
val hiddenScanlators = mutableListOf<String>()
|
||||||
|
var scanlatorSelectionListener: ScanlatorSelectionListener? = null
|
||||||
|
var options = listOf<String>()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
@@ -49,10 +61,10 @@ class MangaReadAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Source Selection
|
//Source Selection
|
||||||
val source = media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it }
|
var source = media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it }
|
||||||
|
setLanguageList(media.selected!!.langIndex,source)
|
||||||
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])
|
||||||
|
|
||||||
mangaReadSources[source].apply {
|
mangaReadSources[source].apply {
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.animeSourceTitle.text = showUserText
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||||
@@ -64,9 +76,36 @@ class MangaReadAdapter(
|
|||||||
fragment.onSourceChange(i).apply {
|
fragment.onSourceChange(i).apply {
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.animeSourceTitle.text = showUserText
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||||
|
source = i
|
||||||
|
setLanguageList(0,i)
|
||||||
}
|
}
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
fragment.loadChapters(i)
|
//invalidate if it's the last source
|
||||||
|
val invalidate = i == mangaReadSources.names.size - 1
|
||||||
|
fragment.loadChapters(i, invalidate)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ ->
|
||||||
|
// Check if 'extension' and 'selected' properties exist and are accessible
|
||||||
|
(mangaReadSources[source] as? DynamicMangaParser)?.let { ext ->
|
||||||
|
ext.sourceLanguage = i
|
||||||
|
fragment.onLangChange(i)
|
||||||
|
fragment.onSourceChange(media.selected!!.sourceIndex).apply {
|
||||||
|
binding.animeSourceTitle.text = showUserText
|
||||||
|
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||||
|
setLanguageList(i, source)
|
||||||
|
}
|
||||||
|
subscribeButton(false)
|
||||||
|
fragment.loadChapters(media.selected!!.sourceIndex, true)
|
||||||
|
} ?: run {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//settings
|
||||||
|
binding.animeSourceSettings.setOnClickListener {
|
||||||
|
(mangaReadSources[source] as? DynamicMangaParser)?.let { ext ->
|
||||||
|
fragment.openSettings(ext.extension)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Subscription
|
//Subscription
|
||||||
@@ -98,6 +137,46 @@ class MangaReadAdapter(
|
|||||||
binding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
binding.animeSourceTop.rotation = if (reversed) -90f else 90f
|
||||||
fragment.onIconPressed(style, reversed)
|
fragment.onIconPressed(style, reversed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.animeScanlatorTop.setOnClickListener {
|
||||||
|
val dialogView = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
|
||||||
|
val checkboxContainer = dialogView.findViewById<LinearLayout>(R.id.checkboxContainer)
|
||||||
|
|
||||||
|
// Dynamically add checkboxes
|
||||||
|
|
||||||
|
options.forEach { option ->
|
||||||
|
val checkBox = CheckBox(currContext()).apply {
|
||||||
|
text = option
|
||||||
|
}
|
||||||
|
//set checked if it's already selected
|
||||||
|
if(media.selected!!.scanlators != null){
|
||||||
|
checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true
|
||||||
|
scanlatorSelectionListener?.onScanlatorsSelected()
|
||||||
|
}else{
|
||||||
|
checkBox.isChecked = true
|
||||||
|
}
|
||||||
|
checkboxContainer.addView(checkBox)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create AlertDialog
|
||||||
|
AlertDialog.Builder(currContext())
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton("OK") { dialog, which ->
|
||||||
|
//add unchecked to hidden
|
||||||
|
hiddenScanlators.clear()
|
||||||
|
for (i in 0 until checkboxContainer.childCount) {
|
||||||
|
val checkBox = checkboxContainer.getChildAt(i) as CheckBox
|
||||||
|
if (!checkBox.isChecked) {
|
||||||
|
hiddenScanlators.add(checkBox.text.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
media.selected!!.scanlators = hiddenScanlators
|
||||||
|
scanlatorSelectionListener?.onScanlatorsSelected()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
var selected = when (style) {
|
var selected = when (style) {
|
||||||
0 -> binding.animeSourceList
|
0 -> binding.animeSourceList
|
||||||
1 -> binding.animeSourceCompact
|
1 -> binding.animeSourceCompact
|
||||||
@@ -145,6 +224,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()
|
||||||
@@ -167,6 +247,7 @@ class MangaReadAdapter(
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun handleChapters() {
|
fun handleChapters() {
|
||||||
|
|
||||||
val binding = _binding
|
val binding = _binding
|
||||||
if (binding != null) {
|
if (binding != null) {
|
||||||
if (media.manga?.chapters != null) {
|
if (media.manga?.chapters != null) {
|
||||||
@@ -174,7 +255,11 @@ 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()
|
||||||
val formattedChapters = chapters.map { MangaNameAdapter.findChapterNumber(it)?.toInt()?.toString() }
|
val filteredChapters = chapters.filter { chapterKey ->
|
||||||
|
val chapter = media.manga.chapters!![chapterKey]!!
|
||||||
|
chapter.scanlator !in hiddenScanlators
|
||||||
|
}
|
||||||
|
val formattedChapters = filteredChapters.map { MangaNameAdapter.findChapterNumber(it)?.toInt()?.toString() }
|
||||||
if (formattedChapters.contains(continueEp)) {
|
if (formattedChapters.contains(continueEp)) {
|
||||||
continueEp = chapters[formattedChapters.indexOf(continueEp)]
|
continueEp = chapters[formattedChapters.indexOf(continueEp)]
|
||||||
binding.animeSourceContinue.visibility = View.VISIBLE
|
binding.animeSourceContinue.visibility = View.VISIBLE
|
||||||
@@ -222,7 +307,30 @@ class MangaReadAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setLanguageList(lang: Int, source: Int) {
|
||||||
|
val binding = _binding
|
||||||
|
if (mangaReadSources is MangaSources) {
|
||||||
|
val parser = mangaReadSources[source] as? DynamicMangaParser
|
||||||
|
if (parser != null) {
|
||||||
|
(mangaReadSources[source] as? DynamicMangaParser)?.let { ext ->
|
||||||
|
ext.sourceLanguage = lang
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang)
|
||||||
|
}catch (e: IndexOutOfBoundsException) {
|
||||||
|
binding?.animeSourceLanguage?.setText(parser.extension.sources.firstOrNull()?.lang ?: "Unknown")
|
||||||
|
}
|
||||||
|
binding?.animeSourceLanguage?.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, parser.extension.sources.map { it.lang }))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ItemAnimeWatchBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class ViewHolder(val binding: ItemAnimeWatchBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ScanlatorSelectionListener {
|
||||||
|
fun onScanlatorsSelected()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
package ani.dantotsu.media.manga
|
package ani.dantotsu.media.manga
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
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.FrameLayout
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.math.MathUtils.clamp
|
import androidx.core.math.MathUtils.clamp
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -13,27 +26,48 @@ import androidx.fragment.app.activityViewModels
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||||
|
import ani.dantotsu.download.Download
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.download.manga.MangaDownloaderService
|
||||||
|
import ani.dantotsu.download.manga.ServiceDataSingleton
|
||||||
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
|
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.HMangaSources
|
import ani.dantotsu.parsers.HMangaSources
|
||||||
import ani.dantotsu.parsers.MangaParser
|
import ani.dantotsu.parsers.MangaParser
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
|
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||||
|
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
||||||
import ani.dantotsu.subcriptions.Notifications
|
import ani.dantotsu.subcriptions.Notifications
|
||||||
import ani.dantotsu.subcriptions.Notifications.Group.MANGA_GROUP
|
import ani.dantotsu.subcriptions.Notifications.Group.MANGA_GROUP
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper
|
import ani.dantotsu.subcriptions.SubscriptionHelper
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
|
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.navigationrail.NavigationRailView
|
||||||
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import android.Manifest
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
|
||||||
open class MangaReadFragment : Fragment() {
|
open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
private var _binding: FragmentAnimeWatchBinding? = null
|
private var _binding: FragmentAnimeWatchBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private val model: MediaDetailsViewModel by activityViewModels()
|
private val model: MediaDetailsViewModel by activityViewModels()
|
||||||
@@ -48,6 +82,8 @@ open class MangaReadFragment : Fragment() {
|
|||||||
private lateinit var headerAdapter: MangaReadAdapter
|
private lateinit var headerAdapter: MangaReadAdapter
|
||||||
private lateinit var chapterAdapter: MangaChapterAdapter
|
private lateinit var chapterAdapter: MangaChapterAdapter
|
||||||
|
|
||||||
|
val downloadManager = Injekt.get<DownloadsManager>()
|
||||||
|
|
||||||
var screenWidth = 0f
|
var screenWidth = 0f
|
||||||
private var progress = View.VISIBLE
|
private var progress = View.VISIBLE
|
||||||
|
|
||||||
@@ -67,10 +103,18 @@ open class MangaReadFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
val intentFilter = IntentFilter().apply {
|
||||||
|
addAction(ACTION_DOWNLOAD_STARTED)
|
||||||
|
addAction(ACTION_DOWNLOAD_FINISHED)
|
||||||
|
addAction(ACTION_DOWNLOAD_FAILED)
|
||||||
|
addAction(ACTION_DOWNLOAD_PROGRESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextCompat.registerReceiver(requireContext(), downloadStatusReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||||
|
|
||||||
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
||||||
screenWidth = resources.displayMetrics.widthPixels.dp
|
screenWidth = resources.displayMetrics.widthPixels.dp
|
||||||
|
|
||||||
|
|
||||||
var maxGridSize = (screenWidth / 100f).roundToInt()
|
var maxGridSize = (screenWidth / 100f).roundToInt()
|
||||||
maxGridSize = max(4, maxGridSize - (maxGridSize % 2))
|
maxGridSize = max(4, maxGridSize - (maxGridSize % 2))
|
||||||
|
|
||||||
@@ -116,8 +160,13 @@ open class MangaReadFragment : Fragment() {
|
|||||||
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
||||||
|
|
||||||
headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!)
|
headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!)
|
||||||
|
headerAdapter.scanlatorSelectionListener = this
|
||||||
chapterAdapter = MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
|
chapterAdapter = MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
|
||||||
|
|
||||||
|
for (download in downloadManager.mangaDownloads){
|
||||||
|
chapterAdapter.stopDownload(download.chapter)
|
||||||
|
}
|
||||||
|
|
||||||
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, chapterAdapter)
|
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, chapterAdapter)
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
@@ -134,45 +183,70 @@ open class MangaReadFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model.getMangaChapters().observe(viewLifecycleOwner) { loadedChapters ->
|
model.getMangaChapters().observe(viewLifecycleOwner) { _ ->
|
||||||
if (loadedChapters != null) {
|
updateChapters()
|
||||||
val chapters = loadedChapters[media.selected!!.sourceIndex]
|
}
|
||||||
if (chapters != null) {
|
}
|
||||||
media.manga?.chapters = chapters
|
|
||||||
|
|
||||||
//CHIP GROUP
|
override fun onScanlatorsSelected() {
|
||||||
val total = chapters.size
|
updateChapters()
|
||||||
val divisions = total.toDouble() / 10
|
}
|
||||||
start = 0
|
|
||||||
end = null
|
|
||||||
val limit = when {
|
|
||||||
(divisions < 25) -> 25
|
|
||||||
(divisions < 50) -> 50
|
|
||||||
else -> 100
|
|
||||||
}
|
|
||||||
headerAdapter.clearChips()
|
|
||||||
if (total > limit) {
|
|
||||||
val arr = chapters.keys.toTypedArray()
|
|
||||||
val stored = ceil((total).toDouble() / limit).toInt()
|
|
||||||
val position = clamp(media.selected!!.chip, 0, stored - 1)
|
|
||||||
val last = if (position + 1 == stored) total else (limit * (position + 1))
|
|
||||||
start = limit * (position)
|
|
||||||
end = last - 1
|
|
||||||
headerAdapter.updateChips(
|
|
||||||
limit,
|
|
||||||
arr,
|
|
||||||
(1..stored).toList().toTypedArray(),
|
|
||||||
position
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
headerAdapter.subscribeButton(true)
|
private fun updateChapters() {
|
||||||
reload()
|
val loadedChapters = model.getMangaChapters().value
|
||||||
|
if (loadedChapters != null) {
|
||||||
|
val chapters = loadedChapters[media.selected!!.sourceIndex]
|
||||||
|
if (chapters != null) {
|
||||||
|
headerAdapter.options = getScanlators(chapters)
|
||||||
|
val filteredChapters = chapters.filterNot { (_, chapter) ->
|
||||||
|
chapter.scanlator in headerAdapter.hiddenScanlators
|
||||||
}
|
}
|
||||||
|
|
||||||
|
media.manga?.chapters = filteredChapters.toMutableMap()
|
||||||
|
|
||||||
|
//CHIP GROUP
|
||||||
|
val total = filteredChapters.size
|
||||||
|
val divisions = total.toDouble() / 10
|
||||||
|
start = 0
|
||||||
|
end = null
|
||||||
|
val limit = when {
|
||||||
|
(divisions < 25) -> 25
|
||||||
|
(divisions < 50) -> 50
|
||||||
|
else -> 100
|
||||||
|
}
|
||||||
|
headerAdapter.clearChips()
|
||||||
|
if (total > limit) {
|
||||||
|
val arr = filteredChapters.keys.toTypedArray()
|
||||||
|
val stored = ceil((total).toDouble() / limit).toInt()
|
||||||
|
val position = clamp(media.selected!!.chip, 0, stored - 1)
|
||||||
|
val last = if (position + 1 == stored) total else (limit * (position + 1))
|
||||||
|
start = limit * (position)
|
||||||
|
end = last - 1
|
||||||
|
headerAdapter.updateChips(
|
||||||
|
limit,
|
||||||
|
arr,
|
||||||
|
(1..stored).toList().toTypedArray(),
|
||||||
|
position
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
headerAdapter.subscribeButton(true)
|
||||||
|
reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getScanlators(chap: MutableMap<String, MangaChapter>?): List<String> {
|
||||||
|
val scanlators = mutableListOf<String>()
|
||||||
|
if (chap != null) {
|
||||||
|
val chapters = chap.values
|
||||||
|
for (chapter in chapters) {
|
||||||
|
scanlators.add(chapter.scanlator ?: "Unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scanlators.distinct()
|
||||||
|
}
|
||||||
|
|
||||||
fun onSourceChange(i: Int): MangaParser {
|
fun onSourceChange(i: Int): MangaParser {
|
||||||
media.manga?.chapters = null
|
media.manga?.chapters = null
|
||||||
reload()
|
reload()
|
||||||
@@ -185,8 +259,16 @@ open class MangaReadFragment : Fragment() {
|
|||||||
return model.mangaReadSources?.get(i)!!
|
return model.mangaReadSources?.get(i)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadChapters(i: Int) {
|
fun onLangChange(i: Int) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) { model.loadMangaChapters(media, i) }
|
val selected = model.loadSelected(media)
|
||||||
|
selected.langIndex = i
|
||||||
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
|
media.selected = selected
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun loadChapters(i: Int, invalidate: Boolean) {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) { model.loadMangaChapters(media, i, invalidate) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onIconPressed(viewType: Int, rev: Boolean) {
|
fun onIconPressed(viewType: Int, rev: Boolean) {
|
||||||
@@ -225,6 +307,75 @@ open class MangaReadFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openSettings(pkg: MangaExtension.Installed){
|
||||||
|
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
||||||
|
val activity = requireActivity() as MediaDetailsActivity
|
||||||
|
val visibility = if (show) View.VISIBLE else View.GONE
|
||||||
|
activity.findViewById<AppBarLayout>(R.id.mediaAppBar).visibility = visibility
|
||||||
|
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility
|
||||||
|
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility
|
||||||
|
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility
|
||||||
|
try{
|
||||||
|
activity.findViewById<CustomBottomNavBar>(R.id.mediaTab).visibility = visibility
|
||||||
|
}catch (e: ClassCastException){
|
||||||
|
activity.findViewById<NavigationRailView>(R.id.mediaTab).visibility = visibility
|
||||||
|
}
|
||||||
|
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||||
|
if (show) View.GONE else View.VISIBLE
|
||||||
|
}
|
||||||
|
val allSettings = pkg.sources.filterIsInstance<ConfigurableSource>()
|
||||||
|
if (allSettings.isNotEmpty()) {
|
||||||
|
var selectedSetting = allSettings[0]
|
||||||
|
if (allSettings.size > 1) {
|
||||||
|
val names = allSettings.map { it.lang }.toTypedArray()
|
||||||
|
var selectedIndex = 0
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Select a Source")
|
||||||
|
.setSingleChoiceItems(names, selectedIndex) { _, which ->
|
||||||
|
selectedIndex = which
|
||||||
|
}
|
||||||
|
.setPositiveButton("OK") { dialog, _ ->
|
||||||
|
selectedSetting = allSettings[selectedIndex]
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
|
// Move the fragment transaction here
|
||||||
|
val fragment =
|
||||||
|
MangaSourcePreferencesFragment().getInstance(selectedSetting.id){
|
||||||
|
changeUIVisibility(true)
|
||||||
|
loadChapters(media.selected!!.sourceIndex, true)
|
||||||
|
}
|
||||||
|
parentFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
changeUIVisibility(true)
|
||||||
|
return@setNegativeButton
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
// If there's only one setting, proceed with the fragment transaction
|
||||||
|
val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id){
|
||||||
|
changeUIVisibility(true)
|
||||||
|
loadChapters(media.selected!!.sourceIndex, true)
|
||||||
|
}
|
||||||
|
parentFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
changeUIVisibility(false)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onMangaChapterClick(i: String) {
|
fun onMangaChapterClick(i: String) {
|
||||||
model.continueMedia = false
|
model.continueMedia = false
|
||||||
media.manga?.chapters?.get(i)?.let {
|
media.manga?.chapters?.get(i)?.let {
|
||||||
@@ -234,6 +385,112 @@ open class MangaReadFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onMangaChapterDownloadClick(i: String) {
|
||||||
|
if (!isNotificationPermissionGranted()) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
requireActivity(),
|
||||||
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.continueMedia = false
|
||||||
|
media.manga?.chapters?.get(i)?.let { chapter ->
|
||||||
|
val parser = model.mangaReadSources?.get(media.selected!!.sourceIndex) as? DynamicMangaParser
|
||||||
|
parser?.let {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val images = parser.imageList("", chapter.sChapter)
|
||||||
|
|
||||||
|
// Create a download task
|
||||||
|
val downloadTask = MangaDownloaderService.DownloadTask(
|
||||||
|
title = media.nameMAL ?: "",
|
||||||
|
chapter = chapter.title!!,
|
||||||
|
imageData = images,
|
||||||
|
sourceMedia = media,
|
||||||
|
retries = 2,
|
||||||
|
simultaneousDownloads = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
ServiceDataSingleton.downloadQueue.offer(downloadTask)
|
||||||
|
|
||||||
|
// If the service is not already running, start it
|
||||||
|
if (!ServiceDataSingleton.isServiceRunning) {
|
||||||
|
val intent = Intent(context, MangaDownloaderService::class.java)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
ContextCompat.startForegroundService(requireContext(), intent)
|
||||||
|
}
|
||||||
|
ServiceDataSingleton.isServiceRunning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform the adapter that the download has started
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
chapterAdapter.startDownload(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isNotificationPermissionGranted(): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
return ActivityCompat.checkSelfPermission(
|
||||||
|
requireContext(),
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun onMangaChapterRemoveDownloadClick(i: String){
|
||||||
|
downloadManager.removeDownload(Download(media.nameMAL!!, i, Download.Type.MANGA))
|
||||||
|
chapterAdapter.deleteDownload(i)
|
||||||
|
}
|
||||||
|
fun onMangaChapterStopDownloadClick(i: String) {
|
||||||
|
val cancelIntent = Intent().apply {
|
||||||
|
action = MangaDownloaderService.ACTION_CANCEL_DOWNLOAD
|
||||||
|
putExtra(MangaDownloaderService.EXTRA_CHAPTER, i)
|
||||||
|
}
|
||||||
|
requireContext().sendBroadcast(cancelIntent)
|
||||||
|
|
||||||
|
// Remove the download from the manager and update the UI
|
||||||
|
downloadManager.removeDownload(Download(media.nameMAL!!, i, Download.Type.MANGA))
|
||||||
|
chapterAdapter.stopDownload(i)
|
||||||
|
}
|
||||||
|
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
when (intent.action) {
|
||||||
|
ACTION_DOWNLOAD_STARTED -> {
|
||||||
|
val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER)
|
||||||
|
chapterNumber?.let { chapterAdapter.startDownload(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_DOWNLOAD_FINISHED -> {
|
||||||
|
val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER)
|
||||||
|
chapterNumber?.let { chapterAdapter.stopDownload(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_DOWNLOAD_FAILED -> {
|
||||||
|
val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER)
|
||||||
|
chapterNumber?.let {
|
||||||
|
chapterAdapter.removeDownload(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_DOWNLOAD_PROGRESS -> {
|
||||||
|
val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER)
|
||||||
|
val progress = intent.getIntExtra("progress", 0)
|
||||||
|
chapterNumber?.let {
|
||||||
|
chapterAdapter.updateDownloadProgress(it, progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
private fun reload() {
|
private fun reload() {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
@@ -262,6 +519,7 @@ open class MangaReadFragment : Fragment() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
model.mangaReadSources?.flushText()
|
model.mangaReadSources?.flushText()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
requireContext().unregisterReceiver(downloadStatusReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var state: Parcelable? = null
|
private var state: Parcelable? = null
|
||||||
@@ -275,4 +533,12 @@ open class MangaReadFragment : Fragment() {
|
|||||||
super.onPause()
|
super.onPause()
|
||||||
state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState()
|
state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ACTION_DOWNLOAD_STARTED = "ani.dantotsu.ACTION_DOWNLOAD_STARTED"
|
||||||
|
const val ACTION_DOWNLOAD_FINISHED = "ani.dantotsu.ACTION_DOWNLOAD_FINISHED"
|
||||||
|
const val ACTION_DOWNLOAD_FAILED = "ani.dantotsu.ACTION_DOWNLOAD_FAILED"
|
||||||
|
const val ACTION_DOWNLOAD_PROGRESS = "ani.dantotsu.ACTION_DOWNLOAD_PROGRESS"
|
||||||
|
const val EXTRA_CHAPTER_NUMBER = "extra_chapter_number"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
import android.net.Uri
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -29,6 +30,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import ani.dantotsu.media.manga.MangaCache
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
abstract class BaseImageAdapter(
|
abstract class BaseImageAdapter(
|
||||||
val activity: MangaReaderActivity,
|
val activity: MangaReaderActivity,
|
||||||
@@ -116,7 +118,7 @@ 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_old(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? {
|
suspend fun Context.loadBitmap_old(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? { //still used in some places
|
||||||
return tryWithSuspend {
|
return tryWithSuspend {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Glide.with(this@loadBitmap_old)
|
Glide.with(this@loadBitmap_old)
|
||||||
@@ -151,8 +153,10 @@ abstract class BaseImageAdapter(
|
|||||||
Glide.with(this@loadBitmap)
|
Glide.with(this@loadBitmap)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.let {
|
.let {
|
||||||
if (link.url.startsWith("file://")) {
|
val fileUri = Uri.fromFile(File(link.url)).toString()
|
||||||
it.load(link.url)
|
val localFile = File(link.url)
|
||||||
|
if (localFile.exists()) {
|
||||||
|
it.load(localFile.absoluteFile)
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package ani.dantotsu.media.manga.mangareader
|
package ani.dantotsu.media.manga.mangareader
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
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
|
||||||
@@ -45,7 +47,7 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
|
|||||||
loaded = true
|
loaded = true
|
||||||
binding.selectorAutoText.text = chp.title
|
binding.selectorAutoText.text = chp.title
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
if(model.loadMangaChapterImages(chp, m.selected!!)) {
|
if(model.loadMangaChapterImages(chp, m.selected!!, m.nameMAL!!)) {
|
||||||
val activity = currActivity()
|
val activity = currActivity()
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
tryWith { dismiss() }
|
tryWith { dismiss() }
|
||||||
@@ -63,6 +65,12 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
|
|||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
_binding = BottomSheetSelectorBinding.inflate(inflater, container, false)
|
_binding = BottomSheetSelectorBinding.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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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
|
||||||
@@ -126,7 +127,8 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityMangaReaderBinding.inflate(layoutInflater)
|
binding = ActivityMangaReaderBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -215,6 +217,10 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//check that index is not out of bounds (crash fix)
|
||||||
|
if (media.selected!!.sourceIndex >= model.mangaReadSources!!.names.size) {
|
||||||
|
media.selected!!.sourceIndex = 0
|
||||||
|
}
|
||||||
binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex]
|
binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex]
|
||||||
|
|
||||||
binding.mangaReaderTitle.text = media.userPreferredName
|
binding.mangaReaderTitle.text = media.userPreferredName
|
||||||
@@ -311,7 +317,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) { model.loadMangaChapterImages(chapter, media.selected!!) }
|
scope.launch(Dispatchers.IO) { model.loadMangaChapterImages(chapter, media.selected!!, media.nameMAL!!) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val snapHelper = PagerSnapHelper()
|
private val snapHelper = PagerSnapHelper()
|
||||||
@@ -700,6 +706,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||||||
model.loadMangaChapterImages(
|
model.loadMangaChapterImages(
|
||||||
chapters[chaptersArr.getOrNull(currentChapterIndex + 1) ?: return@launch]!!,
|
chapters[chaptersArr.getOrNull(currentChapterIndex + 1) ?: return@launch]!!,
|
||||||
media.selected!!,
|
media.selected!!,
|
||||||
|
media.nameMAL!!,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
loading = false
|
loading = false
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ 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.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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
|
||||||
@@ -136,7 +137,8 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityNovelReaderBinding.inflate(layoutInflater)
|
binding = ActivityNovelReaderBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import ani.dantotsu.databinding.ActivityListBinding
|
|||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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
|
||||||
@@ -32,25 +33,29 @@ 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()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityListBinding.inflate(layoutInflater)
|
binding = ActivityListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorOnPrimary, typedValue, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true)
|
||||||
val primaryColor = typedValue.data
|
val primaryColor = typedValue.data
|
||||||
val typedValue2 = TypedValue()
|
val typedValue2 = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue2, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorOnBackground, typedValue2, true)
|
||||||
val primaryTextColor = typedValue2.data
|
val titleTextColor = typedValue2.data
|
||||||
val typedValue3 = TypedValue()
|
val typedValue3 = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorSecondary, typedValue3, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue3, true)
|
||||||
val secondaryColor = typedValue3.data
|
val primaryTextColor = typedValue3.data
|
||||||
|
val typedValue4 = TypedValue()
|
||||||
|
theme.resolveAttribute(com.google.android.material.R.attr.colorOutline, typedValue4, true)
|
||||||
|
val secondaryTextColor = typedValue4.data
|
||||||
|
|
||||||
window.statusBarColor = primaryColor
|
window.statusBarColor = primaryColor
|
||||||
window.navigationBarColor = primaryColor
|
window.navigationBarColor = primaryColor
|
||||||
binding.listTabLayout.setBackgroundColor(primaryColor)
|
binding.listTabLayout.setBackgroundColor(primaryColor)
|
||||||
binding.listAppBar.setBackgroundColor(primaryColor)
|
binding.listAppBar.setBackgroundColor(primaryColor)
|
||||||
binding.listTitle.setTextColor(primaryTextColor)
|
binding.listTitle.setTextColor(titleTextColor)
|
||||||
binding.listTabLayout.setTabTextColors(primaryTextColor, primaryTextColor)
|
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
|
||||||
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
||||||
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||||
if (!uiSettings.immersiveMode) {
|
if (!uiSettings.immersiveMode) {
|
||||||
@@ -126,4 +131,4 @@ class ListActivity : AppCompatActivity() {
|
|||||||
popup.show()
|
popup.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
|
||||||
class ListFragment : Fragment() {
|
class ListFragment : Fragment() {
|
||||||
private var _binding: FragmentListBinding? = null
|
private var _binding: FragmentListBinding? = null
|
||||||
|
|||||||
29
app/src/main/java/ani/dantotsu/offline/OfflineFragment.kt
Normal file
29
app/src/main/java/ani/dantotsu/offline/OfflineFragment.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package ani.dantotsu.offline
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import ani.dantotsu.databinding.FragmentOfflineBinding
|
||||||
|
import ani.dantotsu.isOnline
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.startMainActivity
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
|
||||||
|
class OfflineFragment : Fragment() {
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val binding = FragmentOfflineBinding.inflate(inflater, container, false)
|
||||||
|
binding.refreshContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
topMargin = statusBarHeight
|
||||||
|
bottomMargin = navBarHeight
|
||||||
|
}
|
||||||
|
binding.refreshButton.setOnClickListener {
|
||||||
|
if (isOnline(requireContext())) {
|
||||||
|
startMainActivity(requireActivity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import android.content.IntentFilter
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
@@ -95,8 +96,9 @@ object AppUpdater {
|
|||||||
|
|
||||||
private fun compareVersion(version: String): Boolean {
|
private fun compareVersion(version: String): Boolean {
|
||||||
|
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG) {
|
||||||
return BuildConfig.VERSION_NAME != version
|
return BuildConfig.VERSION_NAME != version
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
fun toDouble(list: List<String>): Double {
|
fun toDouble(list: List<String>): Double {
|
||||||
return list.mapIndexed { i: Int, s: String ->
|
return list.mapIndexed { i: Int, s: String ->
|
||||||
@@ -141,7 +143,8 @@ object AppUpdater {
|
|||||||
-1
|
-1
|
||||||
}
|
}
|
||||||
if (id == -1L) return true
|
if (id == -1L) return true
|
||||||
registerReceiver(
|
ContextCompat.registerReceiver(
|
||||||
|
this,
|
||||||
object : BroadcastReceiver() {
|
object : BroadcastReceiver() {
|
||||||
@SuppressLint("Range")
|
@SuppressLint("Range")
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
@@ -170,7 +173,8 @@ object AppUpdater {
|
|||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
||||||
|
ContextCompat.RECEIVER_NOT_EXPORTED
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
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
|
||||||
@@ -74,15 +75,16 @@ 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_old(image, trans1 ?: listOf())
|
var bitmap = context.loadBitmap_old(image, trans1 ?: listOf())
|
||||||
var bitmap2 = if (image2 != null) requireContext().loadBitmap_old(image2, trans2 ?: listOf()) else null
|
var bitmap2 = if (image2 != null) context.loadBitmap_old(image2, trans2 ?: listOf()) else null
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
bitmap = requireContext().loadBitmap(image, trans1 ?: listOf())
|
bitmap = context.loadBitmap(image, trans1 ?: listOf())
|
||||||
bitmap2 = if (image2 != null) requireContext().loadBitmap(image2, trans2 ?: listOf()) else null
|
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
|
||||||
|
|||||||
24
app/src/main/java/ani/dantotsu/others/LangSet.kt
Normal file
24
app/src/main/java/ani/dantotsu/others/LangSet.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package ani.dantotsu.others
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LangSet {
|
||||||
|
companion object{
|
||||||
|
fun setLocale(activity: Activity) {
|
||||||
|
val useCursedLang = activity.getSharedPreferences("Dantotsu", Activity.MODE_PRIVATE).getBoolean("use_cursed_lang", false)
|
||||||
|
if(!useCursedLang) return
|
||||||
|
val locale = Locale("en", "rDW")
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
val resources: Resources = activity.resources
|
||||||
|
val config: Configuration = resources.configuration
|
||||||
|
config.setLocale(locale)
|
||||||
|
resources.updateConfiguration(config, resources.displayMetrics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ 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.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -47,7 +48,8 @@ class ImageSearchActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityImageSearchBinding.inflate(layoutInflater)
|
binding = ActivityImageSearchBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ani.dantotsu.parsers
|
|||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -10,14 +11,20 @@ import android.os.Build
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException
|
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypassException
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.download.manga.MangaDownloaderService
|
||||||
|
import ani.dantotsu.download.manga.ServiceDataSingleton
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.logger
|
||||||
import ani.dantotsu.media.manga.ImageData
|
import ani.dantotsu.media.manga.ImageData
|
||||||
import ani.dantotsu.media.manga.MangaCache
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
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
|
||||||
@@ -62,22 +69,54 @@ class AniyomiAdapter {
|
|||||||
|
|
||||||
class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
||||||
val extension: AnimeExtension.Installed
|
val extension: AnimeExtension.Installed
|
||||||
|
var sourceLanguage = 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.extension = extension
|
this.extension = extension
|
||||||
}
|
}
|
||||||
|
|
||||||
override val name = extension.name
|
override val name = extension.name
|
||||||
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 val isNSFW = extension.isNsfw
|
||||||
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> {
|
override suspend fun loadEpisodes(
|
||||||
val source = extension.sources.first()
|
animeLink: String,
|
||||||
|
extra: Map<String, String>?,
|
||||||
|
sAnime: SAnime
|
||||||
|
): List<Episode> {
|
||||||
|
val source = try {
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sourceLanguage = 0
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
}
|
||||||
if (source is AnimeCatalogueSource) {
|
if (source is AnimeCatalogueSource) {
|
||||||
try {
|
try {
|
||||||
val res = source.getEpisodeList(sAnime)
|
val res = source.getEpisodeList(sAnime)
|
||||||
|
|
||||||
// Sort episodes by episode_number
|
val sortedEpisodes = if (res[0].episode_number == -1f) {
|
||||||
val sortedEpisodes = res.sortedBy { it.episode_number }
|
// Find the number in the string and sort by that number
|
||||||
|
val sortedByStringNumber = res.sortedBy {
|
||||||
|
val matchResult = "\\d+".toRegex().find(it.name)
|
||||||
|
val number = matchResult?.value?.toFloat() ?: Float.MAX_VALUE
|
||||||
|
it.episode_number = number // Store the found number in episode_number
|
||||||
|
number
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no number, reverse the order and give them an incrementing number
|
||||||
|
var incrementingNumber = 1f
|
||||||
|
sortedByStringNumber.map {
|
||||||
|
if (it.episode_number == Float.MAX_VALUE) {
|
||||||
|
it.episode_number =
|
||||||
|
incrementingNumber++ // Update episode_number with the incrementing number
|
||||||
|
}
|
||||||
|
it
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Sort by the episode_number field
|
||||||
|
res.sortedBy { it.episode_number }
|
||||||
|
}
|
||||||
|
|
||||||
// Transform SEpisode objects to Episode objects
|
// Transform SEpisode objects to Episode objects
|
||||||
|
|
||||||
@@ -90,8 +129,17 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||||||
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(
|
||||||
val source = extension.sources.first() as? AnimeCatalogueSource ?: return emptyList()
|
episodeLink: String,
|
||||||
|
extra: Map<String, String>?,
|
||||||
|
sEpisode: SEpisode
|
||||||
|
): List<VideoServer> {
|
||||||
|
val source = try {
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sourceLanguage = 0
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} as? AnimeCatalogueSource ?: return emptyList()
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val videos = source.getVideoList(sEpisode)
|
val videos = source.getVideoList(sEpisode)
|
||||||
@@ -103,20 +151,25 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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() as? AnimeCatalogueSource ?: return emptyList()
|
val source = try {
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sourceLanguage = 0
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} as? AnimeCatalogueSource ?: return emptyList()
|
||||||
return try {
|
return try {
|
||||||
val res = source.fetchSearchAnime(1, query, AnimeFilterList()).toBlocking().first()
|
val res = source.fetchSearchAnime(1, query, AnimeFilterList()).toBlocking().first()
|
||||||
convertAnimesPageToShowResponse(res)
|
convertAnimesPageToShowResponse(res)
|
||||||
} catch (e: CloudflareBypassException) {
|
} catch (e: CloudflareBypassException) {
|
||||||
logger("Exception in search: $e")
|
logger("Exception in search: $e")
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
Toast.makeText(currContext(), "Failed to bypass Cloudflare", Toast.LENGTH_SHORT).show()
|
Toast.makeText(currContext(), "Failed to bypass Cloudflare", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
emptyList()
|
emptyList()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -150,7 +203,11 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||||||
sEpisode.episode_number
|
sEpisode.episode_number
|
||||||
}
|
}
|
||||||
return Episode(
|
return Episode(
|
||||||
episodeNumberInt.toString(),
|
if (episodeNumberInt.toInt() != -1) {
|
||||||
|
episodeNumberInt.toString()
|
||||||
|
} else {
|
||||||
|
sEpisode.name
|
||||||
|
},
|
||||||
sEpisode.url,
|
sEpisode.url,
|
||||||
sEpisode.name,
|
sEpisode.name,
|
||||||
null,
|
null,
|
||||||
@@ -174,16 +231,28 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||||||
class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
||||||
val mangaCache = Injekt.get<MangaCache>()
|
val mangaCache = Injekt.get<MangaCache>()
|
||||||
val extension: MangaExtension.Installed
|
val extension: MangaExtension.Installed
|
||||||
|
var sourceLanguage = 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.extension = extension
|
this.extension = extension
|
||||||
}
|
}
|
||||||
|
|
||||||
override val name = extension.name
|
override val name = extension.name
|
||||||
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 isNSFW = extension.isNsfw
|
override val isNSFW = extension.isNsfw
|
||||||
|
|
||||||
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> {
|
override suspend fun loadChapters(
|
||||||
val source = extension.sources.first() as? CatalogueSource ?: return emptyList()
|
mangaLink: String,
|
||||||
|
extra: Map<String, String>?,
|
||||||
|
sManga: SManga
|
||||||
|
): List<MangaChapter> {
|
||||||
|
val source = try {
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sourceLanguage = 0
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} as? HttpSource ?: return emptyList()
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val res = source.getChapterList(sManga)
|
val res = source.getChapterList(sManga)
|
||||||
@@ -201,32 +270,79 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
|
|
||||||
|
|
||||||
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> {
|
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> {
|
||||||
val source = extension.sources.first() as? HttpSource ?: return emptyList()
|
val source = try {
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
return coroutineScope {
|
} catch (e: Exception) {
|
||||||
|
sourceLanguage = 0
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} as? HttpSource ?: return emptyList()
|
||||||
|
var imageDataList: List<ImageData> = listOf()
|
||||||
|
val ret = coroutineScope {
|
||||||
try {
|
try {
|
||||||
println("source.name " + source.name)
|
println("source.name " + source.name)
|
||||||
val res = source.getPageList(sChapter)
|
val res = source.getPageList(sChapter)
|
||||||
val reIndexedPages = res.mapIndexed { index, page -> Page(index, page.url, page.imageUrl, page.uri) }
|
val reIndexedPages =
|
||||||
|
res.mapIndexed { index, page -> Page(index, page.url, page.imageUrl, page.uri) }
|
||||||
|
|
||||||
val deferreds = reIndexedPages.map { page ->
|
val deferreds = reIndexedPages.map { page ->
|
||||||
async(Dispatchers.IO) {
|
async(Dispatchers.IO) {
|
||||||
mangaCache.put(page.imageUrl ?: "", ImageData(page, source))
|
mangaCache.put(page.imageUrl ?: "", ImageData(page, source))
|
||||||
|
imageDataList += ImageData(page, source)
|
||||||
logger("put page: ${page.imageUrl}")
|
logger("put page: ${page.imageUrl}")
|
||||||
pageToMangaImage(page)
|
pageToMangaImage(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deferreds.awaitAll()
|
deferreds.awaitAll()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger("loadImages Exception: $e")
|
logger("loadImages Exception: $e")
|
||||||
Toast.makeText(currContext(), "Failed to load images: $e", Toast.LENGTH_SHORT).show()
|
Toast.makeText(currContext(), "Failed to load images: $e", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchAndProcessImage(page: Page, httpSource: HttpSource, context: Context): Bitmap? {
|
suspend fun imageList(chapterLink: String, sChapter: SChapter): List<ImageData>{
|
||||||
|
val source = try {
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sourceLanguage = 0
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} as? HttpSource ?: return emptyList()
|
||||||
|
var imageDataList: List<ImageData> = listOf()
|
||||||
|
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) {
|
||||||
|
imageDataList += ImageData(page, source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deferreds.awaitAll()
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger("loadImages Exception: $e")
|
||||||
|
Toast.makeText(currContext(), "Failed to load images: $e", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return imageDataList
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchAndProcessImage(
|
||||||
|
page: Page,
|
||||||
|
httpSource: HttpSource,
|
||||||
|
context: Context
|
||||||
|
): Bitmap? {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
// Fetch the image
|
// Fetch the image
|
||||||
@@ -259,7 +375,6 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun fetchAndSaveImage(page: Page, httpSource: HttpSource, contentResolver: ContentResolver) {
|
fun fetchAndSaveImage(page: Page, httpSource: HttpSource, contentResolver: ContentResolver) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
@@ -274,7 +389,13 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
// Save the Bitmap using MediaStore API
|
// Save the Bitmap using MediaStore API
|
||||||
saveImage(bitmap, contentResolver, "image_${System.currentTimeMillis()}.jpg", Bitmap.CompressFormat.JPEG, 100)
|
saveImage(
|
||||||
|
bitmap,
|
||||||
|
contentResolver,
|
||||||
|
"image_${System.currentTimeMillis()}.jpg",
|
||||||
|
Bitmap.CompressFormat.JPEG,
|
||||||
|
100
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputStream?.close()
|
inputStream?.close()
|
||||||
@@ -285,16 +406,28 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String, format: Bitmap.CompressFormat, quality: Int) {
|
fun saveImage(
|
||||||
|
bitmap: Bitmap,
|
||||||
|
contentResolver: ContentResolver,
|
||||||
|
filename: String,
|
||||||
|
format: Bitmap.CompressFormat,
|
||||||
|
quality: Int
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
val contentValues = ContentValues().apply {
|
val contentValues = ContentValues().apply {
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||||
put(MediaStore.MediaColumns.MIME_TYPE, "image/${format.name.lowercase()}")
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/${format.name.lowercase()}")
|
||||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Anime")
|
put(
|
||||||
|
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||||
|
"${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Anime"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
val uri: Uri? = contentResolver.insert(
|
||||||
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||||
|
contentValues
|
||||||
|
)
|
||||||
|
|
||||||
uri?.let {
|
uri?.let {
|
||||||
contentResolver.openOutputStream(it)?.use { os ->
|
contentResolver.openOutputStream(it)?.use { os ->
|
||||||
@@ -302,7 +435,8 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Anime")
|
val directory =
|
||||||
|
File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Anime")
|
||||||
if (!directory.exists()) {
|
if (!directory.exists()) {
|
||||||
directory.mkdirs()
|
directory.mkdirs()
|
||||||
}
|
}
|
||||||
@@ -319,9 +453,13 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun search(query: String): List<ShowResponse> {
|
override suspend fun search(query: String): List<ShowResponse> {
|
||||||
val source = extension.sources.first() as? HttpSource ?: return emptyList()
|
val source = try {
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sourceLanguage = 0
|
||||||
|
extension.sources[sourceLanguage]
|
||||||
|
} as? HttpSource ?: return emptyList()
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val res = source.fetchSearchManga(1, query, FilterList()).toBlocking().first()
|
val res = source.fetchSearchManga(1, query, FilterList()).toBlocking().first()
|
||||||
@@ -330,7 +468,8 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
} catch (e: CloudflareBypassException) {
|
} catch (e: CloudflareBypassException) {
|
||||||
logger("Exception in search: $e")
|
logger("Exception in search: $e")
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
Toast.makeText(currContext(), "Failed to bypass Cloudflare", Toast.LENGTH_SHORT).show()
|
Toast.makeText(currContext(), "Failed to bypass Cloudflare", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
emptyList()
|
emptyList()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -390,30 +529,21 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
|
|
||||||
|
|
||||||
private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter {
|
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(
|
return MangaChapter(
|
||||||
sChapter.name,
|
sChapter.name,
|
||||||
sChapter.url,
|
sChapter.url,
|
||||||
//if (parsedChapterTitle.first != null || parsedChapterTitle.second != null) {
|
sChapter.name,
|
||||||
// parsedChapterTitle.third
|
|
||||||
//} else {
|
|
||||||
sChapter.name,
|
|
||||||
//},
|
|
||||||
null,
|
null,
|
||||||
|
sChapter.scanlator,
|
||||||
sChapter
|
sChapter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseChapterTitle(title: String): Triple<String?, String?, String> {
|
fun parseChapterTitle(title: String): Triple<String?, String?, String> {
|
||||||
val volumePattern = Pattern.compile("(?:vol\\.?|v|volume\\s?)(\\d+)", Pattern.CASE_INSENSITIVE)
|
val volumePattern =
|
||||||
val chapterPattern = Pattern.compile("(?:ch\\.?|chapter\\s?)(\\d+)", Pattern.CASE_INSENSITIVE)
|
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 volumeMatcher = volumePattern.matcher(title)
|
||||||
val chapterMatcher = chapterPattern.matcher(title)
|
val chapterMatcher = chapterPattern.matcher(title)
|
||||||
@@ -423,10 +553,12 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||||||
|
|
||||||
var remainingTitle = title
|
var remainingTitle = title
|
||||||
if (volumeNumber != null) {
|
if (volumeNumber != null) {
|
||||||
remainingTitle = volumeMatcher.group(0)?.let { remainingTitle.replace(it, "") }.toString()
|
remainingTitle =
|
||||||
|
volumeMatcher.group(0)?.let { remainingTitle.replace(it, "") }.toString()
|
||||||
}
|
}
|
||||||
if (chapterNumber != null) {
|
if (chapterNumber != null) {
|
||||||
remainingTitle = chapterMatcher.group(0)?.let { remainingTitle.replace(it, "") }.toString()
|
remainingTitle =
|
||||||
|
chapterMatcher.group(0)?.let { remainingTitle.replace(it, "") }.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Triple(volumeNumber, chapterNumber, remainingTitle.trim())
|
return Triple(volumeNumber, chapterNumber, remainingTitle.trim())
|
||||||
@@ -449,7 +581,7 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
// Find the number value from the .quality string
|
// Find the number value from the .quality string
|
||||||
val number = Regex("""\d+""").find(aniVideo.quality)?.value?.toInt() ?: 0
|
val number = Regex("""\d+""").find(aniVideo.quality)?.value?.toInt() ?: 0
|
||||||
|
|
||||||
@@ -479,9 +611,11 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||||||
// 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")
|
||||||
throw Exception("Unknown video format")
|
FirebaseCrashlytics.getInstance().recordException(Exception("Unknown video format: $videoUrl"))
|
||||||
|
format = VideoType.CONTAINER
|
||||||
}
|
}
|
||||||
val headersMap: Map<String, String> = aniVideo.headers?.toMultimap()?.mapValues { it.value.joinToString() } ?: mapOf()
|
val headersMap: Map<String, String> =
|
||||||
|
aniVideo.headers?.toMultimap()?.mapValues { it.value.joinToString() } ?: mapOf()
|
||||||
|
|
||||||
|
|
||||||
return ani.dantotsu.parsers.Video(
|
return ani.dantotsu.parsers.Video(
|
||||||
@@ -494,7 +628,11 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||||||
|
|
||||||
private fun getVideoType(fileName: String): VideoType? {
|
private fun getVideoType(fileName: String): VideoType? {
|
||||||
return when {
|
return when {
|
||||||
fileName.endsWith(".mp4", ignoreCase = true) || fileName.endsWith(".mkv", ignoreCase = true) -> VideoType.CONTAINER
|
fileName.endsWith(".mp4", ignoreCase = true) || fileName.endsWith(
|
||||||
|
".mkv",
|
||||||
|
ignoreCase = true
|
||||||
|
) -> VideoType.CONTAINER
|
||||||
|
|
||||||
fileName.endsWith(".m3u8", ignoreCase = true) -> VideoType.M3U8
|
fileName.endsWith(".m3u8", ignoreCase = true) -> VideoType.M3U8
|
||||||
fileName.endsWith(".mpd", ignoreCase = true) -> VideoType.DASH
|
fileName.endsWith(".mpd", ignoreCase = true) -> VideoType.DASH
|
||||||
else -> null
|
else -> null
|
||||||
@@ -507,12 +645,12 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
type = findSubtitleType(track.url)
|
type = findSubtitleType(track.url)
|
||||||
}
|
}
|
||||||
return Subtitle(track.lang, track.url, type?: SubtitleType.SRT)
|
return Subtitle(track.lang, track.url, type ?: SubtitleType.SRT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findSubtitleType(url: String): SubtitleType? {
|
private fun findSubtitleType(url: String): SubtitleType? {
|
||||||
// First, try to determine the type based on the URL file extension
|
// First, try to determine the type based on the URL file extension
|
||||||
var type: SubtitleType? = when {
|
val type: SubtitleType? = when {
|
||||||
url.endsWith(".vtt", true) -> SubtitleType.VTT
|
url.endsWith(".vtt", true) -> SubtitleType.VTT
|
||||||
url.endsWith(".ass", true) -> SubtitleType.ASS
|
url.endsWith(".ass", true) -> SubtitleType.ASS
|
||||||
url.endsWith(".srt", true) -> SubtitleType.SRT
|
url.endsWith(".srt", true) -> SubtitleType.SRT
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ abstract class BaseParser {
|
|||||||
* Isn't necessary to override, but recommended, if you want to improve auto search results
|
* Isn't necessary to override, but recommended, if you want to improve auto search results
|
||||||
* **/
|
* **/
|
||||||
open suspend fun autoSearch(mediaObj: Media): ShowResponse? {
|
open suspend fun autoSearch(mediaObj: Media): ShowResponse? {
|
||||||
var response = loadSavedShowResponse(mediaObj.id)
|
var response: ShowResponse? = null//loadSavedShowResponse(mediaObj.id)
|
||||||
if (response != null) {
|
if (response != null && this !is OfflineMangaParser) {
|
||||||
saveShowResponse(mediaObj.id, response, true)
|
saveShowResponse(mediaObj.id, response, true)
|
||||||
} else {
|
} else {
|
||||||
setUserText("Searching : ${mediaObj.mainName()}")
|
setUserText("Searching : ${mediaObj.mainName()}")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ani.dantotsu.media.manga.MangaChapter
|
|||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
|
||||||
abstract class WatchSources : BaseSources() {
|
abstract class WatchSources : BaseSources() {
|
||||||
|
|
||||||
@@ -23,14 +24,28 @@ abstract class WatchSources : BaseSources() {
|
|||||||
} ?: mutableMapOf()
|
} ?: mutableMapOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadEpisodes(i: Int, showLink: String, extra: Map<String, String>?, sAnime: SAnime?): MutableMap<String, Episode> {
|
suspend fun loadEpisodes(
|
||||||
|
i: Int,
|
||||||
|
showLink: String,
|
||||||
|
extra: Map<String, String>?,
|
||||||
|
sAnime: SAnime?
|
||||||
|
): MutableMap<String, Episode> {
|
||||||
println("finder333 $showLink")
|
println("finder333 $showLink")
|
||||||
val map = mutableMapOf<String, Episode>()
|
val map = mutableMapOf<String, Episode>()
|
||||||
val parser = get(i)
|
val parser = get(i)
|
||||||
tryWithSuspend(true) {
|
tryWithSuspend(true) {
|
||||||
if (sAnime != null) {
|
if (sAnime != null) {
|
||||||
parser.loadEpisodes(showLink,extra, sAnime).forEach {
|
parser.loadEpisodes(showLink, extra, sAnime).forEach {
|
||||||
map[it.number] = Episode(it.number, it.link, it.title, it.description, it.thumbnail, it.isFiller, extra = it.extra, sEpisode = it.sEpisode)
|
map[it.number] = Episode(
|
||||||
|
it.number,
|
||||||
|
it.link,
|
||||||
|
it.title,
|
||||||
|
it.description,
|
||||||
|
it.thumbnail,
|
||||||
|
it.isFiller,
|
||||||
|
extra = it.extra,
|
||||||
|
sEpisode = it.sEpisode
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +57,7 @@ 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.firstOrNull())?.get?.value as? MangaParser
|
return (list.getOrNull(i) ?: list.firstOrNull())?.get?.value as? MangaParser
|
||||||
?: EmptyMangaParser()
|
?: EmptyMangaParser()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +71,7 @@ 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)
|
||||||
|
|
||||||
show.sManga?.let { sManga ->
|
show.sManga?.let { sManga ->
|
||||||
tryWithSuspend(true) {
|
tryWithSuspend(true) {
|
||||||
parser.loadChapters(show.link, show.extra, sManga).forEach {
|
parser.loadChapters(show.link, show.extra, sManga).forEach {
|
||||||
@@ -63,15 +79,28 @@ abstract class MangaReadSources : BaseSources() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(show.sManga == null) {
|
//must be downloaded
|
||||||
|
if (show.sManga == null) {
|
||||||
logger("sManga is null")
|
logger("sManga is null")
|
||||||
}
|
}
|
||||||
|
if (parser is OfflineMangaParser && show.sManga == null) {
|
||||||
|
tryWithSuspend(true) {
|
||||||
|
// Since we've checked, we can safely cast parser to OfflineMangaParser and call its methods
|
||||||
|
parser.loadChapters(show.link, show.extra, SManga.create()).forEach {
|
||||||
|
map[it.number] = MangaChapter(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger("Parser is not an instance of OfflineMangaParser")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
logger("map size ${map.size}")
|
logger("map size ${map.size}")
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class NovelReadSources : BaseSources(){
|
abstract class NovelReadSources : BaseSources() {
|
||||||
override operator fun get(i: Int): NovelParser? {
|
override operator fun get(i: Int): NovelParser? {
|
||||||
return if (list.isNotEmpty()) {
|
return if (list.isNotEmpty()) {
|
||||||
(list.getOrNull(i) ?: list[0]).get.value as NovelParser
|
(list.getOrNull(i) ?: list[0]).get.value as NovelParser
|
||||||
@@ -87,7 +116,7 @@ class EmptyNovelParser : NovelParser() {
|
|||||||
override val volumeRegex: Regex = Regex("")
|
override val volumeRegex: Regex = Regex("")
|
||||||
|
|
||||||
override suspend fun loadBook(link: String, extra: Map<String, String>?): Book {
|
override suspend fun loadBook(link: String, extra: Map<String, String>?): Book {
|
||||||
return Book("","", null, emptyList()) // Return an empty Book object or some default value
|
return Book("", "", null, emptyList()) // Return an empty Book object or some default value
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun search(query: String): List<ShowResponse> {
|
override suspend fun search(query: String): List<ShowResponse> {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ data class MangaChapter(
|
|||||||
//Self-Descriptive
|
//Self-Descriptive
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
|
val scanlator: String? = null,
|
||||||
val sChapter: SChapter,
|
val sChapter: SChapter,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,8 +81,8 @@ data class MangaImage(
|
|||||||
|
|
||||||
val useTransformation: Boolean = false,
|
val useTransformation: Boolean = false,
|
||||||
|
|
||||||
val page: Page
|
val page: Page? = null,
|
||||||
) : Serializable{
|
) : Serializable{
|
||||||
constructor(url: String,useTransformation: Boolean=false, page: Page)
|
constructor(url: String,useTransformation: Boolean=false, page: Page? = null)
|
||||||
: this(FileUrl(url),useTransformation, page)
|
: this(FileUrl(url),useTransformation, page)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,19 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
object MangaSources : MangaReadSources() {
|
object MangaSources : MangaReadSources() {
|
||||||
|
// Instantiate the static parser
|
||||||
|
private val offlineMangaParser by lazy { OfflineMangaParser() }
|
||||||
|
|
||||||
override var list: List<Lazier<BaseParser>> = emptyList()
|
override var list: List<Lazier<BaseParser>> = emptyList()
|
||||||
|
|
||||||
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
|
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
|
||||||
// Initialize with the first value from StateFlow
|
// Initialize with the first value from StateFlow
|
||||||
val initialExtensions = fromExtensions.first()
|
val initialExtensions = fromExtensions.first()
|
||||||
list = createParsersFromExtensions(initialExtensions)
|
list = createParsersFromExtensions(initialExtensions) + Lazier({ OfflineMangaParser() }, "Downloaded")
|
||||||
|
|
||||||
// Update as StateFlow emits new values
|
// Update as StateFlow emits new values
|
||||||
fromExtensions.collect { extensions ->
|
fromExtensions.collect { extensions ->
|
||||||
list = createParsersFromExtensions(extensions)
|
list = createParsersFromExtensions(extensions) + Lazier({ OfflineMangaParser() }, "Downloaded")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
77
app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt
Normal file
77
app/src/main/java/ani/dantotsu/parsers/OfflineMangaParser.kt
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package ani.dantotsu.parsers
|
||||||
|
|
||||||
|
import android.os.Environment
|
||||||
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class OfflineMangaParser: MangaParser() {
|
||||||
|
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||||
|
|
||||||
|
override val hostUrl: String = "Offline"
|
||||||
|
override val name: String = "Offline"
|
||||||
|
override val saveName: String = "Offline"
|
||||||
|
override suspend fun loadChapters(
|
||||||
|
mangaLink: String,
|
||||||
|
extra: Map<String, String>?,
|
||||||
|
sManga: SManga
|
||||||
|
): List<MangaChapter> {
|
||||||
|
val directory = File(
|
||||||
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/$mangaLink"
|
||||||
|
)
|
||||||
|
//get all of the folder names and add them to the list
|
||||||
|
val chapters = mutableListOf<MangaChapter>()
|
||||||
|
if (directory.exists()) {
|
||||||
|
directory.listFiles()?.forEach {
|
||||||
|
if (it.isDirectory) {
|
||||||
|
val chapter = MangaChapter(it.name, "$mangaLink/${it.name}", it.name, null, null, SChapter.create())
|
||||||
|
chapters.add(chapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chapters.sortBy { MangaNameAdapter.findChapterNumber(it.number) }
|
||||||
|
return chapters
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> {
|
||||||
|
val directory = File(
|
||||||
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/$chapterLink"
|
||||||
|
)
|
||||||
|
val images = mutableListOf<MangaImage>()
|
||||||
|
if (directory.exists()) {
|
||||||
|
directory.listFiles()?.forEach {
|
||||||
|
if (it.isFile) {
|
||||||
|
val image = MangaImage(it.absolutePath, false, null)
|
||||||
|
images.add(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<ShowResponse> {
|
||||||
|
val titles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
||||||
|
val returnTitles: MutableList<String> = mutableListOf()
|
||||||
|
for (title in titles) {
|
||||||
|
if (FuzzySearch.ratio(title.lowercase(), query.lowercase()) > 80) {
|
||||||
|
returnTitles.add(title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val returnList: MutableList<ShowResponse> = mutableListOf()
|
||||||
|
for (title in returnTitles) {
|
||||||
|
returnList.add(ShowResponse(title, title, title))
|
||||||
|
}
|
||||||
|
return returnList
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -49,10 +49,10 @@ class AnimeExtensionsFragment : Fragment(),
|
|||||||
): View {
|
): View {
|
||||||
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
|
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
binding.allAnimeExtensionsRecyclerView.isNestedScrollingEnabled = true
|
binding.allAnimeExtensionsRecyclerView.isNestedScrollingEnabled = false
|
||||||
binding.allAnimeExtensionsRecyclerView.adapter = adapter
|
binding.allAnimeExtensionsRecyclerView.adapter = adapter
|
||||||
binding.allAnimeExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
|
binding.allAnimeExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
(binding.allAnimeExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = false
|
(binding.allAnimeExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = true
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.pagerFlow.collectLatest {
|
viewModel.pagerFlow.collectLatest {
|
||||||
@@ -60,6 +60,8 @@ class AnimeExtensionsFragment : Fragment(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.invalidatePager() // Force a refresh of the pager
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,26 +13,9 @@ 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://avatars.githubusercontent.com/u/149729762?v=4", "Icon Designer", "https://github.com/WaiWhat"),
|
||||||
Developer("jeelpatel231","https://avatars.githubusercontent.com/u/33726155?s=120&v=4","Contributor","https://github.com/jeelpatel231"),
|
Developer("Aayush262", "https://avatars.githubusercontent.com/u/99584765?v=4", "Contributor", "https://github.com/aayush2622"),
|
||||||
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 {
|
||||||
|
|||||||
@@ -1,55 +1,32 @@
|
|||||||
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.pm.PackageManager
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
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.view.LayoutInflater
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
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.ProgressBar
|
|
||||||
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.ActivityCompat
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
|
||||||
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
import ani.dantotsu.databinding.ActivityExtensionsBinding
|
||||||
import ani.dantotsu.home.AnimeFragment
|
|
||||||
import ani.dantotsu.home.MangaFragment
|
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.bumptech.glide.Glide
|
import ani.dantotsu.others.LangSet
|
||||||
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 eu.kanade.tachiyomi.data.notification.Notifications
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
|
|
||||||
class ExtensionsActivity : AppCompatActivity() {
|
class ExtensionsActivity : AppCompatActivity() {
|
||||||
private val restartMainActivity = object : OnBackPressedCallback(false) {
|
private val restartMainActivity = object : OnBackPressedCallback(false) {
|
||||||
@@ -61,7 +38,8 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityExtensionsBinding.inflate(layoutInflater)
|
binding = ActivityExtensionsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -94,21 +72,20 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
|
|
||||||
val searchView: SearchView = findViewById(R.id.searchView)
|
val searchView: AutoCompleteTextView = findViewById(R.id.searchViewText)
|
||||||
|
|
||||||
val extensionsHeader: LinearLayout = findViewById(R.id.extensionsHeader)
|
searchView.addTextChangedListener(object : TextWatcher {
|
||||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
override fun afterTextChanged(s: Editable?) {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextChange(newText: String?): Boolean {
|
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}")
|
val currentFragment = supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}")
|
||||||
if (currentFragment is SearchQueryHandler) {
|
if (currentFragment is SearchQueryHandler) {
|
||||||
currentFragment.updateContentBasedOnQuery(newText)
|
currentFragment.updateContentBasedOnQuery(s?.toString()?.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -116,18 +93,18 @@ class ExtensionsActivity : AppCompatActivity() {
|
|||||||
initActivity(this)
|
initActivity(this)
|
||||||
|
|
||||||
|
|
||||||
|
binding.languageselect.setOnClickListener {
|
||||||
|
val popup = PopupMenu(this, it)
|
||||||
|
|
||||||
|
popup.inflate(R.menu.launguage_selector_menu)
|
||||||
|
popup.show()
|
||||||
|
}
|
||||||
|
|
||||||
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
topMargin = statusBarHeight
|
topMargin = statusBarHeight
|
||||||
bottomMargin = navBarHeight
|
bottomMargin = navBarHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this, restartMainActivity)
|
|
||||||
|
|
||||||
binding.settingsBack.setOnClickListener {
|
|
||||||
onBackPressedDispatcher.onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
|
||||||
class FAQActivity : AppCompatActivity() {
|
class FAQActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityFaqBinding
|
private lateinit var binding: ActivityFaqBinding
|
||||||
@@ -105,7 +106,8 @@ class FAQActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityFaqBinding.inflate(layoutInflater)
|
binding = ActivityFaqBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ani.dantotsu.settings
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -7,8 +8,10 @@ import android.util.Log
|
|||||||
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.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -17,10 +20,15 @@ import androidx.recyclerview.widget.DiffUtil
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
|
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
|
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
@@ -30,62 +38,129 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class InstalledAnimeExtensionsFragment : Fragment() {
|
class InstalledAnimeExtensionsFragment : Fragment() {
|
||||||
|
|
||||||
|
|
||||||
private var _binding: FragmentAnimeExtensionsBinding? = null
|
private var _binding: FragmentAnimeExtensionsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private lateinit var extensionsRecyclerView: RecyclerView
|
private lateinit var extensionsRecyclerView: RecyclerView
|
||||||
val skipIcons = loadData("skip_extension_icons") ?: false
|
val skipIcons = loadData("skip_extension_icons") ?: false
|
||||||
private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
|
private val animeExtensionManager: AnimeExtensionManager = Injekt.get()
|
||||||
private val extensionsAdapter = AnimeExtensionsAdapter({ pkg ->
|
private val extensionsAdapter = AnimeExtensionsAdapter({ pkg ->
|
||||||
if (isAdded) { // Check if the fragment is currently added to its activity
|
val allSettings = pkg.sources.filterIsInstance<ConfigurableAnimeSource>()
|
||||||
val context = requireContext() // Store context in a variable
|
if (allSettings.isNotEmpty()) {
|
||||||
val notificationManager =
|
var selectedSetting = allSettings[0]
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
|
if (allSettings.size > 1) {
|
||||||
|
val names = allSettings.map { it.lang }.toTypedArray()
|
||||||
|
var selectedIndex = 0
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Select a Source")
|
||||||
|
.setSingleChoiceItems(names, selectedIndex) { _, which ->
|
||||||
|
selectedIndex = which
|
||||||
|
}
|
||||||
|
.setPositiveButton("OK") { dialog, _ ->
|
||||||
|
selectedSetting = allSettings[selectedIndex]
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
if (pkg.hasUpdate) {
|
// Move the fragment transaction here
|
||||||
animeExtensionManager.updateExtension(pkg)
|
val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id){
|
||||||
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
|
val activity = requireActivity() as ExtensionsActivity
|
||||||
.subscribe(
|
activity.findViewById<ViewPager2>(R.id.viewPager).visibility = View.VISIBLE
|
||||||
{ installStep ->
|
activity.findViewById<TabLayout>(R.id.tabLayout).visibility = View.VISIBLE
|
||||||
val builder = NotificationCompat.Builder(
|
activity.findViewById<TextInputLayout>(R.id.searchView).visibility = View.VISIBLE
|
||||||
context,
|
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||||
Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
View.GONE
|
||||||
)
|
|
||||||
.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())
|
|
||||||
}
|
}
|
||||||
)
|
parentFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
return@setNegativeButton
|
||||||
|
}
|
||||||
|
.show()
|
||||||
} else {
|
} else {
|
||||||
animeExtensionManager.uninstallExtension(pkg.pkgName)
|
// If there's only one setting, proceed with the fragment transaction
|
||||||
|
val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id){
|
||||||
|
val activity = requireActivity() as ExtensionsActivity
|
||||||
|
activity.findViewById<ViewPager2>(R.id.viewPager).visibility = View.VISIBLE
|
||||||
|
activity.findViewById<TabLayout>(R.id.tabLayout).visibility = View.VISIBLE
|
||||||
|
activity.findViewById<TextInputLayout>(R.id.searchView).visibility = View.VISIBLE
|
||||||
|
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
parentFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide ViewPager2 and TabLayout
|
||||||
|
val activity = requireActivity() as ExtensionsActivity
|
||||||
|
activity.findViewById<ViewPager2>(R.id.viewPager).visibility = View.GONE
|
||||||
|
activity.findViewById<TabLayout>(R.id.tabLayout).visibility = View.GONE
|
||||||
|
activity.findViewById<TextInputLayout>(R.id.searchView).visibility = View.GONE
|
||||||
|
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}, skipIcons)
|
},
|
||||||
|
{ 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(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@@ -114,6 +189,7 @@ class InstalledAnimeExtensionsFragment : Fragment() {
|
|||||||
|
|
||||||
|
|
||||||
private class AnimeExtensionsAdapter(
|
private class AnimeExtensionsAdapter(
|
||||||
|
private val onSettingsClicked: (AnimeExtension.Installed) -> Unit,
|
||||||
private val onUninstallClicked: (AnimeExtension.Installed) -> Unit,
|
private val onUninstallClicked: (AnimeExtension.Installed) -> Unit,
|
||||||
skipIcons: Boolean
|
skipIcons: Boolean
|
||||||
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
|
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
|
||||||
@@ -133,31 +209,37 @@ class InstalledAnimeExtensionsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val extension = getItem(position) // Use getItem() from ListAdapter
|
val extension = getItem(position) // Use getItem() from ListAdapter
|
||||||
|
val nsfw = if (extension.isNsfw) {
|
||||||
|
"(18+)"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
holder.extensionNameTextView.text = extension.name
|
holder.extensionNameTextView.text = extension.name
|
||||||
|
holder.extensionVersionTextView.text = "${extension.versionName} $nsfw"
|
||||||
if (!skipIcons) {
|
if (!skipIcons) {
|
||||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||||
}
|
}
|
||||||
if (extension.hasUpdate) {
|
if (extension.hasUpdate) {
|
||||||
holder.closeTextView.text = "Update"
|
holder.closeTextView.setImageResource(R.drawable.ic_round_sync_24)
|
||||||
holder.closeTextView.setTextColor(
|
|
||||||
ContextCompat.getColor(
|
|
||||||
holder.itemView.context,
|
|
||||||
R.color.warning
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
holder.closeTextView.text = "Uninstall"
|
holder.closeTextView.setImageResource(R.drawable.ic_round_delete_24)
|
||||||
}
|
}
|
||||||
holder.closeTextView.setOnClickListener {
|
holder.closeTextView.setOnClickListener {
|
||||||
onUninstallClicked(extension)
|
onUninstallClicked(extension)
|
||||||
}
|
}
|
||||||
|
holder.settingsImageView.setOnClickListener {
|
||||||
|
onSettingsClicked(extension)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||||
|
val extensionVersionTextView: TextView = view.findViewById(R.id.extensionVersionTextView)
|
||||||
|
val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView)
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
val closeTextView: ImageView = view.findViewById(R.id.closeTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -180,5 +262,4 @@ class InstalledAnimeExtensionsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package ani.dantotsu.settings
|
package ani.dantotsu.settings
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -8,8 +9,10 @@ import android.util.Log
|
|||||||
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.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -18,13 +21,18 @@ import androidx.recyclerview.widget.DiffUtil
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
|
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@@ -37,6 +45,66 @@ class InstalledMangaExtensionsFragment : Fragment() {
|
|||||||
val skipIcons = loadData("skip_extension_icons") ?: false
|
val skipIcons = loadData("skip_extension_icons") ?: false
|
||||||
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
|
private val mangaExtensionManager: MangaExtensionManager = Injekt.get()
|
||||||
private val extensionsAdapter = MangaExtensionsAdapter({ pkg ->
|
private val extensionsAdapter = MangaExtensionsAdapter({ pkg ->
|
||||||
|
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
||||||
|
val activity = requireActivity() as ExtensionsActivity
|
||||||
|
val visibility = if (show) View.VISIBLE else View.GONE
|
||||||
|
activity.findViewById<ViewPager2>(R.id.viewPager).visibility = visibility
|
||||||
|
activity.findViewById<TabLayout>(R.id.tabLayout).visibility = visibility
|
||||||
|
activity.findViewById<TextInputLayout>(R.id.searchView).visibility = visibility
|
||||||
|
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||||
|
if (show) View.GONE else View.VISIBLE
|
||||||
|
}
|
||||||
|
val allSettings = pkg.sources.filterIsInstance<ConfigurableSource>()
|
||||||
|
if (allSettings.isNotEmpty()) {
|
||||||
|
var selectedSetting = allSettings[0]
|
||||||
|
if (allSettings.size > 1) {
|
||||||
|
val names = allSettings.map { it.lang }.toTypedArray()
|
||||||
|
var selectedIndex = 0
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Select a Source")
|
||||||
|
.setSingleChoiceItems(names, selectedIndex) { _, which ->
|
||||||
|
selectedIndex = which
|
||||||
|
}
|
||||||
|
.setPositiveButton("OK") { dialog, _ ->
|
||||||
|
selectedSetting = allSettings[selectedIndex]
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
|
// Move the fragment transaction here
|
||||||
|
val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id){
|
||||||
|
changeUIVisibility(true)
|
||||||
|
}
|
||||||
|
parentFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
changeUIVisibility(true)
|
||||||
|
return@setNegativeButton
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
// If there's only one setting, proceed with the fragment transaction
|
||||||
|
val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id){
|
||||||
|
changeUIVisibility(true)
|
||||||
|
}
|
||||||
|
parentFragmentManager.beginTransaction()
|
||||||
|
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
|
||||||
|
.replace(R.id.fragmentExtensionsContainer, fragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide ViewPager2 and TabLayout
|
||||||
|
changeUIVisibility(false)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ pkg ->
|
||||||
if (isAdded) { // Check if the fragment is currently added to its activity
|
if (isAdded) { // Check if the fragment is currently added to its activity
|
||||||
val context = requireContext() // Store context in a variable
|
val context = requireContext() // Store context in a variable
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
@@ -115,6 +183,7 @@ class InstalledMangaExtensionsFragment : Fragment() {
|
|||||||
|
|
||||||
|
|
||||||
private class MangaExtensionsAdapter(
|
private class MangaExtensionsAdapter(
|
||||||
|
private val onSettingsClicked: (MangaExtension.Installed) -> Unit,
|
||||||
private val onUninstallClicked: (MangaExtension.Installed) -> Unit,
|
private val onUninstallClicked: (MangaExtension.Installed) -> Unit,
|
||||||
skipIcons: Boolean
|
skipIcons: Boolean
|
||||||
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
|
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
|
||||||
@@ -135,30 +204,35 @@ class InstalledMangaExtensionsFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val extension = getItem(position) // Use getItem() from ListAdapter
|
val extension = getItem(position) // Use getItem() from ListAdapter
|
||||||
|
val nsfw = if (extension.isNsfw) {
|
||||||
|
"(18+)"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
holder.extensionNameTextView.text = extension.name
|
holder.extensionNameTextView.text = extension.name
|
||||||
|
holder.extensionVersionTextView.text = "${extension.versionName} $nsfw"
|
||||||
if (!skipIcons) {
|
if (!skipIcons) {
|
||||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||||
}
|
}
|
||||||
if (extension.hasUpdate) {
|
if (extension.hasUpdate) {
|
||||||
holder.closeTextView.text = "Update"
|
holder.closeTextView.setImageResource(R.drawable.ic_round_sync_24)
|
||||||
holder.closeTextView.setTextColor(
|
|
||||||
ContextCompat.getColor(
|
|
||||||
holder.itemView.context,
|
|
||||||
R.color.warning
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
holder.closeTextView.text = "Uninstall"
|
holder.closeTextView.setImageResource(R.drawable.ic_round_delete_24)
|
||||||
}
|
}
|
||||||
holder.closeTextView.setOnClickListener {
|
holder.closeTextView.setOnClickListener {
|
||||||
onUninstallClicked(extension)
|
onUninstallClicked(extension)
|
||||||
}
|
}
|
||||||
|
holder.settingsImageView.setOnClickListener {
|
||||||
|
onSettingsClicked(extension)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
|
||||||
|
val extensionVersionTextView: TextView = view.findViewById(R.id.extensionVersionTextView)
|
||||||
|
val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView)
|
||||||
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
|
||||||
val closeTextView: TextView = view.findViewById(R.id.closeTextView)
|
val closeTextView: ImageView = view.findViewById(R.id.closeTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.paging.PagingData
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
|
||||||
@@ -25,6 +26,7 @@ import ani.dantotsu.settings.paging.MangaExtensionAdapter
|
|||||||
import ani.dantotsu.settings.paging.MangaExtensionsViewModel
|
import ani.dantotsu.settings.paging.MangaExtensionsViewModel
|
||||||
import ani.dantotsu.settings.paging.MangaExtensionsViewModelFactory
|
import ani.dantotsu.settings.paging.MangaExtensionsViewModelFactory
|
||||||
import ani.dantotsu.settings.paging.OnMangaInstallClickListener
|
import ani.dantotsu.settings.paging.OnMangaInstallClickListener
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
class MangaExtensionsFragment : Fragment(),
|
class MangaExtensionsFragment : Fragment(),
|
||||||
@@ -50,17 +52,19 @@ class MangaExtensionsFragment : Fragment(),
|
|||||||
): View {
|
): View {
|
||||||
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
|
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
binding.allMangaExtensionsRecyclerView.isNestedScrollingEnabled = true
|
binding.allMangaExtensionsRecyclerView.isNestedScrollingEnabled = false
|
||||||
binding.allMangaExtensionsRecyclerView.adapter = adapter
|
binding.allMangaExtensionsRecyclerView.adapter = adapter
|
||||||
binding.allMangaExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
|
binding.allMangaExtensionsRecyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
(binding.allMangaExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = false
|
(binding.allMangaExtensionsRecyclerView.layoutManager as LinearLayoutManager).isItemPrefetchEnabled = true
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.pagerFlow.collectLatest {
|
viewModel.pagerFlow.collectLatest { pagingData ->
|
||||||
adapter.submitData(it)
|
adapter.submitData(pagingData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.invalidatePager() // Force a refresh of the pager
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ data class PlayerSettings(
|
|||||||
var focusPause: Boolean = true,
|
var focusPause: Boolean = true,
|
||||||
var gestures: Boolean = true,
|
var gestures: Boolean = true,
|
||||||
var doubleTap: Boolean = true,
|
var doubleTap: Boolean = true,
|
||||||
|
var fastforward: Boolean = true,
|
||||||
var seekTime: Int = 10,
|
var seekTime: Int = 10,
|
||||||
var skipTime: Int = 85,
|
var skipTime: Int = 85,
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -31,7 +32,8 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityPlayerSettingsBinding.inflate(layoutInflater)
|
binding = ActivityPlayerSettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -183,7 +185,11 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
|||||||
settings.doubleTap = isChecked
|
settings.doubleTap = isChecked
|
||||||
saveData(player, settings)
|
saveData(player, settings)
|
||||||
}
|
}
|
||||||
|
binding.playerSettingsFastForward.isChecked = settings.fastforward
|
||||||
|
binding.playerSettingsFastForward.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
settings.fastforward = isChecked
|
||||||
|
saveData(player, settings)
|
||||||
|
}
|
||||||
binding.playerSettingsSeekTime.value = settings.seekTime.toFloat()
|
binding.playerSettingsSeekTime.value = settings.seekTime.toFloat()
|
||||||
binding.playerSettingsSeekTime.addOnChangeListener { _, value, _ ->
|
binding.playerSettingsSeekTime.addOnChangeListener { _, value, _ ->
|
||||||
settings.seekTime = value.toInt()
|
settings.seekTime = value.toInt()
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ 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
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
|
|
||||||
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()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityReaderSettingsBinding.inflate(layoutInflater)
|
binding = ActivityReaderSettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
@@ -53,11 +54,13 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
lateinit var binding: ActivitySettingsBinding
|
lateinit var binding: ActivitySettingsBinding
|
||||||
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
||||||
private val networkPreferences = Injekt.get<NetworkPreferences>()
|
private val networkPreferences = Injekt.get<NetworkPreferences>()
|
||||||
|
private var cursedCounter = 0
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -105,6 +108,12 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
restartApp()
|
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")!!
|
val themeString = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getString("theme", "PURPLE")!!
|
||||||
binding.themeSwitcher.setText(themeString.substring(0, 1) + themeString.substring(1).lowercase())
|
binding.themeSwitcher.setText(themeString.substring(0, 1) + themeString.substring(1).lowercase())
|
||||||
|
|
||||||
@@ -161,6 +170,11 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
binding.skipExtensionIcons.setOnCheckedChangeListener { _, isChecked ->
|
binding.skipExtensionIcons.setOnCheckedChangeListener { _, isChecked ->
|
||||||
saveData("skip_extension_icons", isChecked)
|
saveData("skip_extension_icons", isChecked)
|
||||||
}
|
}
|
||||||
|
binding.NSFWExtension.isChecked = loadData("NFSWExtension") ?: true
|
||||||
|
binding.NSFWExtension.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
saveData("NFSWExtension", isChecked)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
binding.userAgent.setText(networkPreferences.defaultUserAgent().get())
|
binding.userAgent.setText(networkPreferences.defaultUserAgent().get())
|
||||||
binding.userAgent.setOnEditorActionListener { _, _, _ ->
|
binding.userAgent.setOnEditorActionListener { _, _, _ ->
|
||||||
@@ -168,7 +182,7 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
val exDns = listOf("None", "Cloudflare", "Google", "AdGuard", "Quad9", "AliDNS", "DNSPod", "360", "Quad101", "Mullvad", "Controld", "Njalla", "Shecan")
|
val exDns = listOf("None", "Cloudflare", "Google", "AdGuard", "Quad9", "AliDNS", "DNSPod", "360", "Quad101", "Mullvad", "Controld", "Njalla", "Shecan", "Libre")
|
||||||
binding.settingsExtensionDns.setText(exDns[networkPreferences.dohProvider().get()], false)
|
binding.settingsExtensionDns.setText(exDns[networkPreferences.dohProvider().get()], false)
|
||||||
binding.settingsExtensionDns.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, exDns))
|
binding.settingsExtensionDns.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, exDns))
|
||||||
binding.settingsExtensionDns.setOnItemClickListener { _, _, i, _ ->
|
binding.settingsExtensionDns.setOnItemClickListener { _, _, i, _ ->
|
||||||
@@ -383,8 +397,15 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
val array = resources.getStringArray(R.array.tips)
|
val array = resources.getStringArray(R.array.tips)
|
||||||
|
|
||||||
binding.settingsLogo.setSafeOnClickListener {
|
binding.settingsLogo.setSafeOnClickListener {
|
||||||
|
cursedCounter++
|
||||||
(binding.settingsLogo.drawable as Animatable).start()
|
(binding.settingsLogo.drawable as Animatable).start()
|
||||||
snackString(array[(Math.random() * array.size).toInt()], this)
|
if (cursedCounter % 7 == 0){
|
||||||
|
snackString("youwu have been cuwsed :pwayge:")
|
||||||
|
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_cursed_lang", true).apply()
|
||||||
|
} else{
|
||||||
|
snackString(array[(Math.random() * array.size).toInt()], this)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.settingsDev.setOnClickListener {
|
binding.settingsDev.setOnClickListener {
|
||||||
@@ -408,7 +429,7 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var curTime = loadData<Int>("subscriptions_time_r") ?: 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
|
||||||
@@ -421,7 +442,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_r", curTime)
|
saveData("subscriptions_time_s", curTime)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
startSubscription(true)
|
startSubscription(true)
|
||||||
}.show()
|
}.show()
|
||||||
|
|||||||
@@ -3,19 +3,24 @@ 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
|
||||||
|
import android.widget.Switch
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.others.imagesearch.ImageSearchActivity
|
import ani.dantotsu.others.imagesearch.ImageSearchActivity
|
||||||
import ani.dantotsu.databinding.BottomSheetSettingsBinding
|
import ani.dantotsu.databinding.BottomSheetSettingsBinding
|
||||||
|
import ani.dantotsu.download.DownloadContainerActivity
|
||||||
|
import ani.dantotsu.download.manga.OfflineMangaFragment
|
||||||
|
|
||||||
|
|
||||||
class SettingsDialogFragment : BottomSheetDialogFragment() {
|
class SettingsDialogFragment(val pageType: PageType) : BottomSheetDialogFragment() {
|
||||||
private var _binding: BottomSheetSettingsBinding? = null
|
private var _binding: BottomSheetSettingsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
@@ -26,6 +31,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)
|
||||||
@@ -62,18 +73,42 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
binding.settingsDownloads.setSafeOnClickListener {
|
binding.settingsDownloads.setSafeOnClickListener {
|
||||||
try {
|
when(pageType) {
|
||||||
val arrayOfFiles = ContextCompat.getExternalFilesDirs(requireContext(), null)
|
PageType.MANGA -> {
|
||||||
startActivity(
|
val intent = Intent(activity, DownloadContainerActivity::class.java)
|
||||||
if (loadData<Boolean>("sd_dl") == true && arrayOfFiles.size > 1 && arrayOfFiles[0] != null && arrayOfFiles[1] != null) {
|
intent.putExtra("FRAGMENT_CLASS_NAME", OfflineMangaFragment::class.java.name)
|
||||||
val parentDirectory = arrayOfFiles[1].toString()
|
startActivity(intent)
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
}
|
||||||
intent.setDataAndType(Uri.parse(parentDirectory), "resource/folder")
|
PageType.ANIME -> {
|
||||||
} else Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
|
try {
|
||||||
)
|
val arrayOfFiles = ContextCompat.getExternalFilesDirs(requireContext(), null)
|
||||||
} catch (e: ActivityNotFoundException) {
|
startActivity(
|
||||||
toast(getString(R.string.file_manager_not_found))
|
if (loadData<Boolean>("sd_dl") == true && arrayOfFiles.size > 1 && arrayOfFiles[0] != null && arrayOfFiles[1] != null) {
|
||||||
|
val parentDirectory = arrayOfFiles[1].toString()
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.setDataAndType(Uri.parse(parentDirectory), "resource/folder")
|
||||||
|
} else Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
|
||||||
|
)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
toast(getString(R.string.file_manager_not_found))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PageType.HOME -> {
|
||||||
|
try {
|
||||||
|
val arrayOfFiles = ContextCompat.getExternalFilesDirs(requireContext(), null)
|
||||||
|
startActivity(
|
||||||
|
if (loadData<Boolean>("sd_dl") == true && arrayOfFiles.size > 1 && arrayOfFiles[0] != null && arrayOfFiles[1] != null) {
|
||||||
|
val parentDirectory = arrayOfFiles[1].toString()
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.setDataAndType(Uri.parse(parentDirectory), "resource/folder")
|
||||||
|
} else Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
|
||||||
|
)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
toast(getString(R.string.file_manager_not_found))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,4 +117,10 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
|
|||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object{
|
||||||
|
enum class PageType{
|
||||||
|
MANGA, ANIME, HOME
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ 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 ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
class UserInterfaceSettingsActivity : AppCompatActivity() {
|
class UserInterfaceSettingsActivity : AppCompatActivity() {
|
||||||
@@ -16,7 +17,8 @@ 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()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityUserInterfaceSettingsBinding.inflate(layoutInflater)
|
binding = ActivityUserInterfaceSettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package ani.dantotsu.settings.extensionprefs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.preference.DialogPreference
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.forEach
|
||||||
|
import androidx.preference.getOnBindEditTextListener
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.settings.ExtensionsActivity
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import eu.kanade.tachiyomi.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
|
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
|
||||||
|
import eu.kanade.tachiyomi.source.anime.getPreferenceKey
|
||||||
|
import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito
|
||||||
|
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class AnimeSourcePreferencesFragment : PreferenceFragmentCompat() {
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
preferenceScreen = populateAnimePreferenceScreen()
|
||||||
|
//set background color
|
||||||
|
val color = TypedValue()
|
||||||
|
requireContext().theme.resolveAttribute(com.google.android.material.R.attr.backgroundColor, color, true)
|
||||||
|
view?.setBackgroundColor(color.data)
|
||||||
|
}
|
||||||
|
private var onCloseAction: (() -> Unit)? = null
|
||||||
|
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
onCloseAction?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun populateAnimePreferenceScreen(): PreferenceScreen {
|
||||||
|
val sourceId = requireArguments().getLong(SOURCE_ID)
|
||||||
|
val source = Injekt.get<AnimeSourceManager>().get(sourceId)!!
|
||||||
|
check(source is ConfigurableAnimeSource)
|
||||||
|
val sharedPreferences = requireContext().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE)
|
||||||
|
val dataStore = SharedPreferencesDataStore(sharedPreferences)
|
||||||
|
preferenceManager.preferenceDataStore = dataStore
|
||||||
|
val sourceScreen = preferenceManager.createPreferenceScreen(requireContext())
|
||||||
|
source.setupPreferenceScreen(sourceScreen)
|
||||||
|
sourceScreen.forEach { pref ->
|
||||||
|
pref.isIconSpaceReserved = false
|
||||||
|
if (pref is DialogPreference) {
|
||||||
|
pref.dialogTitle = pref.title
|
||||||
|
println("pref.dialogTitle: ${pref.dialogTitle}")
|
||||||
|
}
|
||||||
|
for (entry in sharedPreferences.all.entries) {
|
||||||
|
Log.d("Preferences", "Key: ${entry.key}, Value: ${entry.value}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply incognito IME for EditTextPreference
|
||||||
|
if (pref is EditTextPreference) {
|
||||||
|
val setListener = pref.getOnBindEditTextListener()
|
||||||
|
pref.setOnBindEditTextListener {
|
||||||
|
setListener?.onBindEditText(it)
|
||||||
|
it.setIncognito(lifecycleScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceScreen
|
||||||
|
}
|
||||||
|
fun getInstance(sourceId: Long, onCloseAction: (() -> Unit)? = null): AnimeSourcePreferencesFragment {
|
||||||
|
val fragment = AnimeSourcePreferencesFragment()
|
||||||
|
fragment.arguments = bundleOf(SOURCE_ID to sourceId)
|
||||||
|
fragment.onCloseAction = onCloseAction
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object { //idk why it needs both
|
||||||
|
private const val SOURCE_ID = "source_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package ani.dantotsu.settings.extensionprefs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.preference.DialogPreference
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.forEach
|
||||||
|
import androidx.preference.getOnBindEditTextListener
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.settings.ExtensionsActivity
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import eu.kanade.tachiyomi.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
|
||||||
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
|
import eu.kanade.tachiyomi.source.manga.getPreferenceKey
|
||||||
|
import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito
|
||||||
|
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class MangaSourcePreferencesFragment : PreferenceFragmentCompat() {
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
preferenceScreen = populateMangaPreferenceScreen()
|
||||||
|
}
|
||||||
|
private var onCloseAction: (() -> Unit)? = null
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
onCloseAction?.invoke()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun populateMangaPreferenceScreen(): PreferenceScreen {
|
||||||
|
val sourceId = requireArguments().getLong(SOURCE_ID)
|
||||||
|
val source = Injekt.get<MangaSourceManager>().get(sourceId)!!
|
||||||
|
check(source is ConfigurableSource)
|
||||||
|
val sharedPreferences = requireContext().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE)
|
||||||
|
val dataStore = SharedPreferencesDataStore(sharedPreferences)
|
||||||
|
preferenceManager.preferenceDataStore = dataStore
|
||||||
|
val sourceScreen = preferenceManager.createPreferenceScreen(requireContext())
|
||||||
|
source.setupPreferenceScreen(sourceScreen)
|
||||||
|
sourceScreen.forEach { pref ->
|
||||||
|
pref.isIconSpaceReserved = false
|
||||||
|
if (pref is DialogPreference) {
|
||||||
|
pref.dialogTitle = pref.title
|
||||||
|
println("pref.dialogTitle: ${pref.dialogTitle}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply incognito IME for EditTextPreference
|
||||||
|
if (pref is EditTextPreference) {
|
||||||
|
val setListener = pref.getOnBindEditTextListener()
|
||||||
|
pref.setOnBindEditTextListener {
|
||||||
|
setListener?.onBindEditText(it)
|
||||||
|
it.setIncognito(lifecycleScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceScreen
|
||||||
|
}
|
||||||
|
fun getInstance(sourceId: Long, onCloseAction: (() -> Unit)? = null): MangaSourcePreferencesFragment {
|
||||||
|
val fragment = MangaSourcePreferencesFragment()
|
||||||
|
fragment.arguments = bundleOf(SOURCE_ID to sourceId)
|
||||||
|
fragment.onCloseAction = onCloseAction
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SOURCE_ID = "source_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import androidx.paging.PagingState
|
|||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.settings.SettingsActivity
|
||||||
import ani.dantotsu.databinding.ItemExtensionAllBinding
|
import ani.dantotsu.databinding.ItemExtensionAllBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
@@ -77,21 +78,26 @@ class AnimeExtensionPagingSource(
|
|||||||
val installedExtensions = installedExtensionsFlow.first().map { it.pkgName }.toSet()
|
val installedExtensions = installedExtensionsFlow.first().map { it.pkgName }.toSet()
|
||||||
val availableExtensions = availableExtensionsFlow.first().filterNot { it.pkgName in installedExtensions }
|
val availableExtensions = availableExtensionsFlow.first().filterNot { it.pkgName in installedExtensions }
|
||||||
val query = searchQuery.first()
|
val query = searchQuery.first()
|
||||||
|
var isNsfwEnabled: Boolean = loadData("NFSWExtension") ?: true
|
||||||
val filteredExtensions = if (query.isEmpty()) {
|
val filteredExtensions = if (query.isEmpty()) {
|
||||||
availableExtensions
|
availableExtensions
|
||||||
} else {
|
} else {
|
||||||
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
|
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
|
||||||
}
|
}
|
||||||
|
val filternfsw = if(isNsfwEnabled) {
|
||||||
|
filteredExtensions
|
||||||
|
} else {
|
||||||
|
filteredExtensions.filterNot { it.isNsfw }
|
||||||
|
}
|
||||||
return try {
|
return try {
|
||||||
val sublist = filteredExtensions.subList(
|
val sublist = filternfsw.subList(
|
||||||
fromIndex = position,
|
fromIndex = position,
|
||||||
toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size)
|
toIndex = (position + params.loadSize).coerceAtMost(filternfsw.size)
|
||||||
)
|
)
|
||||||
LoadResult.Page(
|
LoadResult.Page(
|
||||||
data = sublist,
|
data = sublist,
|
||||||
prevKey = if (position == 0) null else position - params.loadSize,
|
prevKey = if (position == 0) null else position - params.loadSize,
|
||||||
nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize
|
nextKey = if (position + params.loadSize >= filternfsw.size) null else position + params.loadSize
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LoadResult.Error(e)
|
LoadResult.Error(e)
|
||||||
@@ -151,8 +157,14 @@ class AnimeExtensionAdapter(private val clickListener: OnAnimeInstallClickListen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val extensionIconImageView: ImageView = binding.extensionIconImageView
|
val extensionIconImageView: ImageView = binding.extensionIconImageView
|
||||||
fun bind(extension: AnimeExtension.Available) {
|
fun bind(extension: AnimeExtension.Available) {
|
||||||
|
val nsfw = if (extension.isNsfw) {
|
||||||
|
"(18+)"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
binding.extensionNameTextView.text = extension.name
|
binding.extensionNameTextView.text = extension.name
|
||||||
|
binding.extensionVersionTextView.text = "${extension.versionName} $nsfw"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import androidx.paging.PagingState
|
|||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.settings.SettingsActivity
|
||||||
import ani.dantotsu.databinding.ItemExtensionAllBinding
|
import ani.dantotsu.databinding.ItemExtensionAllBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
@@ -81,21 +82,26 @@ class MangaExtensionPagingSource(
|
|||||||
val installedExtensions = installedExtensionsFlow.first().map { it.pkgName }.toSet()
|
val installedExtensions = installedExtensionsFlow.first().map { it.pkgName }.toSet()
|
||||||
val availableExtensions = availableExtensionsFlow.first().filterNot { it.pkgName in installedExtensions }
|
val availableExtensions = availableExtensionsFlow.first().filterNot { it.pkgName in installedExtensions }
|
||||||
val query = searchQuery.first()
|
val query = searchQuery.first()
|
||||||
|
var isNsfwEnabled: Boolean = loadData("NFSWExtension") ?: false
|
||||||
val filteredExtensions = if (query.isEmpty()) {
|
val filteredExtensions = if (query.isEmpty()) {
|
||||||
availableExtensions
|
availableExtensions
|
||||||
} else {
|
} else {
|
||||||
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
|
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
|
||||||
}
|
}
|
||||||
|
val filternfsw = if(isNsfwEnabled) {
|
||||||
|
filteredExtensions
|
||||||
|
} else {
|
||||||
|
filteredExtensions.filterNot { it.isNsfw }
|
||||||
|
}
|
||||||
return try {
|
return try {
|
||||||
val sublist = filteredExtensions.subList(
|
val sublist = filternfsw.subList(
|
||||||
fromIndex = position,
|
fromIndex = position,
|
||||||
toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size)
|
toIndex = (position + params.loadSize).coerceAtMost(filternfsw.size)
|
||||||
)
|
)
|
||||||
LoadResult.Page(
|
LoadResult.Page(
|
||||||
data = sublist,
|
data = sublist,
|
||||||
prevKey = if (position == 0) null else position - params.loadSize,
|
prevKey = if (position == 0) null else position - params.loadSize,
|
||||||
nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize
|
nextKey = if (position + params.loadSize >= filternfsw.size) null else position + params.loadSize
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LoadResult.Error(e)
|
LoadResult.Error(e)
|
||||||
@@ -117,12 +123,10 @@ class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListen
|
|||||||
companion object {
|
companion object {
|
||||||
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<MangaExtension.Available>() {
|
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<MangaExtension.Available>() {
|
||||||
override fun areItemsTheSame(oldItem: MangaExtension.Available, newItem: MangaExtension.Available): Boolean {
|
override fun areItemsTheSame(oldItem: MangaExtension.Available, newItem: MangaExtension.Available): Boolean {
|
||||||
// Your logic here
|
|
||||||
return oldItem.pkgName == newItem.pkgName
|
return oldItem.pkgName == newItem.pkgName
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: MangaExtension.Available, newItem: MangaExtension.Available): Boolean {
|
override fun areContentsTheSame(oldItem: MangaExtension.Available, newItem: MangaExtension.Available): Boolean {
|
||||||
// Your logic here
|
|
||||||
return oldItem == newItem
|
return oldItem == newItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +160,13 @@ class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListen
|
|||||||
}
|
}
|
||||||
val extensionIconImageView: ImageView = binding.extensionIconImageView
|
val extensionIconImageView: ImageView = binding.extensionIconImageView
|
||||||
fun bind(extension: MangaExtension.Available) {
|
fun bind(extension: MangaExtension.Available) {
|
||||||
|
val nsfw = if (extension.isNsfw) {
|
||||||
|
"(18+)"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
binding.extensionNameTextView.text = extension.name
|
binding.extensionNameTextView.text = extension.name
|
||||||
|
binding.extensionVersionTextView.text = "${extension.versionName} $nsfw"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_r", context) ?: defaultTime
|
val curTime = loadData<Int>("subscriptions_time_s", context) ?: defaultTime
|
||||||
|
|
||||||
if (timeMinutes[curTime] > 0)
|
if (timeMinutes[curTime] > 0)
|
||||||
alarmManager.setRepeating(
|
alarmManager.setRepeating(
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import kotlinx.coroutines.launch
|
|||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
class Subscription {
|
class Subscription {
|
||||||
companion object {
|
companion object {
|
||||||
const val defaultTime = 3
|
const val defaultTime = 1
|
||||||
val timeMinutes = arrayOf(0L, 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) {
|
||||||
|
|||||||
@@ -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_r") ?: 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(
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
package ani.dantotsu.themes
|
package ani.dantotsu.themes
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
|
||||||
class ThemeManager(private val context: Context) {
|
class ThemeManager(private val context: Context) {
|
||||||
fun applyTheme() {
|
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)){
|
if(context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_material_you", false)){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
when (context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getString("theme", "PURPLE")!!) {
|
val theme = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getString("theme", "PURPLE")!!
|
||||||
"PURPLE" -> {
|
|
||||||
context.setTheme(R.style.Theme_Dantotsu_Purple)
|
val themeToApply = when (theme) {
|
||||||
}
|
"PURPLE" -> if (useOLED) R.style.Theme_Dantotsu_PurpleOLED else R.style.Theme_Dantotsu_Purple
|
||||||
"BLUE" -> {
|
"BLUE" -> if (useOLED) R.style.Theme_Dantotsu_BlueOLED else R.style.Theme_Dantotsu_Blue
|
||||||
context.setTheme(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
|
||||||
"GREEN" -> {
|
"RED" -> if (useOLED) R.style.Theme_Dantotsu_RedOLED else R.style.Theme_Dantotsu_Red
|
||||||
context.setTheme(R.style.Theme_Dantotsu_Green)
|
"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
|
||||||
"PINK" -> {
|
"SAIKOU" -> if (useOLED) R.style.Theme_Dantotsu_SaikouOLED else R.style.Theme_Dantotsu_Saikou
|
||||||
context.setTheme(R.style.Theme_Dantotsu_Pink)
|
else -> if (useOLED) R.style.Theme_Dantotsu_PurpleOLED else R.style.Theme_Dantotsu_Purple
|
||||||
}
|
}
|
||||||
"RED" -> {
|
|
||||||
context.setTheme(R.style.Theme_Dantotsu_Red)
|
context.setTheme(themeToApply)
|
||||||
}
|
}
|
||||||
"LAVENDER" -> {
|
|
||||||
context.setTheme(R.style.Theme_Dantotsu_Lavender)
|
private fun isDarkThemeActive(context: Context): Boolean {
|
||||||
}
|
return when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
|
||||||
"MONOCHROME (BETA)" -> {
|
Configuration.UI_MODE_NIGHT_YES -> true
|
||||||
context.setTheme(R.style.Theme_Dantotsu_Monochrome)
|
Configuration.UI_MODE_NIGHT_NO -> false
|
||||||
}
|
Configuration.UI_MODE_NIGHT_UNDEFINED -> false
|
||||||
else -> {
|
else -> false
|
||||||
context.setTheme(R.style.Theme_Dantotsu_Purple)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
enum class Theme(val theme: String) {
|
enum class Theme(val theme: String) {
|
||||||
PURPLE("PURPLE"),
|
PURPLE("PURPLE"),
|
||||||
@@ -44,7 +45,8 @@ class ThemeManager(private val context: Context) {
|
|||||||
PINK("PINK"),
|
PINK("PINK"),
|
||||||
RED("RED"),
|
RED("RED"),
|
||||||
LAVENDER("LAVENDER"),
|
LAVENDER("LAVENDER"),
|
||||||
MONOCHROME("MONOCHROME (BETA)");
|
MONOCHROME("MONOCHROME (BETA)"),
|
||||||
|
SAIKOU("SAIKOU");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromString(value: String): Theme {
|
fun fromString(value: String): Theme {
|
||||||
@@ -53,4 +55,4 @@ class ThemeManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A source that explicitly doesn't require traffic considerations.
|
||||||
|
*
|
||||||
|
* This typically applies for self-hosted sources.
|
||||||
|
*/
|
||||||
|
interface UnmeteredSource
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.preference
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.preference.PreferenceDataStore
|
||||||
|
|
||||||
|
class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() {
|
||||||
|
|
||||||
|
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
|
||||||
|
return prefs.getBoolean(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putBoolean(key: String?, value: Boolean) {
|
||||||
|
prefs.edit {
|
||||||
|
putBoolean(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInt(key: String?, defValue: Int): Int {
|
||||||
|
return prefs.getInt(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putInt(key: String?, value: Int) {
|
||||||
|
prefs.edit {
|
||||||
|
putInt(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLong(key: String?, defValue: Long): Long {
|
||||||
|
return prefs.getLong(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putLong(key: String?, value: Long) {
|
||||||
|
prefs.edit {
|
||||||
|
putLong(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFloat(key: String?, defValue: Float): Float {
|
||||||
|
return prefs.getFloat(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putFloat(key: String?, value: Float) {
|
||||||
|
prefs.edit {
|
||||||
|
putFloat(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getString(key: String?, defValue: String?): String? {
|
||||||
|
return prefs.getString(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putString(key: String?, value: String?) {
|
||||||
|
prefs.edit {
|
||||||
|
putString(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? {
|
||||||
|
return prefs.getStringSet(key, defValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putStringSet(key: String?, values: MutableSet<String>?) {
|
||||||
|
prefs.edit {
|
||||||
|
putStringSet(key, values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageInstaller
|
import android.content.pm.PackageInstaller
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import eu.kanade.tachiyomi.extension.InstallStep
|
import eu.kanade.tachiyomi.extension.InstallStep
|
||||||
import eu.kanade.tachiyomi.util.lang.use
|
import eu.kanade.tachiyomi.util.lang.use
|
||||||
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
|
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
|
||||||
@@ -100,7 +101,7 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
service.registerReceiver(packageActionReceiver, IntentFilter(INSTALL_ACTION))
|
ContextCompat.registerReceiver(service, packageActionReceiver, IntentFilter(INSTALL_ACTION), ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import eu.kanade.tachiyomi.extension.InstallStep
|
import eu.kanade.tachiyomi.extension.InstallStep
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||||
@@ -25,7 +26,8 @@ class AnimeExtensionInstallActivity : Activity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||||
.setDataAndType(intent.data, intent.type)
|
.setDataAndType(intent.data, intent.type)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
@@ -27,7 +28,7 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) :
|
|||||||
* Registers this broadcast receiver
|
* Registers this broadcast receiver
|
||||||
*/
|
*/
|
||||||
fun register(context: Context) {
|
fun register(context: Context) {
|
||||||
context.registerReceiver(this, filter)
|
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ internal class AnimeExtensionInstaller(private val context: Context) {
|
|||||||
isRegistered = true
|
isRegistered = true
|
||||||
|
|
||||||
val filter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
val filter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||||
context.registerReceiver(this, filter)
|
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_EXPORTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageInstaller
|
import android.content.pm.PackageInstaller
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import eu.kanade.tachiyomi.extension.InstallStep
|
import eu.kanade.tachiyomi.extension.InstallStep
|
||||||
import eu.kanade.tachiyomi.util.lang.use
|
import eu.kanade.tachiyomi.util.lang.use
|
||||||
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
|
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
|
||||||
@@ -100,7 +101,7 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
service.registerReceiver(packageActionReceiver, IntentFilter(INSTALL_ACTION))
|
ContextCompat.registerReceiver(service, packageActionReceiver, IntentFilter(INSTALL_ACTION), ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import eu.kanade.tachiyomi.extension.InstallStep
|
import eu.kanade.tachiyomi.extension.InstallStep
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||||
@@ -25,7 +26,8 @@ class MangaExtensionInstallActivity : Activity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeManager(this).applyTheme()
|
LangSet.setLocale(this)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||||
.setDataAndType(intent.data, intent.type)
|
.setDataAndType(intent.data, intent.type)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
|
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
|
||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
@@ -27,7 +28,7 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) :
|
|||||||
* Registers this broadcast receiver
|
* Registers this broadcast receiver
|
||||||
*/
|
*/
|
||||||
fun register(context: Context) {
|
fun register(context: Context) {
|
||||||
context.registerReceiver(this, filter)
|
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ internal class MangaExtensionInstaller(private val context: Context) {
|
|||||||
isRegistered = true
|
isRegistered = true
|
||||||
|
|
||||||
val filter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
val filter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||||
context.registerReceiver(this, filter)
|
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_EXPORTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const val PREF_DOH_MULLVAD = 9
|
|||||||
const val PREF_DOH_CONTROLD = 10
|
const val PREF_DOH_CONTROLD = 10
|
||||||
const val PREF_DOH_NJALLA = 11
|
const val PREF_DOH_NJALLA = 11
|
||||||
const val PREF_DOH_SHECAN = 12
|
const val PREF_DOH_SHECAN = 12
|
||||||
|
const val PREF_DOH_LIBREDNS = 13
|
||||||
|
|
||||||
fun OkHttpClient.Builder.dohCloudflare() = dns(
|
fun OkHttpClient.Builder.dohCloudflare() = dns(
|
||||||
DnsOverHttps.Builder().client(build())
|
DnsOverHttps.Builder().client(build())
|
||||||
@@ -184,3 +185,13 @@ fun OkHttpClient.Builder.dohShecan() = dns(
|
|||||||
)
|
)
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun OkHttpClient.Builder.dohLibreDNS() = dns(
|
||||||
|
DnsOverHttps.Builder().client(build())
|
||||||
|
.url("https://doh.libredns.gr/dns-query".toHttpUrl())
|
||||||
|
.bootstrapDnsHosts(
|
||||||
|
InetAddress.getByName("116.202.176.26"), // IPv4 address for LibreDNS
|
||||||
|
InetAddress.getByName("192.71.166.92") // Fallback IPv4 address
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class NetworkHelper(
|
|||||||
PREF_DOH_CONTROLD -> builder.dohControlD()
|
PREF_DOH_CONTROLD -> builder.dohControlD()
|
||||||
PREF_DOH_NJALLA -> builder.dohNajalla()
|
PREF_DOH_NJALLA -> builder.dohNajalla()
|
||||||
PREF_DOH_SHECAN -> builder.dohShecan()
|
PREF_DOH_SHECAN -> builder.dohShecan()
|
||||||
|
PREF_DOH_LIBREDNS -> builder.dohLibreDNS()
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.anime
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||||
|
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import tachiyomi.domain.source.anime.model.AnimeSourceData
|
||||||
|
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
||||||
|
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||||
|
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
class AndroidAnimeSourceManager(
|
||||||
|
private val context: Context,
|
||||||
|
private val extensionManager: AnimeExtensionManager,
|
||||||
|
) : AnimeSourceManager {
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
|
||||||
|
private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap<Long, AnimeSource>())
|
||||||
|
|
||||||
|
private val stubSourcesMap = ConcurrentHashMap<Long, StubAnimeSource>()
|
||||||
|
|
||||||
|
override val catalogueSources: Flow<List<AnimeCatalogueSource>> = sourcesMapFlow.map { it.values.filterIsInstance<AnimeCatalogueSource>() }
|
||||||
|
|
||||||
|
init {
|
||||||
|
scope.launch {
|
||||||
|
extensionManager.installedExtensionsFlow
|
||||||
|
.collectLatest { extensions ->
|
||||||
|
val mutableMap = ConcurrentHashMap<Long, AnimeSource>(
|
||||||
|
mapOf(
|
||||||
|
LocalAnimeSource.ID to LocalAnimeSource(
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
extensions.forEach { extension ->
|
||||||
|
extension.sources.forEach {
|
||||||
|
mutableMap[it.id] = it
|
||||||
|
registerStubSource(it.toSourceData())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourcesMapFlow.value = mutableMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(sourceKey: Long): AnimeSource? {
|
||||||
|
return sourcesMapFlow.value[sourceKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOrStub(sourceKey: Long): AnimeSource {
|
||||||
|
return sourcesMapFlow.value[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
|
||||||
|
runBlocking { createStubSource(sourceKey) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance<AnimeHttpSource>()
|
||||||
|
|
||||||
|
override fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance<AnimeCatalogueSource>()
|
||||||
|
|
||||||
|
override fun getStubSources(): List<StubAnimeSource> {
|
||||||
|
val onlineSourceIds = getOnlineSources().map { it.id }
|
||||||
|
return stubSourcesMap.values.filterNot { it.id in onlineSourceIds }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerStubSource(sourceData: AnimeSourceData) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun createStubSource(id: Long): StubAnimeSource {
|
||||||
|
return StubAnimeSource(AnimeSourceData(id, "", ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.anime
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
|
import tachiyomi.domain.source.anime.model.AnimeSourceData
|
||||||
|
import tachiyomi.domain.source.anime.model.StubAnimeSource
|
||||||
|
import tachiyomi.source.local.entries.anime.isLocal
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
fun AnimeSource.icon(): Drawable? = Injekt.get<AnimeExtensionManager>().getAppIconForSource(this.id)
|
||||||
|
|
||||||
|
fun AnimeSource.getPreferenceKey(): String = "source_$id"
|
||||||
|
|
||||||
|
fun AnimeSource.toSourceData(): AnimeSourceData = AnimeSourceData(id = id, lang = lang, name = name)
|
||||||
|
|
||||||
|
fun AnimeSource.getNameForAnimeInfo(): String {
|
||||||
|
val preferences = Injekt.get<SourcePreferences>()
|
||||||
|
val enabledLanguages = preferences.enabledLanguages().get()
|
||||||
|
.filterNot { it in listOf("all", "other") }
|
||||||
|
val hasOneActiveLanguages = enabledLanguages.size == 1
|
||||||
|
val isInEnabledLanguages = lang in enabledLanguages
|
||||||
|
return when {
|
||||||
|
// For edge cases where user disables a source they got manga of in their library.
|
||||||
|
hasOneActiveLanguages && !isInEnabledLanguages -> toString()
|
||||||
|
// Hide the language tag when only one language is used.
|
||||||
|
hasOneActiveLanguages && isInEnabledLanguages -> name
|
||||||
|
else -> toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AnimeSource.isLocalOrStub(): Boolean = isLocal() || this is StubAnimeSource
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user