Compare commits

..

7 Commits

Author SHA1 Message Date
Finnley Somdahl
dc165fa6bc update preparation 2023-10-22 02:33:06 -05:00
Finnley Somdahl
dc959796e6 various bugfixes 2023-10-22 02:28:39 -05:00
Finnley Somdahl
0b9f2bb019 update preparation 2023-10-20 21:47:28 -05:00
Finnley Somdahl
6ddbd4760c Merge branch 'main' of https://github.com/rebelonion/Dantotsu 2023-10-20 21:39:01 -05:00
Finnley Somdahl
d1270c7c83 various fixes and updates 2023-10-20 21:38:40 -05:00
Finnley Somdahl
79618e1963 Update stable.md 2023-10-20 02:16:40 -05:00
Finnley Somdahl
da81646297 update stable 2023-10-20 02:14:04 -05:00
41 changed files with 1067 additions and 344 deletions

View File

@@ -21,7 +21,7 @@ android {
minSdk 23 minSdk 23
targetSdk 34 targetSdk 34
versionCode ((System.currentTimeMillis() / 60000).toInteger()) versionCode ((System.currentTimeMillis() / 60000).toInteger())
versionName "0.1.0" versionName "0.1.2"
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
@@ -97,6 +97,9 @@ dependencies {
implementation 'com.alexvasilkov:gesture-views:2.8.3' implementation 'com.alexvasilkov:gesture-views:2.8.3'
implementation 'com.github.VipulOG:ebook-reader:0.1.6' implementation 'com.github.VipulOG:ebook-reader:0.1.6'
// string matching
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
// Aniyomi // Aniyomi
implementation 'io.reactivex:rxjava:1.3.8' implementation 'io.reactivex:rxjava:1.3.8'
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'

View File

@@ -2,6 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@@ -10,7 +17,8 @@
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- For background jobs --> <!-- For background jobs -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -47,7 +55,7 @@
android:theme="@style/Theme.Dantotsu" android:theme="@style/Theme.Dantotsu"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:ignore="AllowBackup" tools:ignore="AllowBackup"
> android:banner="@drawable/ic_banner_foreground">
<activity <activity
android:name="ani.dantotsu.media.novel.novelreader.NovelReaderActivity" android:name="ani.dantotsu.media.novel.novelreader.NovelReaderActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
@@ -206,9 +214,12 @@
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.Main" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity> </activity>
<activity <activity
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity" android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity"

View File

@@ -11,6 +11,7 @@ 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 com.google.android.material.color.DynamicColors
import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import logcat.AndroidLogcatLogger import logcat.AndroidLogcatLogger
@@ -33,6 +34,11 @@ class App : MultiDexApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
val sharedPreferences = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
val useMaterialYou = sharedPreferences.getBoolean("use_material_you", false)
if(useMaterialYou) {
DynamicColors.applyToActivitiesIfAvailable(this)
}
registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks) registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports) Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)

View File

@@ -2,6 +2,7 @@ package ani.dantotsu.aniyomi.anime.custom
import android.app.Application import android.app.Application
import android.content.Context
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
@@ -10,6 +11,7 @@ import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore 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 kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.InjektRegistrar
@@ -21,12 +23,15 @@ class AppModule(val app: Application) : InjektModule {
override fun InjektRegistrar.registerInjectables() { override fun InjektRegistrar.registerInjectables() {
addSingleton(app) addSingleton(app)
addSingletonFactory { NetworkHelper(app) } addSingletonFactory { NetworkHelper(app, get()) }
addSingletonFactory { AnimeExtensionManager(app) } addSingletonFactory { AnimeExtensionManager(app) }
addSingletonFactory { MangaExtensionManager(app) } addSingletonFactory { MangaExtensionManager(app) }
val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
addSingleton(sharedPreferences)
addSingletonFactory { addSingletonFactory {
Json { Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
@@ -44,6 +49,13 @@ class PreferenceModule(val application: Application) : InjektModule {
AndroidPreferenceStore(application) AndroidPreferenceStore(application)
} }
addSingletonFactory {
NetworkPreferences(
preferenceStore = get(),
verboseLogging = false,
)
}
addSingletonFactory { addSingletonFactory {
SourcePreferences(get()) SourcePreferences(get())
} }

View File

@@ -1,21 +1,30 @@
package ani.dantotsu.connections.discord package ani.dantotsu.connections.discord
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application.getProcessName
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.discord.Discord.saveToken import ani.dantotsu.connections.discord.Discord.saveToken
import ani.dantotsu.startMainActivity import ani.dantotsu.startMainActivity
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)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = getProcessName()
if (packageName != process) WebView.setDataDirectorySuffix(process)
}
setContentView(R.layout.activity_discord) setContentView(R.layout.activity_discord)
val webView = findViewById<WebView>(R.id.discordWebview) val webView = findViewById<WebView>(R.id.discordWebview)
webView.apply { webView.apply {
settings.javaScriptEnabled = true settings.javaScriptEnabled = true
settings.databaseEnabled = true settings.databaseEnabled = true

View File

@@ -113,6 +113,10 @@ data class Media(
this.relation = mediaEdge.relationType?.toString() this.relation = mediaEdge.relationType?.toString()
} }
fun mainName() = nameMAL ?: name ?: nameRomaji fun mainName() = name ?: nameMAL ?: nameRomaji
fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji
} }
object MediaSingleton {
var media: Media? = null
}

View File

@@ -1,6 +1,8 @@
package ani.dantotsu.media package ani.dantotsu.media
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
@@ -40,6 +42,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MediaDetailsViewModel : ViewModel() { class MediaDetailsViewModel : ViewModel() {
val scrolledToTop = MutableLiveData(true) val scrolledToTop = MutableLiveData(true)
@@ -48,27 +52,18 @@ class MediaDetailsViewModel : ViewModel() {
saveData("$id-select", data, activity) saveData("$id-select", data, activity)
} }
fun loadSelected(media: Media): Selected { fun loadSelected(media: Media): Selected {
val sharedPreferences = Injekt.get<SharedPreferences>()
val data = loadData<Selected>("${media.id}-select") ?: Selected().let { val data = loadData<Selected>("${media.id}-select") ?: Selected().let {
it.source = if (media.isAdult) "" else when (media.anime != null) { it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) {
true -> loadData("settings_def_anime_source") ?: "" true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0)
else -> loadData("settings_def_manga_source") ?: "" else -> sharedPreferences.getInt(("settings_def_manga_source_s_r"), 0)
} }
it.preferDub = loadData("settings_prefer_dub") ?: false it.preferDub = loadData("settings_prefer_dub") ?: false
it.sourceIndex = loadSelectedStringLocation(it.source)
saveSelected(media.id, it) saveSelected(media.id, it)
it it
} }
if (media.anime != null) {
val sources = if (media.isAdult) HAnimeSources else AnimeSources
data.sourceIndex = sources.list.indexOfFirst { it.name == data.source }
} else {
val sources = if (media.isAdult) HMangaSources else MangaSources
data.sourceIndex = sources.list.indexOfFirst { it.name == data.source }
}
if (data.sourceIndex == -1) {
data.sourceIndex = 0
}
return data return data
} }
@@ -194,8 +189,8 @@ class MediaDetailsViewModel : ViewModel() {
val server = selected.server ?: return false val server = selected.server ?: return false
val link = ep.link ?: return false val link = ep.link ?: return false
ep.extractors = mutableListOf(watchSources?.get(loadSelectedStringLocation(selected.source))?.let { ep.extractors = mutableListOf(watchSources?.get(selected.sourceIndex)?.let {
selected.sourceIndex = loadSelectedStringLocation(selected.source) selected.sourceIndex = selected.sourceIndex
if (!post && !it.allowsPreloading) null if (!post && !it.allowsPreloading) null
else ep.sEpisode?.let { it1 -> else ep.sEpisode?.let { it1 ->
it.loadSingleVideoServer(server, link, ep.extra, it.loadSingleVideoServer(server, link, ep.extra,
@@ -266,7 +261,7 @@ class MediaDetailsViewModel : ViewModel() {
suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, post: Boolean = true): Boolean { suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, post: Boolean = true): Boolean {
return tryWithSuspend(true) { return tryWithSuspend(true) {
chapter.addImages( chapter.addImages(
mangaReadSources?.get(loadSelectedStringLocation(selected.source))?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false mangaReadSources?.get(selected.sourceIndex)?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false
) )
if (post) mangaChapter.postValue(chapter) if (post) mangaChapter.postValue(chapter)
true true
@@ -289,7 +284,7 @@ class MediaDetailsViewModel : ViewModel() {
} }
suspend fun autoSearchNovels(media: Media) { suspend fun autoSearchNovels(media: Media) {
val source = novelSources[loadSelectedStringLocation(media.selected?.source?:"")] val source = novelSources[media.selected?.sourceIndex?:0]
tryWithSuspend(post = true) { tryWithSuspend(post = true) {
if (source != null) { if (source != null) {
novelResponses.postValue(source.sortedSearch(media)) novelResponses.postValue(source.sortedSearch(media))

View File

@@ -7,7 +7,7 @@ data class Selected(
var recyclerStyle: Int? = null, var recyclerStyle: Int? = null,
var recyclerReversed: Boolean = false, var recyclerReversed: Boolean = false,
var chip: Int = 0, var chip: Int = 0,
var source: String = "", //var source: String = "",
var sourceIndex: Int = 0, var sourceIndex: Int = 0,
var preferDub: Boolean = false, var preferDub: Boolean = false,
var server: String? = null, var server: String? = null,

View File

@@ -1,22 +1,33 @@
package ani.dantotsu.media.manga package ani.dantotsu.media.manga
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.LruCache import android.util.LruCache
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
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
data class ImageData( data class ImageData(
val page: Page, val page: Page,
val source: HttpSource, val source: HttpSource
){ ){
suspend fun fetchAndProcessImage(page: Page, httpSource: HttpSource): Bitmap? { 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
val response = httpSource.getImage(page) val response = httpSource.getImage(page)
println("Response: ${response.code}")
println("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()
@@ -25,6 +36,7 @@ data class ImageData(
val bitmap = BitmapFactory.decodeStream(inputStream) val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close() inputStream?.close()
saveImage(bitmap, context.contentResolver, page.imageUrl!!, Bitmap.CompressFormat.JPEG, 100)
return@withContext bitmap return@withContext bitmap
} catch (e: Exception) { } catch (e: Exception) {
@@ -36,6 +48,39 @@ data class ImageData(
} }
} }
fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String, format: Bitmap.CompressFormat, quality: Int) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/${format.name.lowercase()}")
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Manga")
}
val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
uri?.let {
contentResolver.openOutputStream(it)?.use { os ->
bitmap.compress(format, quality, os)
}
}
} else {
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Anime")
if (!directory.exists()) {
directory.mkdirs()
}
val file = File(directory, filename)
FileOutputStream(file).use { outputStream ->
bitmap.compress(format, quality, outputStream)
}
}
} catch (e: Exception) {
// Handle exception here
println("Exception while saving image: ${e.message}")
}
}
class MangaCache() { class MangaCache() {
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024 / 2).toInt() private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024 / 2).toInt()
private val cache = LruCache<String, ImageData>(maxMemory) private val cache = LruCache<String, ImageData>(maxMemory)

View File

@@ -9,6 +9,8 @@ import ani.dantotsu.databinding.ItemEpisodeCompactBinding
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.setAnimation import ani.dantotsu.setAnimation
import ani.dantotsu.connections.updateProgress import ani.dantotsu.connections.updateProgress
import java.util.regex.Matcher
import java.util.regex.Pattern
class MangaChapterAdapter( class MangaChapterAdapter(
private var type: Int, private var type: Int,
@@ -63,12 +65,12 @@ class MangaChapterAdapter(
val ep = arr[position] val ep = arr[position]
binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeNumber.text = ep.number
if (media.userProgress != null) { if (media.userProgress != null) {
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 9999f) <= media.userProgress!!.toFloat())
binding.itemEpisodeViewedCover.visibility = View.VISIBLE binding.itemEpisodeViewedCover.visibility = View.VISIBLE
else { else {
binding.itemEpisodeViewedCover.visibility = View.GONE binding.itemEpisodeViewedCover.visibility = View.GONE
binding.itemEpisodeCont.setOnLongClickListener { binding.itemEpisodeCont.setOnLongClickListener {
updateProgress(media, ep.number) updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString())
true true
} }
} }
@@ -91,14 +93,14 @@ class MangaChapterAdapter(
} else binding.itemChapterTitle.visibility = View.GONE } else binding.itemChapterTitle.visibility = View.GONE
if (media.userProgress != null) { if (media.userProgress != null) {
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) { if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 9999f) <= media.userProgress!!.toFloat()) {
binding.itemEpisodeViewedCover.visibility = View.VISIBLE binding.itemEpisodeViewedCover.visibility = View.VISIBLE
binding.itemEpisodeViewed.visibility = View.VISIBLE binding.itemEpisodeViewed.visibility = View.VISIBLE
} else { } else {
binding.itemEpisodeViewedCover.visibility = View.GONE binding.itemEpisodeViewedCover.visibility = View.GONE
binding.itemEpisodeViewed.visibility = View.GONE binding.itemEpisodeViewed.visibility = View.GONE
binding.root.setOnLongClickListener { binding.root.setOnLongClickListener {
updateProgress(media, ep.number) updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString())
true true
} }
} }
@@ -113,4 +115,6 @@ class MangaChapterAdapter(
fun updateType(t: Int) { fun updateType(t: Int) {
type = t type = t
} }
} }

View File

@@ -0,0 +1,20 @@
package ani.dantotsu.media.manga
import java.util.regex.Matcher
import java.util.regex.Pattern
class MangaNameAdapter {
companion object {
fun findChapterNumber(text: String): Float? {
val regex = "(chapter|chap|ch|c)[\\s:.\\-]*([\\d]+\\.?[\\d]*)"
val pattern: Pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE)
val matcher: Matcher = pattern.matcher(text)
return if (matcher.find()) {
matcher.group(2)?.toFloat()
} else {
null
}
}
}
}

View File

@@ -161,7 +161,7 @@ abstract class BaseImageAdapter(
println(mangaCache.get(link.url)) println(mangaCache.get(link.url))
println("cache size: ${mangaCache.size()}") println("cache size: ${mangaCache.size()}")
mangaCache.get(link.url)?.let { imageData -> mangaCache.get(link.url)?.let { imageData ->
val bitmap = imageData.fetchAndProcessImage(imageData.page, imageData.source) val bitmap = imageData.fetchAndProcessImage(imageData.page, imageData.source, context = this@loadBitmap)
it.load(bitmap) it.load(bitmap)
.skipMemoryCache(true) .skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)

View File

@@ -14,6 +14,7 @@ import ani.dantotsu.currActivity
import ani.dantotsu.databinding.BottomSheetSelectorBinding import ani.dantotsu.databinding.BottomSheetSelectorBinding
import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaSingleton
import ani.dantotsu.others.getSerialized import ani.dantotsu.others.getSerialized
import ani.dantotsu.tryWith import ani.dantotsu.tryWith
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -49,7 +50,8 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
activity?.runOnUiThread { activity?.runOnUiThread {
tryWith { dismiss() } tryWith { dismiss() }
if(launch) { if(launch) {
val intent = Intent(activity, MangaReaderActivity::class.java).apply { putExtra("media", m) } MediaSingleton.media = m
val intent = Intent(activity, MangaReaderActivity::class.java)//.apply { putExtra("media", m) }
activity.startActivity(intent) activity.startActivity(intent)
} }
} }

View File

@@ -30,8 +30,10 @@ import ani.dantotsu.connections.updateProgress
import ani.dantotsu.databinding.ActivityMangaReaderBinding import ani.dantotsu.databinding.ActivityMangaReaderBinding
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaSingleton
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.media.manga.MangaNameAdapter
import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.getSerialized import ani.dantotsu.others.getSerialized
import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.HMangaSources
@@ -46,7 +48,12 @@ import ani.dantotsu.settings.UserInterfaceSettings
import com.alexvasilkov.gestures.views.GestureFrameLayout import com.alexvasilkov.gestures.views.GestureFrameLayout
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@@ -164,10 +171,13 @@ class MangaReaderActivity : AppCompatActivity() {
media = if (model.getMedia().value == null) media = if (model.getMedia().value == null)
try { try {
(intent.getSerialized("media")) ?: return //(intent.getSerialized("media")) ?: return
MediaSingleton.media ?: return
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
return return
} finally {
MediaSingleton.media = null
} }
else model.getMedia().value ?: return else model.getMedia().value ?: return
model.setMedia(media) model.setMedia(media)
@@ -180,6 +190,29 @@ class MangaReaderActivity : AppCompatActivity() {
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE
if(model.mangaReadSources!!.names.isEmpty()){
//try to reload sources
try {
if (media.isAdult) {
val mangaSources = MangaSources
val scope = lifecycleScope
scope.launch(Dispatchers.IO) {
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow)
}
model.mangaReadSources = mangaSources
}else{
val mangaSources = HMangaSources
val scope = lifecycleScope
scope.launch(Dispatchers.IO) {
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow)
}
model.mangaReadSources = mangaSources
}
}catch (e: Exception){
Firebase.crashlytics.recordException(e)
logError(e)
}
}
binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex] binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex]
binding.mangaReaderTitle.text = media.userPreferredName binding.mangaReaderTitle.text = media.userPreferredName
@@ -677,7 +710,7 @@ class MangaReaderActivity : AppCompatActivity() {
progressDialog?.setCancelable(false) progressDialog?.setCancelable(false)
?.setPositiveButton(getString(R.string.yes)) { dialog, _ -> ?.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
saveData("${media.id}_save_progress", true) saveData("${media.id}_save_progress", true)
updateProgress(media, media.manga!!.selectedChapter!!) updateProgress(media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString())
dialog.dismiss() dialog.dismiss()
runnable.run() runnable.run()
} }
@@ -689,7 +722,7 @@ class MangaReaderActivity : AppCompatActivity() {
progressDialog?.show() progressDialog?.show()
} else { } else {
if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true) if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true)
updateProgress(media, media.manga!!.selectedChapter!!) updateProgress(media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString())
runnable.run() runnable.run()
} }
} else { } else {

View File

@@ -171,6 +171,17 @@ abstract class AnimeParser : BaseParser() {
} }
} }
class EmptyAnimeParser: AnimeParser() {
override val name: String = "None"
override val saveName: String = "None"
override val isDubAvailableSeparately: Boolean = false
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> = emptyList()
override suspend fun loadVideoServers(episodeLink: String, extra: Map<String, String>?, sEpisode: SEpisode): List<VideoServer> = emptyList()
override suspend fun search(query: String): List<ShowResponse> = emptyList()
}
/** /**
* A class for containing Episode data of a particular parser * A class for containing Episode data of a particular parser
* **/ * **/

View File

@@ -2,6 +2,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.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
@@ -65,6 +66,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
override val saveName = extension.name override val saveName = extension.name
override val hostUrl = extension.sources.first().name override val hostUrl = extension.sources.first().name
override val isDubAvailableSeparately = false override val isDubAvailableSeparately = false
override val isNSFW = extension.isNsfw
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> { override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> {
val source = extension.sources.first() val source = extension.sources.first()
if (source is AnimeCatalogueSource) { if (source is AnimeCatalogueSource) {
@@ -175,6 +177,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
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 suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> { override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> {
val source = extension.sources.first() as? CatalogueSource ?: return emptyList() val source = extension.sources.first() as? CatalogueSource ?: return emptyList()
@@ -199,12 +202,14 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
return coroutineScope { return coroutineScope {
try { try {
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))
logger("put page: ${page.imageUrl}")
pageToMangaImage(page) pageToMangaImage(page)
} }
} }
@@ -212,11 +217,44 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
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()
emptyList() emptyList()
} }
} }
} }
suspend fun fetchAndProcessImage(page: Page, httpSource: HttpSource, context: Context): Bitmap? {
return withContext(Dispatchers.IO) {
try {
// Fetch the image
val response = httpSource.getImage(page)
println("Response: ${response.code}")
println("Response: ${response.message}")
// Convert the Response to an InputStream
val inputStream = response.body?.byteStream()
// Convert InputStream to Bitmap
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
ani.dantotsu.media.manga.saveImage(
bitmap,
context.contentResolver,
page.imageUrl!!,
Bitmap.CompressFormat.JPEG,
100
)
return@withContext bitmap
} catch (e: Exception) {
// Handle any exceptions
println("An error occurred: ${e.message}")
return@withContext null
}
}
}
fun fetchAndSaveImage(page: Page, httpSource: HttpSource, contentResolver: ContentResolver) { fun fetchAndSaveImage(page: Page, httpSource: HttpSource, contentResolver: ContentResolver) {
@@ -349,22 +387,22 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter { private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter {
val parsedChapterTitle = parseChapterTitle(sChapter.name) /*val parsedChapterTitle = parseChapterTitle(sChapter.name)
val number = if (sChapter.chapter_number.toInt() != -1){ val number = if (sChapter.chapter_number.toInt() != -1){
sChapter.chapter_number.toString() sChapter.chapter_number.toString()
} else if(parsedChapterTitle.first != null || parsedChapterTitle.second != null){ } else if(parsedChapterTitle.first != null || parsedChapterTitle.second != null){
(parsedChapterTitle.first ?: "") + "." + (parsedChapterTitle.second ?: "") (parsedChapterTitle.first ?: "") + "." + (parsedChapterTitle.second ?: "")
}else{ }else{
sChapter.name sChapter.name
} }*/
return MangaChapter( return MangaChapter(
number, sChapter.name,
sChapter.url, sChapter.url,
if (parsedChapterTitle.first != null || parsedChapterTitle.second != null) { //if (parsedChapterTitle.first != null || parsedChapterTitle.second != null) {
parsedChapterTitle.third // parsedChapterTitle.third
} else { //} else {
sChapter.name sChapter.name,
}, //},
null, null,
sChapter sChapter
) )

View File

@@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.source.model.SManga
import java.io.Serializable import java.io.Serializable
import java.net.URLDecoder import java.net.URLDecoder
import java.net.URLEncoder import java.net.URLEncoder
import me.xdrop.fuzzywuzzy.FuzzySearch
abstract class BaseParser { abstract class BaseParser {
@@ -55,21 +57,41 @@ abstract class BaseParser {
setUserText("Searching : ${mediaObj.mainName()}") setUserText("Searching : ${mediaObj.mainName()}")
val results = search(mediaObj.mainName()) val results = search(mediaObj.mainName())
val sortedResults = if (results.isNotEmpty()) { val sortedResults = if (results.isNotEmpty()) {
StringMatcher.closestShowMovedToTop(mediaObj.mainName(), results) results.sortedByDescending { FuzzySearch.ratio(it.name, mediaObj.mainName()) }
} else { } else {
emptyList() emptyList()
} }
response = sortedResults.firstOrNull() response = sortedResults.firstOrNull()
if (response == null) { if (response == null || FuzzySearch.ratio(response.name, mediaObj.mainName()) < 100) {
setUserText("Searching : ${mediaObj.nameRomaji}") setUserText("Searching : ${mediaObj.nameRomaji}")
val romajiResults = search(mediaObj.nameRomaji) val romajiResults = search(mediaObj.nameRomaji)
val sortedRomajiResults = if (romajiResults.isNotEmpty()) { val sortedRomajiResults = if (romajiResults.isNotEmpty()) {
StringMatcher.closestShowMovedToTop(mediaObj.nameRomaji, romajiResults) romajiResults.sortedByDescending { FuzzySearch.ratio(it.name, mediaObj.nameRomaji) }
} else { } else {
emptyList() emptyList()
} }
response = sortedRomajiResults.firstOrNull() val closestRomaji = sortedRomajiResults.firstOrNull()
logger("Closest match from RomajiResults: ${closestRomaji?.name ?: "None"}")
response = if (response == null) {
logger("No exact match found in results. Using closest match from RomajiResults.")
closestRomaji
} else {
val romajiRatio = FuzzySearch.ratio(closestRomaji?.name ?: "", mediaObj.nameRomaji)
val mainNameRatio = FuzzySearch.ratio(response.name, mediaObj.mainName())
logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name}")
logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name ?: "None"}")
if (romajiRatio > mainNameRatio) {
logger("RomajiResults has a closer match. Replacing response.")
closestRomaji
} else {
logger("Results has a closer or equal match. Keeping existing response.")
response
}
}
} }
saveShowResponse(mediaObj.id, response) saveShowResponse(mediaObj.id, response)
} }

View File

@@ -11,9 +11,11 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
abstract class WatchSources : BaseSources() { abstract class WatchSources : BaseSources() {
override operator fun get(i: Int): AnimeParser { override operator fun get(i: Int): AnimeParser {
return (list.getOrNull(i)?:list[0]).get.value as AnimeParser return (list.getOrNull(i) ?: list.firstOrNull())?.get?.value as? AnimeParser
?: EmptyAnimeParser()
} }
suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap<String, Episode> { suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap<String, Episode> {
return tryWithSuspend(true) { return tryWithSuspend(true) {
val res = get(i).autoSearch(media) ?: return@tryWithSuspend mutableMapOf() val res = get(i).autoSearch(media) ?: return@tryWithSuspend mutableMapOf()
@@ -40,7 +42,8 @@ abstract class WatchSources : BaseSources() {
abstract class MangaReadSources : BaseSources() { abstract class MangaReadSources : BaseSources() {
override operator fun get(i: Int): MangaParser { override operator fun get(i: Int): MangaParser {
return (list.getOrNull(i)?:list[0]).get.value as MangaParser return (list.getOrNull(i)?:list.firstOrNull())?.get?.value as? MangaParser
?: EmptyMangaParser()
} }
suspend fun loadChaptersFromMedia(i: Int, media: Media): MutableMap<String, MangaChapter> { suspend fun loadChaptersFromMedia(i: Int, media: Media): MutableMap<String, MangaChapter> {

View File

@@ -33,7 +33,7 @@ abstract class MangaParser : BaseParser() {
* **/ * **/
abstract suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> abstract suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage>
override suspend fun autoSearch(mediaObj: Media): ShowResponse? { /*override suspend fun autoSearch(mediaObj: Media): ShowResponse? {
var response = loadSavedShowResponse(mediaObj.id) var response = loadSavedShowResponse(mediaObj.id)
if (response != null) { if (response != null) {
saveShowResponse(mediaObj.id, response, true) saveShowResponse(mediaObj.id, response, true)
@@ -48,11 +48,22 @@ abstract class MangaParser : BaseParser() {
saveShowResponse(mediaObj.id, response) saveShowResponse(mediaObj.id, response)
} }
return response return response
} }*/
open fun getTransformation(): BitmapTransformation? = null open fun getTransformation(): BitmapTransformation? = null
} }
class EmptyMangaParser: MangaParser() {
override val name: String = "None"
override val saveName: String = "None"
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> = emptyList()
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> = emptyList()
override suspend fun search(query: String): List<ShowResponse> = emptyList()
}
data class MangaChapter( data class MangaChapter(
/** /**
* Number of the Chapter in "String", * Number of the Chapter in "String",

View File

@@ -29,7 +29,9 @@ object MangaSources : MangaReadSources() {
} }
object HMangaSources : MangaReadSources() { object HMangaSources : MangaReadSources() {
val aList: List<Lazier<BaseParser>> = lazyList( val aList: List<Lazier<BaseParser>> = lazyList()
) suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
//todo
}
override val list = listOf(aList,MangaSources.list).flatten() override val list = listOf(aList,MangaSources.list).flatten()
} }

View File

@@ -1,5 +1,7 @@
package ani.dantotsu.parsers package ani.dantotsu.parsers
import ani.dantotsu.logger
class StringMatcher { class StringMatcher {
companion object { companion object {
private fun levenshteinDistance(s1: String, s2: String): Int { private fun levenshteinDistance(s1: String, s2: String): Int {
@@ -52,8 +54,10 @@ class StringMatcher {
val closestShowAndIndex = closestShow(target, shows) val closestShowAndIndex = closestShow(target, shows)
val closestIndex = closestShowAndIndex.second val closestIndex = closestShowAndIndex.second
if (closestIndex == -1) { if (closestIndex == -1) {
logger("No closest show found for $target")
return shows // Return original list if no closest show found return shows // Return original list if no closest show found
} }
logger("Closest show found for $target is ${closestShowAndIndex.first.name}")
return listOf(shows[closestIndex]) + shows.subList(0, closestIndex) + shows.subList(closestIndex + 1, shows.size) return listOf(shows[closestIndex]) + shows.subList(0, closestIndex) + shows.subList(closestIndex + 1, shows.size)
} }

View File

@@ -11,13 +11,17 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
import ani.dantotsu.loadData
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
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
@@ -31,69 +35,125 @@ import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class AnimeExtensionsFragment : Fragment(), SearchQueryHandler { class AnimeExtensionsFragment : Fragment(),
SearchQueryHandler {
private var _binding: FragmentAnimeExtensionsBinding? = null private var _binding: FragmentAnimeExtensionsBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
val skipIcons = loadData("skip_extension_icons") ?: false
private lateinit var extensionsRecyclerView: RecyclerView private lateinit var extensionsRecyclerView: RecyclerView
private lateinit var allextenstionsRecyclerView: RecyclerView private lateinit var allextenstionsRecyclerView: RecyclerView
private val animeExtensionManager: AnimeExtensionManager = Injekt.get<AnimeExtensionManager>() private val animeExtensionManager: AnimeExtensionManager = Injekt.get<AnimeExtensionManager>()
private val extensionsAdapter = AnimeExtensionsAdapter { pkgName -> private val extensionsAdapter = AnimeExtensionsAdapter({ pkg ->
animeExtensionManager.uninstallExtension(pkgName) if (isAdded) { // Check if the fragment is currently added to its activity
} val context = requireContext() // Store context in a variable
private val allExtensionsAdapter = AllAnimeExtensionsAdapter(lifecycleScope) { pkgName ->
val notificationManager = val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
// Start the installation process if (pkg.hasUpdate) {
animeExtensionManager.installExtension(pkgName) animeExtensionManager.updateExtension(pkg)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
.subscribe( .subscribe(
{ installStep -> { installStep ->
val builder = NotificationCompat.Builder( val builder = NotificationCompat.Builder(
requireContext(), context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS Notifications.CHANNEL_DOWNLOADER_PROGRESS
) )
.setSmallIcon(R.drawable.ic_round_sync_24) .setSmallIcon(R.drawable.ic_round_sync_24)
.setContentTitle("Installing extension") .setContentTitle("Updating extension")
.setContentText("Step: $installStep") .setContentText("Step: $installStep")
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build()) notificationManager.notify(1, builder.build())
}, },
{ error -> { error ->
val builder = NotificationCompat.Builder( Log.e("AnimeExtensionsAdapter", "Error: ", error) // Log the error
requireContext(), val builder = NotificationCompat.Builder(
Notifications.CHANNEL_DOWNLOADER_ERROR context,
) Notifications.CHANNEL_DOWNLOADER_ERROR
.setSmallIcon(R.drawable.ic_round_info_24) )
.setContentTitle("Installation failed") .setSmallIcon(R.drawable.ic_round_info_24)
.setContentText("Error: ${error.message}") .setContentTitle("Update failed")
.setPriority(NotificationCompat.PRIORITY_HIGH) .setContentText("Error: ${error.message}")
notificationManager.notify(1, builder.build()) .setPriority(NotificationCompat.PRIORITY_HIGH)
}, notificationManager.notify(1, builder.build())
{ },
val builder = NotificationCompat.Builder( {
requireContext(), val builder = NotificationCompat.Builder(
Notifications.CHANNEL_DOWNLOADER_PROGRESS context,
) Notifications.CHANNEL_DOWNLOADER_PROGRESS
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) )
.setContentTitle("Installation complete") .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
.setContentText("The extension has been successfully installed.") .setContentTitle("Update complete")
.setPriority(NotificationCompat.PRIORITY_LOW) .setContentText("The extension has been successfully updated.")
notificationManager.notify(1, builder.build()) .setPriority(NotificationCompat.PRIORITY_LOW)
} notificationManager.notify(1, builder.build())
) }
)
} else {
animeExtensionManager.uninstallExtension(pkg.pkgName)
}
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { }, skipIcons)
private val allExtensionsAdapter = AllAnimeExtensionsAdapter(lifecycleScope, { pkgName ->
val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Start the installation process
animeExtensionManager.installExtension(pkgName)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ installStep ->
val builder = NotificationCompat.Builder(
requireContext(),
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_round_sync_24)
.setContentTitle("Installing extension")
.setContentText("Step: $installStep")
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
},
{ error ->
val builder = NotificationCompat.Builder(
requireContext(),
Notifications.CHANNEL_DOWNLOADER_ERROR
)
.setSmallIcon(R.drawable.ic_round_info_24)
.setContentTitle("Installation failed")
.setContentText("Error: ${error.message}")
.setPriority(NotificationCompat.PRIORITY_HIGH)
notificationManager.notify(1, builder.build())
},
{
val builder = NotificationCompat.Builder(
requireContext(),
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
.setContentTitle("Installation complete")
.setContentText("The extension has been successfully installed.")
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
}
)
}, skipIcons)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false) _binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
extensionsRecyclerView = binding.animeExtensionsRecyclerView extensionsRecyclerView = binding.animeExtensionsRecyclerView
extensionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
extensionsRecyclerView.adapter = extensionsAdapter extensionsRecyclerView.adapter = extensionsAdapter
allextenstionsRecyclerView = binding.allAnimeExtensionsRecyclerView allextenstionsRecyclerView = binding.allAnimeExtensionsRecyclerView
allextenstionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
allextenstionsRecyclerView.adapter = allExtensionsAdapter allextenstionsRecyclerView.adapter = allExtensionsAdapter
lifecycleScope.launch { lifecycleScope.launch {
@@ -110,11 +170,11 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
Pair(availableExtensions, installedExtensions) Pair(availableExtensions, installedExtensions)
}.collect { pair -> }.collect { pair ->
val (availableExtensions, installedExtensions) = pair val (availableExtensions, installedExtensions) = pair
allExtensionsAdapter.updateData(availableExtensions, installedExtensions) allExtensionsAdapter.updateData(availableExtensions, installedExtensions)
} }
} }
val extensionsRecyclerView: RecyclerView = binding.animeExtensionsRecyclerView val extensionsRecyclerView: RecyclerView = binding.animeExtensionsRecyclerView
return binding.root return binding.root
} }
@@ -123,7 +183,6 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
allExtensionsAdapter.filter("") // Reset the filter allExtensionsAdapter.filter("") // Reset the filter
allextenstionsRecyclerView.visibility = View.VISIBLE allextenstionsRecyclerView.visibility = View.VISIBLE
extensionsRecyclerView.visibility = View.VISIBLE extensionsRecyclerView.visibility = View.VISIBLE
println("asdf: ${allExtensionsAdapter.getItemCount()}")
} else { } else {
allExtensionsAdapter.filter(query) allExtensionsAdapter.filter(query)
allextenstionsRecyclerView.visibility = View.VISIBLE allextenstionsRecyclerView.visibility = View.VISIBLE
@@ -136,13 +195,17 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
} }
private class AnimeExtensionsAdapter(private val onUninstallClicked: (String) -> Unit) : RecyclerView.Adapter<AnimeExtensionsAdapter.ViewHolder>() { private class AnimeExtensionsAdapter(
private val onUninstallClicked: (AnimeExtension.Installed) -> Unit,
skipIcons: Boolean
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
DIFF_CALLBACK_INSTALLED
) {
private var extensions: List<AnimeExtension.Installed> = emptyList() val skipIcons = skipIcons
fun updateData(newExtensions: List<AnimeExtension.Installed>) { fun updateData(newExtensions: List<AnimeExtension.Installed>) {
extensions = newExtensions submitList(newExtensions) // Use submitList instead of manual list handling
notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -152,65 +215,112 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = extensions[position] val extension = getItem(position) // Use getItem() from ListAdapter
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
holder.extensionIconImageView.setImageDrawable(extension.icon) if (!skipIcons) {
holder.closeTextView.text = "Uninstall" holder.extensionIconImageView.setImageDrawable(extension.icon)
}
if (extension.hasUpdate) {
holder.closeTextView.text = "Update"
holder.closeTextView.setTextColor(
ContextCompat.getColor(
holder.itemView.context,
R.color.warning
)
)
} else {
holder.closeTextView.text = "Uninstall"
}
holder.closeTextView.setOnClickListener { holder.closeTextView.setOnClickListener {
onUninstallClicked(extension.pkgName) onUninstallClicked(extension)
} }
} }
override fun getItemCount(): Int = extensions.size
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 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: TextView = view.findViewById(R.id.closeTextView)
} }
companion object {
val DIFF_CALLBACK_INSTALLED =
object : DiffUtil.ItemCallback<AnimeExtension.Installed>() {
override fun areItemsTheSame(
oldItem: AnimeExtension.Installed,
newItem: AnimeExtension.Installed
): Boolean {
return oldItem.pkgName == newItem.pkgName
}
override fun areContentsTheSame(
oldItem: AnimeExtension.Installed,
newItem: AnimeExtension.Installed
): Boolean {
return oldItem == newItem
}
}
}
} }
private class AllAnimeExtensionsAdapter(private val coroutineScope: CoroutineScope,
private val onButtonClicked: (AnimeExtension.Available) -> Unit) : RecyclerView.Adapter<AllAnimeExtensionsAdapter.ViewHolder>() {
private var extensions: List<AnimeExtension.Available> = emptyList()
fun updateData(newExtensions: List<AnimeExtension.Available>, installedExtensions: List<AnimeExtension.Installed> = emptyList()) { private class AllAnimeExtensionsAdapter(
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet() private val coroutineScope: CoroutineScope,
extensions = newExtensions.filter { it.pkgName !in installedPkgNames } private val onButtonClicked: (AnimeExtension.Available) -> Unit,
filteredExtensions = extensions skipIcons: Boolean
notifyDataSetChanged() ) : ListAdapter<AnimeExtension.Available, AllAnimeExtensionsAdapter.ViewHolder>(
DIFF_CALLBACK_AVAILABLE
) {
val skipIcons = skipIcons
fun updateData(
newExtensions: List<AnimeExtension.Available>,
installedExtensions: List<AnimeExtension.Installed> = emptyList()
) {
coroutineScope.launch(Dispatchers.Default) {
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
val filteredExtensions = newExtensions.filter { it.pkgName !in installedPkgNames }
// Switch back to main thread to update UI
withContext(Dispatchers.Main) {
submitList(filteredExtensions)
}
}
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AllAnimeExtensionsAdapter.ViewHolder {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): AllAnimeExtensionsAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context) val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_extension_all, parent, false) .inflate(R.layout.item_extension_all, parent, false)
return ViewHolder(view) return ViewHolder(view)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = filteredExtensions[position] val extension = getItem(position)
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
coroutineScope.launch {
val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl) if (!skipIcons) {
holder.extensionIconImageView.setImageDrawable(drawable) Glide.with(holder.itemView.context)
.load(extension.iconUrl)
.into(holder.extensionIconImageView)
} }
holder.closeTextView.text = "Install" holder.closeTextView.text = "Install"
holder.closeTextView.setOnClickListener { holder.closeTextView.setOnClickListener {
onButtonClicked(extension) onButtonClicked(extension)
} }
} }
override fun getItemCount(): Int = filteredExtensions.size
private var filteredExtensions: List<AnimeExtension.Available> = emptyList()
fun filter(query: String) { fun filter(query: String) {
filteredExtensions = if (query.isEmpty()) { val filteredExtensions = if (query.isEmpty()) {
extensions currentList
} else { } else {
extensions.filter { it.name.contains(query, ignoreCase = true) } currentList.filter { it.name.contains(query, ignoreCase = true) }
} }
notifyDataSetChanged() submitList(filteredExtensions)
} }
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@@ -219,18 +329,24 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
val closeTextView: TextView = view.findViewById(R.id.closeTextView) val closeTextView: TextView = view.findViewById(R.id.closeTextView)
} }
suspend fun urlToDrawable(context: Context, url: String): Drawable? { companion object {
return withContext(Dispatchers.IO) { val DIFF_CALLBACK_AVAILABLE =
try { object : DiffUtil.ItemCallback<AnimeExtension.Available>() {
return@withContext Glide.with(context) override fun areItemsTheSame(
.load(url) oldItem: AnimeExtension.Available,
.submit() newItem: AnimeExtension.Available
.get() ): Boolean {
} catch (e: Exception) { return oldItem.pkgName == newItem.pkgName
e.printStackTrace() }
return@withContext null
override fun areContentsTheSame(
oldItem: AnimeExtension.Available,
newItem: AnimeExtension.Available
): Boolean {
return oldItem == newItem
}
} }
}
} }
} }
} }

View File

@@ -13,6 +13,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.SearchView import android.widget.SearchView
import android.widget.TextView import android.widget.TextView
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
@@ -49,17 +50,13 @@ import uy.kohesive.injekt.injectLazy
import javax.inject.Inject import javax.inject.Inject
class ExtensionsActivity : AppCompatActivity() { class ExtensionsActivity : AppCompatActivity() {
private val restartMainActivity = object : OnBackPressedCallback(false) { private val restartMainActivity = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() = startMainActivity(this@ExtensionsActivity) override fun handleOnBackPressed() = startMainActivity(this@ExtensionsActivity)
} }
lateinit var binding: ActivityExtensionsBinding lateinit var binding: ActivityExtensionsBinding
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -132,4 +129,4 @@ class ExtensionsActivity : AppCompatActivity() {
interface SearchQueryHandler { interface SearchQueryHandler {
fun updateContentBasedOnQuery(query: String?) fun updateContentBasedOnQuery(query: String?)
} }

View File

@@ -11,26 +11,96 @@ import ani.dantotsu.initActivity
class FAQActivity : AppCompatActivity() { class FAQActivity : AppCompatActivity() {
private lateinit var binding: ActivityFaqBinding private lateinit var binding: ActivityFaqBinding
private val faqs = listOf( private val faqs by lazy {
listOf(
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_1), currContext()!!.getString(R.string.answer_1)), Triple(
Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_2), currContext()!!.getString(R.string.answer_2)), R.drawable.ic_round_help_24,
Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_17), currContext()!!.getString(R.string.answer_17)), currContext()!!.getString(R.string.question_1),
Triple(R.drawable.ic_round_download_24, currContext()!!.getString(R.string.question_3), currContext()!!.getString(R.string.answer_3)), currContext()!!.getString(R.string.answer_1)
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_16), currContext()!!.getString(R.string.answer_16)), ),
Triple(R.drawable.ic_round_dns_24, currContext()!!.getString(R.string.question_4), currContext()!!.getString(R.string.answer_4)), Triple(
Triple(R.drawable.ic_baseline_screen_lock_portrait_24, currContext()!!.getString(R.string.question_5), currContext()!!.getString(R.string.answer_5)), R.drawable.ic_round_auto_awesome_24,
Triple(R.drawable.ic_anilist, currContext()!!.getString(R.string.question_6), currContext()!!.getString(R.string.answer_6)), currContext()!!.getString(R.string.question_2),
Triple(R.drawable.ic_round_movie_filter_24, currContext()!!.getString(R.string.question_7), currContext()!!.getString(R.string.answer_7)), currContext()!!.getString(R.string.answer_2)
Triple(R.drawable.ic_round_menu_book_24, currContext()!!.getString(R.string.question_8), currContext()!!.getString(R.string.answer_8)), ),
Triple(R.drawable.ic_round_lock_open_24, currContext()!!.getString(R.string.question_9), currContext()!!.getString(R.string.answer_9)), Triple(
Triple(R.drawable.ic_round_smart_button_24, currContext()!!.getString(R.string.question_10), currContext()!!.getString(R.string.answer_10)), R.drawable.ic_round_auto_awesome_24,
Triple(R.drawable.ic_round_smart_button_24, currContext()!!.getString(R.string.question_11), currContext()!!.getString(R.string.answer_11)), currContext()!!.getString(R.string.question_17),
Triple(R.drawable.ic_round_info_24, currContext()!!.getString(R.string.question_12), currContext()!!.getString(R.string.answer_12)), currContext()!!.getString(R.string.answer_17)
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_13), currContext()!!.getString(R.string.answer_13)), ),
Triple(R.drawable.ic_round_art_track_24, currContext()!!.getString(R.string.question_14), currContext()!!.getString(R.string.answer_14)), Triple(
Triple(R.drawable.ic_round_video_settings_24, currContext()!!.getString(R.string.question_15), currContext()!!.getString(R.string.answer_15)) R.drawable.ic_round_download_24,
currContext()!!.getString(R.string.question_3),
currContext()!!.getString(R.string.answer_3)
),
Triple(
R.drawable.ic_round_help_24,
currContext()!!.getString(R.string.question_16),
currContext()!!.getString(R.string.answer_16)
),
Triple(
R.drawable.ic_round_dns_24,
currContext()!!.getString(R.string.question_4),
currContext()!!.getString(R.string.answer_4)
),
Triple(
R.drawable.ic_baseline_screen_lock_portrait_24,
currContext()!!.getString(R.string.question_5),
currContext()!!.getString(R.string.answer_5)
),
Triple(
R.drawable.ic_anilist,
currContext()!!.getString(R.string.question_6),
currContext()!!.getString(R.string.answer_6)
),
Triple(
R.drawable.ic_round_movie_filter_24,
currContext()!!.getString(R.string.question_7),
currContext()!!.getString(R.string.answer_7)
),
Triple(
R.drawable.ic_round_menu_book_24,
currContext()!!.getString(R.string.question_8),
currContext()!!.getString(R.string.answer_8)
),
Triple(
R.drawable.ic_round_lock_open_24,
currContext()!!.getString(R.string.question_9),
currContext()!!.getString(R.string.answer_9)
),
Triple(
R.drawable.ic_round_smart_button_24,
currContext()!!.getString(R.string.question_10),
currContext()!!.getString(R.string.answer_10)
),
Triple(
R.drawable.ic_round_smart_button_24,
currContext()!!.getString(R.string.question_11),
currContext()!!.getString(R.string.answer_11)
),
Triple(
R.drawable.ic_round_info_24,
currContext()!!.getString(R.string.question_12),
currContext()!!.getString(R.string.answer_12)
),
Triple(
R.drawable.ic_round_help_24,
currContext()!!.getString(R.string.question_13),
currContext()!!.getString(R.string.answer_13)
),
Triple(
R.drawable.ic_round_art_track_24,
currContext()!!.getString(R.string.question_14),
currContext()!!.getString(R.string.answer_14)
),
Triple(
R.drawable.ic_round_video_settings_24,
currContext()!!.getString(R.string.question_15),
currContext()!!.getString(R.string.answer_15)
)
) )
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@@ -4,19 +4,24 @@ import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
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.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentMangaBinding import ani.dantotsu.databinding.FragmentMangaBinding
import ani.dantotsu.databinding.FragmentMangaExtensionsBinding import ani.dantotsu.databinding.FragmentMangaExtensionsBinding
import ani.dantotsu.loadData
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
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
@@ -30,18 +35,69 @@ import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class MangaExtensionsFragment : Fragment(), SearchQueryHandler { class MangaExtensionsFragment : Fragment(),
SearchQueryHandler {
private var _binding: FragmentMangaExtensionsBinding? = null private var _binding: FragmentMangaExtensionsBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
val skipIcons = loadData("skip_extension_icons") ?: false
private lateinit var extensionsRecyclerView: RecyclerView private lateinit var extensionsRecyclerView: RecyclerView
private lateinit var allextenstionsRecyclerView: RecyclerView private lateinit var allextenstionsRecyclerView: RecyclerView
private val mangaExtensionManager:MangaExtensionManager = Injekt.get<MangaExtensionManager>() private val mangaExtensionManager: MangaExtensionManager = Injekt.get<MangaExtensionManager>()
private val extensionsAdapter = MangaExtensionsAdapter { pkgName -> private val extensionsAdapter = MangaExtensionsAdapter({ pkg ->
mangaExtensionManager.uninstallExtension(pkgName) if (isAdded) { // Check if the fragment is currently added to its activity
} val context = requireContext() // Store context in a variable
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
if (pkg.hasUpdate) {
mangaExtensionManager.updateExtension(pkg)
.observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
.subscribe(
{ installStep ->
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_round_sync_24)
.setContentTitle("Updating extension")
.setContentText("Step: $installStep")
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
},
{ error ->
Log.e("MangaExtensionsAdapter", "Error: ", error) // Log the error
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_ERROR
)
.setSmallIcon(R.drawable.ic_round_info_24)
.setContentTitle("Update failed")
.setContentText("Error: ${error.message}")
.setPriority(NotificationCompat.PRIORITY_HIGH)
notificationManager.notify(1, builder.build())
},
{
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
.setContentTitle("Update complete")
.setContentText("The extension has been successfully updated.")
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
}
)
} else {
mangaExtensionManager.uninstallExtension(pkg.pkgName)
}
}
}, skipIcons)
private val allExtensionsAdapter = private val allExtensionsAdapter =
AllMangaExtensionsAdapter(lifecycleScope) { pkgName -> AllMangaExtensionsAdapter(lifecycleScope, { pkgName ->
val notificationManager = val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -84,16 +140,21 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
notificationManager.notify(1, builder.build()) notificationManager.notify(1, builder.build())
} }
) )
} }, skipIcons)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false) _binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
extensionsRecyclerView = binding.mangaExtensionsRecyclerView extensionsRecyclerView = binding.mangaExtensionsRecyclerView
extensionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
extensionsRecyclerView.adapter = extensionsAdapter extensionsRecyclerView.adapter = extensionsAdapter
allextenstionsRecyclerView = binding.allMangaExtensionsRecyclerView allextenstionsRecyclerView = binding.allMangaExtensionsRecyclerView
allextenstionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
allextenstionsRecyclerView.adapter = allExtensionsAdapter allextenstionsRecyclerView.adapter = allExtensionsAdapter
lifecycleScope.launch { lifecycleScope.launch {
@@ -114,7 +175,6 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
} }
} }
val extensionsRecyclerView: RecyclerView = binding.mangaExtensionsRecyclerView val extensionsRecyclerView: RecyclerView = binding.mangaExtensionsRecyclerView
return binding.root return binding.root
} }
@@ -134,13 +194,18 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
super.onDestroyView();_binding = null super.onDestroyView();_binding = null
} }
private class MangaExtensionsAdapter(private val onUninstallClicked: (String) -> Unit) : RecyclerView.Adapter<MangaExtensionsAdapter.ViewHolder>() { private class MangaExtensionsAdapter(
private val onUninstallClicked: (MangaExtension.Installed) -> Unit,
skipIcons: Boolean
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
DIFF_CALLBACK_INSTALLED
) {
private var extensions: List<MangaExtension.Installed> = emptyList() val skipIcons = skipIcons
// Use submitList to update data
fun updateData(newExtensions: List<MangaExtension.Installed>) { fun updateData(newExtensions: List<MangaExtension.Installed>) {
extensions = newExtensions submitList(newExtensions)
notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -150,65 +215,118 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = extensions[position] val extension = getItem(position) // Use getItem from ListAdapter
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
holder.extensionIconImageView.setImageDrawable(extension.icon) if (!skipIcons) {
holder.closeTextView.text = "Uninstall" holder.extensionIconImageView.setImageDrawable(extension.icon)
}
if (extension.hasUpdate) {
holder.closeTextView.text = "Update"
holder.closeTextView.setTextColor(
ContextCompat.getColor(
holder.itemView.context,
R.color.warning
)
)
} else {
holder.closeTextView.text = "Uninstall"
}
holder.closeTextView.setOnClickListener { holder.closeTextView.setOnClickListener {
onUninstallClicked(extension.pkgName) onUninstallClicked(extension)
} }
} }
override fun getItemCount(): Int = extensions.size
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 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: TextView = view.findViewById(R.id.closeTextView)
} }
companion object {
val DIFF_CALLBACK_INSTALLED =
object : DiffUtil.ItemCallback<MangaExtension.Installed>() {
override fun areItemsTheSame(
oldItem: MangaExtension.Installed,
newItem: MangaExtension.Installed
): Boolean {
return oldItem.pkgName == newItem.pkgName
}
override fun areContentsTheSame(
oldItem: MangaExtension.Installed,
newItem: MangaExtension.Installed
): Boolean {
return oldItem == newItem
}
}
}
} }
private class AllMangaExtensionsAdapter(private val coroutineScope: CoroutineScope,
private val onButtonClicked: (MangaExtension.Available) -> Unit) : RecyclerView.Adapter<AllMangaExtensionsAdapter.ViewHolder>() {
private var extensions: List<MangaExtension.Available> = emptyList()
fun updateData(newExtensions: List<MangaExtension.Available>, installedExtensions: List<MangaExtension.Installed> = emptyList()) { private class AllMangaExtensionsAdapter(
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet() private val coroutineScope: CoroutineScope,
extensions = newExtensions.filter { it.pkgName !in installedPkgNames } private val onButtonClicked: (MangaExtension.Available) -> Unit,
filteredExtensions = extensions skipIcons: Boolean
notifyDataSetChanged() ) : ListAdapter<MangaExtension.Available, AllMangaExtensionsAdapter.ViewHolder>(
DIFF_CALLBACK_AVAILABLE
) {
init {
setHasStableIds(true)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AllMangaExtensionsAdapter.ViewHolder {
val skipIcons = skipIcons
// Use submitList to update the data
fun updateData(
newExtensions: List<MangaExtension.Available>,
installedExtensions: List<MangaExtension.Installed> = emptyList()
) {
coroutineScope.launch(Dispatchers.Default) {
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
val filteredExtensions = newExtensions.filter { it.pkgName !in installedPkgNames }
// Switch back to main thread to update UI
withContext(Dispatchers.Main) {
submitList(filteredExtensions)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context) val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_extension_all, parent, false) .inflate(R.layout.item_extension_all, parent, false)
return ViewHolder(view) return ViewHolder(view)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = filteredExtensions[position] val extension = getItem(position) // Use getItem from ListAdapter
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
coroutineScope.launch { if (!skipIcons) {
val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl) Glide.with(holder.itemView.context)
holder.extensionIconImageView.setImageDrawable(drawable) .load(extension.iconUrl)
.into(holder.extensionIconImageView)
} }
holder.closeTextView.text = "Install" holder.closeTextView.text = "Install"
holder.closeTextView.setOnClickListener { holder.closeTextView.setOnClickListener {
onButtonClicked(extension) onButtonClicked(extension)
} }
} }
override fun getItemCount(): Int = filteredExtensions.size // Filtering function
private var filteredExtensions: List<MangaExtension.Available> = emptyList()
fun filter(query: String) { fun filter(query: String) {
filteredExtensions = if (query.isEmpty()) { val filteredExtensions = if (query.isEmpty()) {
extensions currentList
} else { } else {
extensions.filter { it.name.contains(query, ignoreCase = true) } currentList.filter { it.name.contains(query, ignoreCase = true) }
} }
notifyDataSetChanged() submitList(filteredExtensions)
} }
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@@ -217,18 +335,24 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
val closeTextView: TextView = view.findViewById(R.id.closeTextView) val closeTextView: TextView = view.findViewById(R.id.closeTextView)
} }
suspend fun urlToDrawable(context: Context, url: String): Drawable? { companion object {
return withContext(Dispatchers.IO) { val DIFF_CALLBACK_AVAILABLE =
try { object : DiffUtil.ItemCallback<MangaExtension.Available>() {
return@withContext Glide.with(context) override fun areItemsTheSame(
.load(url) oldItem: MangaExtension.Available,
.submit() newItem: MangaExtension.Available
.get() ): Boolean {
} catch (e: Exception) { return oldItem.pkgName == newItem.pkgName
e.printStackTrace() }
return@withContext null
override fun areContentsTheSame(
oldItem: MangaExtension.Available,
newItem: MangaExtension.Available
): Boolean {
return oldItem == newItem
}
} }
}
} }
} }
} }

View File

@@ -2,6 +2,7 @@ package ani.dantotsu.settings
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.os.Build.* import android.os.Build.*
@@ -31,6 +32,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 eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.network.NetworkPreferences
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -47,6 +49,7 @@ 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>()
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -92,18 +95,23 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
val animeSourceName = loadData<String>("settings_def_anime_source") ?: AnimeSources.names[0] binding.settingsUseMaterialYou.isChecked = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_material_you", false)
// Set the dropdown item in the UI if the name exists in the list. binding.settingsUseMaterialYou.setOnCheckedChangeListener { _, isChecked ->
if (AnimeSources.names.contains(animeSourceName)) { getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_material_you", isChecked).apply()
binding.animeSource.setText(animeSourceName, false) }
//val animeSource = loadData<Int>("settings_def_anime_source_s")?.let { if (it >= AnimeSources.names.size) 0 else it } ?: 0
val animeSource = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("settings_def_anime_source_s_r", 0)
if (AnimeSources.names.isNotEmpty() && animeSource in 0 until AnimeSources.names.size) {
binding.animeSource.setText(AnimeSources.names[animeSource], false)
} }
binding.animeSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, AnimeSources.names)) binding.animeSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, AnimeSources.names))
// Set up the item click listener for the dropdown.
binding.animeSource.setOnItemClickListener { _, _, i, _ -> binding.animeSource.setOnItemClickListener { _, _, i, _ ->
val selectedName = AnimeSources.names[i] //saveData("settings_def_anime_source_s", i)
// Save the string name of the selected item. getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putInt("settings_def_anime_source_s_r", i).apply()
saveData("settings_def_anime_source", selectedName)
binding.animeSource.clearFocus() binding.animeSource.clearFocus()
} }
@@ -131,6 +139,17 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
} }
} }
binding.skipExtensionIcons.isChecked = loadData("skip_extension_icons") ?: false
binding.skipExtensionIcons.setOnCheckedChangeListener { _, isChecked ->
saveData("skip_extension_icons", isChecked)
}
binding.userAgent.setText(networkPreferences.defaultUserAgent().get())
binding.userAgent.setOnEditorActionListener { _, _, _ ->
networkPreferences.defaultUserAgent().set(binding.userAgent.text.toString())
true
}
binding.settingsDownloadInSd.isChecked = loadData("sd_dl") ?: false binding.settingsDownloadInSd.isChecked = loadData("sd_dl") ?: false
binding.settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked -> binding.settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) { if (isChecked) {
@@ -169,12 +188,10 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
saveData("settings_prefer_dub", isChecked) saveData("settings_prefer_dub", isChecked)
} }
// Load the saved manga source name from data storage. //val mangaSource = loadData<Int>("settings_def_manga_source_s")?.let { if (it >= MangaSources.names.size) 0 else it } ?: 0
val mangaSourceName = loadData<String>("settings_def_manga_source") ?: MangaSources.names[0] val mangaSource = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("settings_def_manga_source_s_r", 0)
if (MangaSources.names.isNotEmpty() && mangaSource in 0 until MangaSources.names.size) {
// Set the dropdown item in the UI if the name exists in the list. binding.mangaSource.setText(MangaSources.names[mangaSource], false)
if (MangaSources.names.contains(mangaSourceName)) {
binding.mangaSource.setText(mangaSourceName, false)
} }
// Set up the dropdown adapter. // Set up the dropdown adapter.
@@ -182,9 +199,8 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
// Set up the item click listener for the dropdown. // Set up the item click listener for the dropdown.
binding.mangaSource.setOnItemClickListener { _, _, i, _ -> binding.mangaSource.setOnItemClickListener { _, _, i, _ ->
val selectedName = MangaSources.names[i] //saveData("settings_def_manga_source_s", i)
// Save the string name of the selected item. getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putInt("settings_def_manga_source_s_r", i).apply()
saveData("settings_def_manga_source", selectedName)
binding.mangaSource.clearFocus() binding.mangaSource.clearFocus()
} }

View File

@@ -14,22 +14,15 @@ import kotlinx.coroutines.withTimeoutOrNull
class SubscriptionHelper { class SubscriptionHelper {
companion object { companion object {
private fun loadSelected(context: Context, mediaId: Int, isAdult: Boolean, isAnime: Boolean): Selected { private fun loadSelected(context: Context, mediaId: Int, isAdult: Boolean, isAnime: Boolean): Selected {
val sharedPreferences = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
val data = loadData<Selected>("${mediaId}-select", context) ?: Selected().let { val data = loadData<Selected>("${mediaId}-select", context) ?: Selected().let {
it.source = it.sourceIndex =
if (isAdult) "" if (isAdult) 0
else if (isAnime) {loadData("settings_def_anime_source", context) ?: ""} else if (isAnime) {sharedPreferences.getInt("settings_def_anime_source_s_r",0)}
else loadData("settings_def_manga_source", context) ?: "" else {sharedPreferences.getInt("settings_def_manga_source_s_r",0)}
it.preferDub = loadData("settings_prefer_dub", context) ?: false it.preferDub = loadData("settings_prefer_dub", context) ?: false
it it
} }
if (isAnime){
val sources = if (isAdult) HAnimeSources else AnimeSources
data.sourceIndex = sources.list.indexOfFirst { it.name == data.source }
}else{
val sources = if (isAdult) HMangaSources else MangaSources
data.sourceIndex = sources.list.indexOfFirst { it.name == data.source }
}
if (data.sourceIndex == -1) {data.sourceIndex = 0}
return data return data
} }
@@ -40,9 +33,7 @@ class SubscriptionHelper {
fun getAnimeParser(context: Context, isAdult: Boolean, id: Int): AnimeParser { fun getAnimeParser(context: Context, isAdult: Boolean, id: Int): AnimeParser {
val sources = if (isAdult) HAnimeSources else AnimeSources val sources = if (isAdult) HAnimeSources else AnimeSources
val selected = loadSelected(context, id, isAdult, true) val selected = loadSelected(context, id, isAdult, true)
var location = sources.list.indexOfFirst { it.name == selected.source } val parser = sources[selected.sourceIndex]
if (location == -1) {location = 0}
val parser = sources[location]
parser.selectDub = selected.preferDub parser.selectDub = selected.preferDub
return parser return parser
} }
@@ -69,9 +60,7 @@ class SubscriptionHelper {
fun getMangaParser(context: Context, isAdult: Boolean, id: Int): MangaParser { fun getMangaParser(context: Context, isAdult: Boolean, id: Int): MangaParser {
val sources = if (isAdult) HMangaSources else MangaSources val sources = if (isAdult) HMangaSources else MangaSources
val selected = loadSelected(context, id, isAdult, false) val selected = loadSelected(context, id, isAdult, false)
var location = sources.list.indexOfFirst { it.name == selected.source } return sources[selected.sourceIndex]
if (location == -1) {location = 0}
return sources[location]
} }
suspend fun getChapter(context: Context, parser: MangaParser, id: Int, isAdult: Boolean): MangaChapter? { suspend fun getChapter(context: Context, parser: MangaParser, id: Int, isAdult: Boolean): MangaChapter? {

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import okhttp3.Headers import okhttp3.Headers
import rx.subjects.Subject import rx.subjects.Subject
import java.io.Serializable
data class Track(val url: String, val lang: String) data class Track(val url: String, val lang: String)
@@ -17,7 +18,7 @@ open class Video(
// "url", "language-label-2", "url2", "language-label-2" // "url", "language-label-2", "url2", "language-label-2"
val subtitleTracks: List<Track> = emptyList(), val subtitleTracks: List<Track> = emptyList(),
val audioTracks: List<Track> = emptyList(), val audioTracks: List<Track> = emptyList(),
) : ProgressListener { ) : Serializable, ProgressListener {
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
constructor( constructor(

View File

@@ -11,11 +11,13 @@ import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
import okhttp3.Cache import okhttp3.Cache
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class NetworkHelper( class NetworkHelper(
context: Context, context: Context,
private val preferences: NetworkPreferences,
) { ) {
private val cacheDir = File(context.cacheDir, "network_cache") private val cacheDir = File(context.cacheDir, "network_cache")
@@ -40,18 +42,17 @@ class NetworkHelper(
.addInterceptor(UncaughtExceptionInterceptor()) .addInterceptor(UncaughtExceptionInterceptor())
.addInterceptor(userAgentInterceptor) .addInterceptor(userAgentInterceptor)
/*if (preferences.verboseLogging().get()) { if (preferences.verboseLogging().get()) {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply { val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS level = HttpLoggingInterceptor.Level.HEADERS
} }
builder.addNetworkInterceptor(httpLoggingInterceptor) builder.addNetworkInterceptor(httpLoggingInterceptor)
}*/ }
//when (preferences.dohProvider().get()) { when (preferences.dohProvider().get()) {
when (PREF_DOH_CLOUDFLARE) {
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
PREF_DOH_GOOGLE -> builder.dohGoogle() PREF_DOH_GOOGLE -> builder.dohGoogle()
/*PREF_DOH_ADGUARD -> builder.dohAdGuard() PREF_DOH_ADGUARD -> builder.dohAdGuard()
PREF_DOH_QUAD9 -> builder.dohQuad9() PREF_DOH_QUAD9 -> builder.dohQuad9()
PREF_DOH_ALIDNS -> builder.dohAliDNS() PREF_DOH_ALIDNS -> builder.dohAliDNS()
PREF_DOH_DNSPOD -> builder.dohDNSPod() PREF_DOH_DNSPOD -> builder.dohDNSPod()
@@ -60,7 +61,7 @@ class NetworkHelper(
PREF_DOH_MULLVAD -> builder.dohMullvad() PREF_DOH_MULLVAD -> builder.dohMullvad()
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()
} }
return builder return builder
@@ -75,5 +76,5 @@ class NetworkHelper(
.build() .build()
} }
fun defaultUserAgentProvider() = "Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36"//preferences.defaultUserAgent().get().trim() fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim()
} }

View File

@@ -0,0 +1,22 @@
package eu.kanade.tachiyomi.network
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
class NetworkPreferences(
private val preferenceStore: PreferenceStore,
private val verboseLogging: Boolean = false,
) {
fun verboseLogging(): Preference<Boolean> {
return preferenceStore.getBoolean("verbose_logging", verboseLogging)
}
fun dohProvider(): Preference<Int> {
return preferenceStore.getInt("doh_provider", 1)
}
fun defaultUserAgent(): Preference<String> {
return preferenceStore.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0")
}
}

View File

@@ -0,0 +1,67 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="320dp"
android:height="180dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.6666667"
android:scaleY="0.6666667"
android:translateX="18"
android:translateY="18">
<group android:scaleX="0.5625"
android:translateX="-5.535">
<path
android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:fillType="evenOdd">
<aapt:attr name="android:fillColor">
<gradient
android:startY="49.59793"
android:startX="42.9492"
android:endY="92.4963"
android:endX="85.84757"
android:type="linear">
<item
android:color="#44000000"
android:offset="0"/>
<item
android:color="#00000000"
android:offset="1"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:fillColor="#3DDC84"
android:fillType="evenOdd"
android:strokeWidth="1"
android:strokeColor="#00000000"/>
</group>
<group android:scaleX="0.492"
android:scaleY="0.24750866"
android:translateX="277.2"
android:translateY="73.782364">
<group android:translateY="145.33594">
<path android:pathData="M12.796875,-0L12.796875,-101.109375L34.171875,-101.109375Q42.828125,-101.109375,50.234375,-100.234375Q57.65625,-99.359375,63.84375,-97.171875Q70.03125,-95,74.703125,-91.375Q79.390625,-87.75,82.765625,-82.125Q86.0625,-76.5,87.75,-68.765625Q89.4375,-61.03125,89.4375,-50.5625Q89.4375,-40.078125,87.75,-32.34375Q86.0625,-24.609375,82.765625,-18.984375Q79.390625,-13.359375,74.703125,-9.734375Q70.03125,-6.125,63.84375,-3.9375Q57.65625,-1.765625,50.234375,-0.875Q42.828125,-0,34.171875,-0L12.796875,-0ZM36.359375,-10.828125Q47.109375,-10.828125,54.59375,-12.96875Q62.09375,-15.125,66.875,-19.96875Q71.578125,-24.828125,73.71875,-32.3125Q75.875,-39.796875,75.875,-50.5625Q75.875,-61.3125,73.71875,-68.796875Q71.578125,-76.296875,66.875,-81.140625Q62.09375,-86,54.59375,-88.140625Q47.109375,-90.28125,36.359375,-90.28125L26.015625,-90.28125L26.015625,-10.828125L36.359375,-10.828125Z"
android:fillColor="#000000"/>
<path android:pathData="M113.109375,-60.1875L110.015625,-70.03125Q121.828125,-74.75,134.76562,-74.75Q146.07812,-74.75,152.0625,-70.953125Q158.875,-66.59375,158.875,-56.109375L158.875,-27.359375Q158.875,-21.59375,159.01562,-15.546875Q159.09375,-11.953125,159.71875,-8.09375Q160.28125,-4.4375,161.20312,-1.125L149.67188,1.40625Q148.04688,-3.3125,147.625,-9.140625L146.21875,-9.28125Q137.5,1.6875,124,1.6875Q114.15625,1.6875,108.171875,-3.765625Q102.203125,-9.21875,102.203125,-19.125Q102.203125,-26.515625,105.46875,-31.21875Q108.75,-35.9375,114.71875,-39.171875Q124.140625,-44.296875,141.09375,-44.296875Q143.34375,-44.296875,147.14062,-44.15625L147.14062,-54.84375Q147.14062,-59.90625,144.04688,-62.15625Q140.95312,-64.40625,134.34375,-64.40625Q124.703125,-64.40625,113.109375,-60.1875ZM147.14062,-20.53125L147.14062,-35.296875Q145.23438,-35.4375,141.57812,-35.4375Q135.39062,-35.4375,130.1875,-34.390625Q122.453125,-32.84375,118.203125,-29.5Q113.953125,-26.15625,113.953125,-19.90625Q113.953125,-8.65625,126.1875,-8.65625Q132.51562,-8.65625,138,-12.03125Q142.5,-14.84375,147.14062,-20.53125Z"
android:fillColor="#000000"/>
<path android:pathData="M179.5,-71.859375L191.17188,-74.46875Q192.5,-70.25,193.0625,-66.3125Q193.34375,-64.40625,193.76562,-58.78125L195.25,-58.78125Q199.10938,-65.890625,204.8125,-70.109375Q211.0625,-74.75,218.03125,-74.75Q228.5,-74.75,233.98438,-69.6875Q238.70312,-65.390625,240.53125,-56.53125Q241.71875,-50.484375,241.71875,-38.53125L241.71875,0L229.625,0L229.625,-41.90625Q229.625,-52.390625,227.59375,-56.890625Q224.5625,-63.5625,215.42188,-63.5625Q208.95312,-63.5625,202.76562,-57.375Q198.625,-53.234375,193.90625,-45.21875L193.90625,0L181.8125,0L181.8125,-45Q181.8125,-54.5,181.32812,-61.390625Q181.04688,-65.671875,179.5,-71.859375Z"
android:fillColor="#000000"/>
<path android:pathData="M269.28125,-89.859375L281.375,-89.859375L281.375,-73.125L305.98438,-73.125L305.98438,-62.9375L281.375,-62.9375L281.375,-20.953125Q281.375,-16.734375,281.625,-14.515625Q281.875,-12.3125,282.85938,-11.046875Q284.6875,-8.515625,290.23438,-8.515625Q293.32812,-8.515625,296.21875,-9.140625Q299.17188,-9.78125,301.625,-10.546875Q303.59375,-6.265625,305.21875,-1.125Q296.92188,1.6875,288.48438,1.6875Q282.35938,1.6875,278.67188,0.3125Q274.98438,-1.0625,272.875,-4.15625Q270.6875,-7.390625,269.98438,-11.890625Q269.28125,-16.390625,269.28125,-24.328125L269.28125,-62.9375L258.73438,-62.9375L258.73438,-73.125L269.28125,-73.125L269.28125,-89.859375Z"
android:fillColor="#000000"/>
<path android:pathData="M351.03125,-74.75Q354.46875,-74.75,358.1875,-74.21875Q361.92188,-73.6875,365.51562,-72.21875Q369.09375,-70.671875,372.29688,-68.03125Q375.5,-65.390625,377.95312,-61.171875Q380.42188,-56.890625,381.82812,-50.875Q383.23438,-44.859375,383.23438,-36.5625Q383.23438,-28.265625,381.82812,-22.25Q380.42188,-16.25,377.95312,-12.03125Q375.5,-7.734375,372.29688,-5.0625Q369.09375,-2.390625,365.51562,-0.921875Q361.92188,0.640625,358.1875,1.15625Q354.46875,1.6875,351.03125,1.6875Q347.65625,1.6875,343.95312,1.15625Q340.26562,0.640625,336.6875,-0.921875Q333.09375,-2.390625,329.92188,-5.0625Q326.76562,-7.734375,324.3125,-12.03125Q321.84375,-16.25,320.4375,-22.25Q319.03125,-28.265625,319.03125,-36.5625Q319.03125,-44.859375,320.4375,-50.875Q321.84375,-56.890625,324.3125,-61.171875Q326.76562,-65.390625,329.92188,-68.03125Q333.09375,-70.671875,336.6875,-72.21875Q340.26562,-73.6875,343.92188,-74.21875Q347.57812,-74.75,351.03125,-74.75ZM351.03125,-64.828125Q347.01562,-64.828125,343.46875,-63.5625Q339.92188,-62.296875,337.25,-59.140625Q334.5,-55.90625,332.875,-50.484375Q331.26562,-45.078125,331.26562,-36.5625Q331.26562,-28.125,332.875,-22.671875Q334.5,-17.234375,337.25,-14.0625Q339.92188,-10.828125,343.46875,-9.59375Q347.01562,-8.375,351.03125,-8.375Q355.10938,-8.375,358.71875,-9.59375Q362.34375,-10.828125,365.09375,-14.0625Q367.82812,-17.234375,369.40625,-22.671875Q371,-28.125,371,-36.5625Q371,-45.078125,369.40625,-50.484375Q367.82812,-55.90625,365.09375,-59.140625Q362.34375,-62.296875,358.71875,-63.5625Q355.10938,-64.828125,351.03125,-64.828125Z"
android:fillColor="#000000"/>
<path android:pathData="M408.28125,-89.859375L420.375,-89.859375L420.375,-73.125L444.98438,-73.125L444.98438,-62.9375L420.375,-62.9375L420.375,-20.953125Q420.375,-16.734375,420.625,-14.515625Q420.875,-12.3125,421.85938,-11.046875Q423.6875,-8.515625,429.23438,-8.515625Q432.32812,-8.515625,435.21875,-9.140625Q438.17188,-9.78125,440.625,-10.546875Q442.59375,-6.265625,444.21875,-1.125Q435.92188,1.6875,427.48438,1.6875Q421.35938,1.6875,417.67188,0.3125Q413.98438,-1.0625,411.875,-4.15625Q409.6875,-7.390625,408.98438,-11.890625Q408.28125,-16.390625,408.28125,-24.328125L408.28125,-62.9375L397.73438,-62.9375L397.73438,-73.125L408.28125,-73.125L408.28125,-89.859375Z"
android:fillColor="#000000"/>
<path android:pathData="M484.54688,-74.75Q490.3125,-74.75,496.10938,-73.546875Q501.90625,-72.359375,506.90625,-70.25Q505.92188,-68,504.85938,-65.8125Q503.8125,-63.640625,502.54688,-61.53125Q500.78125,-62.234375,498.64062,-62.9375Q496.5,-63.640625,494.14062,-64.125Q491.78125,-64.625,489.28125,-64.9375Q486.79688,-65.25,484.40625,-65.25Q481.79688,-65.25,479.375,-64.71875Q476.95312,-64.203125,475.07812,-63Q473.21875,-61.8125,472.09375,-59.875Q470.96875,-57.9375,470.96875,-55.0625Q470.96875,-52.109375,472.23438,-50.09375Q473.5,-48.09375,475.67188,-46.75Q477.85938,-45.421875,480.78125,-44.5Q483.70312,-43.59375,487,-42.828125Q492.14062,-41.5625,496.5625,-39.96875Q501,-38.390625,504.23438,-35.859375Q507.46875,-33.328125,509.29688,-29.59375Q511.125,-25.875,511.125,-20.390625Q511.125,-14.625,508.82812,-10.46875Q506.54688,-6.328125,502.64062,-3.625Q498.75,-0.921875,493.54688,0.375Q488.34375,1.6875,482.4375,1.6875Q479.34375,1.6875,476.0625,1.296875Q472.79688,0.921875,469.59375,0.171875Q466.40625,-0.5625,463.3125,-1.609375Q460.21875,-2.671875,457.46875,-3.9375Q458.45312,-6.1875,459.54688,-8.328125Q460.64062,-10.484375,461.82812,-12.59375Q464.15625,-11.609375,466.75,-10.71875Q469.35938,-9.84375,472.09375,-9.203125Q474.84375,-8.578125,477.51562,-8.1875Q480.1875,-7.8125,482.64062,-7.8125Q485.73438,-7.8125,488.65625,-8.515625Q491.57812,-9.21875,493.85938,-10.71875Q496.14062,-12.234375,497.51562,-14.625Q498.89062,-17.015625,498.89062,-20.390625Q498.89062,-23.5625,497.57812,-25.59375Q496.28125,-27.640625,494.09375,-29Q491.92188,-30.375,489,-31.25Q486.09375,-32.140625,482.85938,-32.90625Q477.9375,-34.109375,473.53125,-35.65625Q469.14062,-37.203125,465.875,-39.6875Q462.60938,-42.1875,460.67188,-45.875Q458.73438,-49.578125,458.73438,-55.0625Q458.73438,-60.1875,460.84375,-63.90625Q462.95312,-67.640625,466.5,-70.03125Q470.0625,-72.421875,474.73438,-73.578125Q479.40625,-74.75,484.54688,-74.75Z"
android:fillColor="#000000"/>
<path android:pathData="M590.28125,-1.265625L578.6094,1.34375Q577.28125,-2.890625,576.71875,-6.828125Q576.4375,-8.65625,576.0156,-14.28125L574.53125,-14.28125Q570.6719,-7.171875,564.96875,-2.953125Q558.71875,1.6875,551.75,1.6875Q541.28125,1.6875,535.7969,-3.375Q531.0781,-7.671875,529.25,-16.53125Q528.0625,-22.578125,528.0625,-34.53125L528.0625,-73.125L540.15625,-73.125L540.15625,-31.15625Q540.15625,-20.671875,542.1875,-16.171875Q545.21875,-9.5,554.3594,-9.5Q560.8281,-9.5,567.0156,-15.6875Q571.15625,-19.828125,575.875,-27.84375L575.875,-73.125L587.96875,-73.125L587.96875,-27.984375Q587.96875,-18.640625,588.4531,-11.75Q588.7344,-7.453125,590.28125,-1.265625Z"
android:fillColor="#000000"/>
<path android:pathData="M644.78125,0L644.78125,-95.984375L683.03125,-95.984375L683.03125,0L644.78125,0ZM649.5625,-4.78125L678.25,-4.78125L678.25,-91.203125L649.5625,-91.203125L649.5625,-4.78125Z"
android:fillColor="#000000"/>
</group>
</group>
</group>
</vector>

View File

@@ -74,77 +74,110 @@
android:paddingStart="32dp" android:paddingStart="32dp"
android:paddingEnd="32dp"> android:paddingEnd="32dp">
<LinearLayout <ani.dantotsu.others.Xpandable
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:orientation="vertical">
android:gravity="center"
android:orientation="horizontal">
<TextView <TextView
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="64dp"
android:layout_weight="1"
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:gravity="center_vertical"
android:text="@string/theme" android:text="@string/theme"
android:textColor="?attr/colorSecondary" /> android:textColor="?attr/colorSecondary"
app:drawableEndCompat="@drawable/ic_round_arrow_drop_down_24"
tools:ignore="TextContrastCheck" />
<androidx.cardview.widget.CardView <LinearLayout
android:layout_width="wrap_content" android:id="@+id/settingsThemeContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:cardBackgroundColor="@color/nav_bg_inv" android:orientation="horizontal">
app:cardCornerRadius="16dp"
app:cardElevation="0dp">
<ImageButton <LinearLayout
android:id="@+id/settingsUiLight" android:layout_width="match_parent"
android:layout_width="48dp" android:layout_height="match_parent"
android:layout_height="64dp" android:layout_marginTop="8dp"
android:alpha="0.33" android:gravity="center"
android:background="?android:attr/selectableItemBackground" android:orientation="horizontal">
android:src="@drawable/ic_round_brightness_high_24"
app:tint="@color/bg_opp"
tools:ignore="ContentDescription,SpeakableTextPresentCheck,ImageContrastCheck,DuplicateSpeakableTextCheck" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="wrap_content" <androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/nav_bg_inv"
app:cardCornerRadius="16dp"
app:cardElevation="0dp">
<ImageButton
android:id="@+id/settingsUiLight"
android:layout_width="48dp"
android:layout_height="64dp"
android:alpha="0.33"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_round_brightness_high_24"
app:tint="@color/bg_opp"
tools:ignore="ContentDescription,SpeakableTextPresentCheck,ImageContrastCheck,DuplicateSpeakableTextCheck" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/nav_bg_inv"
app:cardCornerRadius="16dp"
app:cardElevation="0dp">
<ImageButton
android:id="@+id/settingsUiDark"
android:layout_width="48dp"
android:layout_height="64dp"
android:alpha="0.33"
android:background="?android:attr/selectableItemBackground"
android:scaleX="-1"
android:src="@drawable/ic_round_brightness_4_24"
app:tint="@color/bg_opp"
tools:ignore="ContentDescription,SpeakableTextPresentCheck,ImageContrastCheck" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/nav_bg_inv"
app:cardCornerRadius="16dp"
app:cardElevation="0dp">
<ImageButton
android:id="@+id/settingsUiAuto"
android:layout_width="48dp"
android:layout_height="64dp"
android:alpha="0.33"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_round_brightness_auto_24"
app:tint="@color/bg_opp"
tools:ignore="ContentDescription,SpeakableTextPresentCheck,ImageContrastCheck" />
</androidx.cardview.widget.CardView>
</LinearLayout>
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/settingsUseMaterialYou"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:cardBackgroundColor="@color/nav_bg_inv" android:checked="false"
app:cardCornerRadius="16dp" android:drawableStart="@drawable/ic_round_new_releases_24"
app:cardElevation="0dp"> android:drawablePadding="16dp"
android:elegantTextHeight="true"
<ImageButton android:fontFamily="@font/poppins_bold"
android:id="@+id/settingsUiDark" android:minHeight="64dp"
android:layout_width="48dp" android:text="@string/use_material_you"
android:layout_height="64dp" android:textAlignment="viewStart"
android:alpha="0.33" android:textColor="@color/bg_opp"
android:background="?android:attr/selectableItemBackground" app:cornerRadius="0dp"
android:scaleX="-1" app:drawableTint="?attr/colorPrimary"
android:src="@drawable/ic_round_brightness_4_24" app:showText="false"
app:tint="@color/bg_opp" app:thumbTint="@color/button_switch_track" />
tools:ignore="ContentDescription,SpeakableTextPresentCheck,ImageContrastCheck" /> </ani.dantotsu.others.Xpandable>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/nav_bg_inv"
app:cardCornerRadius="16dp"
app:cardElevation="0dp">
<ImageButton
android:id="@+id/settingsUiAuto"
android:layout_width="48dp"
android:layout_height="64dp"
android:alpha="0.33"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_round_brightness_auto_24"
app:tint="@color/bg_opp"
tools:ignore="ContentDescription,SpeakableTextPresentCheck,ImageContrastCheck" />
</androidx.cardview.widget.CardView>
</LinearLayout>
<ani.dantotsu.others.Xpandable <ani.dantotsu.others.Xpandable
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -429,6 +462,44 @@
app:showText="false" app:showText="false"
app:thumbTint="@color/button_switch_track" /> app:thumbTint="@color/button_switch_track" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/skipExtensionIcons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/ic_round_new_releases_24"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:text="@string/skip_loading_extension_icons"
android:textAlignment="viewStart"
android:textColor="@color/bg_opp"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/userAgent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="user agent"
app:boxCornerRadiusBottomEnd="8dp"
app:boxCornerRadiusBottomStart="8dp"
app:boxCornerRadiusTopEnd="8dp"
app:boxCornerRadiusTopStart="8dp"
app:hintAnimationEnabled="true" />
</com.google.android.material.textfield.TextInputLayout>
</ani.dantotsu.others.Xpandable> </ani.dantotsu.others.Xpandable>
<ani.dantotsu.others.Xpandable <ani.dantotsu.others.Xpandable
@@ -698,8 +769,8 @@
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="64dp"
android:layout_marginTop="8dp"
android:layout_marginStart="-12dp" android:layout_marginStart="-12dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:insetTop="0dp" android:insetTop="0dp"
android:insetBottom="0dp" android:insetBottom="0dp"
@@ -1222,11 +1293,11 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:clipToPadding="false" android:clipToPadding="false"
android:gravity="center" android:gravity="center"
android:orientation="horizontal"> android:orientation="horizontal"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<ImageView <ImageView
android:id="@+id/settingBuyMeCoffee" android:id="@+id/settingBuyMeCoffee"

View File

@@ -28,6 +28,6 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:textStyle="bold" android:textStyle="bold"
android:text="Uninstall" android:text="Uninstall"
android:textColor="@color/fav" android:textColor="@color/pink_500"
android:textSize="14sp"/> android:textSize="14sp"/>
</LinearLayout> </LinearLayout>

View File

@@ -28,6 +28,6 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:textStyle="bold" android:textStyle="bold"
android:text="Install" android:text="Install"
android:textColor="@color/fav" android:textColor="@color/pink_500"
android:textSize="14sp"/> android:textSize="14sp"/>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@drawable/ic_banner_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -21,4 +21,5 @@
<color name="nav_status">#80FFFFFF</color> <color name="nav_status">#80FFFFFF</color>
<color name="fav">#ad5edd</color> <color name="fav">#ad5edd</color>
<color name="filler">#54FF8400</color> <color name="filler">#54FF8400</color>
<color name="warning">#FF0000</color>
</resources> </resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#FFFFFF</color>
</resources>

View File

@@ -624,5 +624,7 @@
<string name="view_manga">View Manga</string> <string name="view_manga">View Manga</string>
<string name="force_legacy_installer">Force Legacy Installer</string> <string name="force_legacy_installer">Force Legacy Installer</string>
<string name="extensions_settings">Extensions</string> <string name="extensions_settings">Extensions</string>
<string name="skip_loading_extension_icons">skip loading extension icons</string>
<string name="use_material_you">Use Material You</string>
</resources> </resources>

10
beta.md
View File

@@ -1,10 +0,0 @@
# 1.2.4
Hey there, the alpha app has now switched to getting immediate updates. (gets updated when the code gets updated)
This means alpha can now also have ground breaking bugs with it. (Previously, I avoided this so alpha were stable enough to use)
If you wish still wish to stay on the alpha Version, [click here](https://github.com/rebelonion/Dantotsu/releases)
If not
## You will now be downloading the stable version of the app.

12
stable.md Normal file
View File

@@ -0,0 +1,12 @@
# 0.1.2
- **Bugfixes:**
- Fixes for manga and anime extensions
- Auto search is now EVEN MORE likely to find correct series
- Fixed a crash for large manga chapters
- Fixed most manga titles
- Fixed manga Anilist logging
- probably other stuff I forgor
- **New Features:**
- none