commit
7f92ac686d
201 changed files with 8057 additions and 2750 deletions
|
@ -21,18 +21,18 @@ android {
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode ((System.currentTimeMillis() / 60000).toInteger())
|
versionCode ((System.currentTimeMillis() / 60000).toInteger())
|
||||||
versionName "1.0.0-beta03i"
|
versionName "1.0.0-beta03i-2"
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".beta"
|
applicationIdSuffix ".beta"
|
||||||
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_beta"]
|
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_beta", icon_placeholder_round: "@mipmap/ic_launcher_beta_round"]
|
||||||
debuggable true
|
debuggable true
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher"]
|
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher", icon_placeholder_round: "@mipmap/ic_launcher_round"]
|
||||||
debuggable false
|
debuggable false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,10 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'com.google.code.gson:gson:2.8.9'
|
implementation 'com.google.code.gson:gson:2.8.9'
|
||||||
implementation 'com.github.Blatzar:NiceHttp:0.4.3'
|
implementation 'com.github.Blatzar:NiceHttp:0.4.4'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0'
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
|
implementation 'androidx.webkit:webkit:1.9.0'
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
ext.glide_version = '4.16.0'
|
ext.glide_version = '4.16.0'
|
||||||
|
@ -99,6 +100,7 @@ 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'
|
||||||
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
||||||
|
implementation "com.github.skydoves:colorpickerview:2.3.0"
|
||||||
|
|
||||||
// string matching
|
// string matching
|
||||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="${icon_placeholder_round}"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Dantotsu"
|
android:theme="@style/Theme.Dantotsu"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
|
@ -276,6 +276,14 @@
|
||||||
<service android:name=".download.manga.MangaDownloaderService"
|
<service android:name=".download.manga.MangaDownloaderService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="dataSync" />
|
android:foregroundServiceType="dataSync" />
|
||||||
|
|
||||||
|
<service android:name=".download.novel.NovelDownloaderService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="dataSync" />
|
||||||
|
|
||||||
|
<service android:name=".connections.discord.DiscordService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="dataSync" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -8,14 +8,15 @@ import androidx.multidex.MultiDex
|
||||||
import androidx.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import ani.dantotsu.aniyomi.anime.custom.AppModule
|
import ani.dantotsu.aniyomi.anime.custom.AppModule
|
||||||
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
|
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
|
||||||
import tachiyomi.core.util.system.logcat
|
|
||||||
import ani.dantotsu.others.DisabledReports
|
import ani.dantotsu.others.DisabledReports
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
import ani.dantotsu.parsers.NovelSources
|
||||||
|
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -25,14 +26,16 @@ import kotlinx.coroutines.launch
|
||||||
import logcat.AndroidLogcatLogger
|
import logcat.AndroidLogcatLogger
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import logcat.LogcatLogger
|
import logcat.LogcatLogger
|
||||||
|
import tachiyomi.core.util.system.logcat
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
class App : MultiDexApplication() {
|
class App : MultiDexApplication() {
|
||||||
private lateinit var animeExtensionManager: AnimeExtensionManager
|
private lateinit var animeExtensionManager: AnimeExtensionManager
|
||||||
private lateinit var mangaExtensionManager: MangaExtensionManager
|
private lateinit var mangaExtensionManager: MangaExtensionManager
|
||||||
|
private lateinit var novelExtensionManager: NovelExtensionManager
|
||||||
override fun attachBaseContext(base: Context?) {
|
override fun attachBaseContext(base: Context?) {
|
||||||
super.attachBaseContext(base)
|
super.attachBaseContext(base)
|
||||||
MultiDex.install(this)
|
MultiDex.install(this)
|
||||||
|
@ -50,15 +53,17 @@ class App : MultiDexApplication() {
|
||||||
val useMaterialYou = sharedPreferences.getBoolean("use_material_you", false)
|
val useMaterialYou = sharedPreferences.getBoolean("use_material_you", false)
|
||||||
if (useMaterialYou) {
|
if (useMaterialYou) {
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||||
|
//TODO: HarmonizedColors
|
||||||
}
|
}
|
||||||
registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
|
registerActivityLifecycleCallbacks(mFTActivityLifecycleCallbacks)
|
||||||
|
|
||||||
Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)
|
Firebase.crashlytics.setCrashlyticsCollectionEnabled(!DisabledReports)
|
||||||
initializeNetwork(baseContext)
|
|
||||||
|
|
||||||
Injekt.importModule(AppModule(this))
|
Injekt.importModule(AppModule(this))
|
||||||
Injekt.importModule(PreferenceModule(this))
|
Injekt.importModule(PreferenceModule(this))
|
||||||
|
|
||||||
|
initializeNetwork(baseContext)
|
||||||
|
|
||||||
setupNotificationChannels()
|
setupNotificationChannels()
|
||||||
if (!LogcatLogger.isInstalled) {
|
if (!LogcatLogger.isInstalled) {
|
||||||
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
|
||||||
|
@ -66,6 +71,7 @@ class App : MultiDexApplication() {
|
||||||
|
|
||||||
animeExtensionManager = Injekt.get()
|
animeExtensionManager = Injekt.get()
|
||||||
mangaExtensionManager = Injekt.get()
|
mangaExtensionManager = Injekt.get()
|
||||||
|
novelExtensionManager = Injekt.get()
|
||||||
|
|
||||||
val animeScope = CoroutineScope(Dispatchers.Default)
|
val animeScope = CoroutineScope(Dispatchers.Default)
|
||||||
animeScope.launch {
|
animeScope.launch {
|
||||||
|
@ -79,9 +85,16 @@ class App : MultiDexApplication() {
|
||||||
logger("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
logger("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
||||||
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
|
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
|
val novelScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
novelScope.launch {
|
||||||
|
novelExtensionManager.findAvailableExtensions()
|
||||||
|
logger("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
|
||||||
|
NovelSources.init(novelExtensionManager.installedExtensionsFlow)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun setupNotificationChannels() {
|
private fun setupNotificationChannels() {
|
||||||
try {
|
try {
|
||||||
Notifications.createChannels(this)
|
Notifications.createChannels(this)
|
||||||
|
|
|
@ -132,7 +132,8 @@ fun <T> loadData(fileName: String, context: Context? = null, toast: Boolean = tr
|
||||||
fun initActivity(a: Activity) {
|
fun initActivity(a: Activity) {
|
||||||
val window = a.window
|
val window = a.window
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
val uiSettings = loadData<UserInterfaceSettings>("ui_settings", toast = false) ?: UserInterfaceSettings().apply {
|
val uiSettings = loadData<UserInterfaceSettings>("ui_settings", toast = false)
|
||||||
|
?: UserInterfaceSettings().apply {
|
||||||
saveData("ui_settings", this)
|
saveData("ui_settings", this)
|
||||||
}
|
}
|
||||||
uiSettings.darkMode.apply {
|
uiSettings.darkMode.apply {
|
||||||
|
@ -146,7 +147,8 @@ fun initActivity(a: Activity) {
|
||||||
}
|
}
|
||||||
if (uiSettings.immersiveMode) {
|
if (uiSettings.immersiveMode) {
|
||||||
if (navBarHeight == 0) {
|
if (navBarHeight == 0) {
|
||||||
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))?.apply {
|
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
|
||||||
|
?.apply {
|
||||||
navBarHeight = this.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
|
navBarHeight = this.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +162,8 @@ fun initActivity(a: Activity) {
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
if (statusBarHeight == 0) {
|
if (statusBarHeight == 0) {
|
||||||
val windowInsets = ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
|
val windowInsets =
|
||||||
|
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
|
||||||
if (windowInsets != null) {
|
if (windowInsets != null) {
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
statusBarHeight = insets.top
|
statusBarHeight = insets.top
|
||||||
|
@ -205,7 +208,8 @@ open class BottomSheetDialogFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isOnline(context: Context): Boolean {
|
fun isOnline(context: Context): Boolean {
|
||||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectivityManager =
|
||||||
|
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
return tryWith {
|
return tryWith {
|
||||||
val cap = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
val cap = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||||
return@tryWith if (cap != null) {
|
return@tryWith if (cap != null) {
|
||||||
|
@ -239,7 +243,8 @@ fun startMainActivity(activity: Activity, bundle: Bundle? = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DatePickerFragment(activity: Activity, var date: FuzzyDate = FuzzyDate().getToday()) : DialogFragment(),
|
class DatePickerFragment(activity: Activity, var date: FuzzyDate = FuzzyDate().getToday()) :
|
||||||
|
DialogFragment(),
|
||||||
DatePickerDialog.OnDateSetListener {
|
DatePickerDialog.OnDateSetListener {
|
||||||
var dialog: DatePickerDialog
|
var dialog: DatePickerDialog
|
||||||
|
|
||||||
|
@ -264,9 +269,20 @@ class DatePickerFragment(activity: Activity, var date: FuzzyDate = FuzzyDate().g
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InputFilterMinMax(private val min: Double, private val max: Double, private val status: AutoCompleteTextView? = null) :
|
class InputFilterMinMax(
|
||||||
|
private val min: Double,
|
||||||
|
private val max: Double,
|
||||||
|
private val status: AutoCompleteTextView? = null
|
||||||
|
) :
|
||||||
InputFilter {
|
InputFilter {
|
||||||
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
|
override fun filter(
|
||||||
|
source: CharSequence,
|
||||||
|
start: Int,
|
||||||
|
end: Int,
|
||||||
|
dest: Spanned,
|
||||||
|
dstart: Int,
|
||||||
|
dend: Int
|
||||||
|
): CharSequence? {
|
||||||
try {
|
try {
|
||||||
val input = (dest.toString() + source.toString()).toDouble()
|
val input = (dest.toString() + source.toString()).toDouble()
|
||||||
if (isInRange(min, max, input)) return null
|
if (isInRange(min, max, input)) return null
|
||||||
|
@ -289,11 +305,20 @@ class InputFilterMinMax(private val min: Double, private val max: Double, privat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ZoomOutPageTransformer(private val uiSettings: UserInterfaceSettings) : ViewPager2.PageTransformer {
|
class ZoomOutPageTransformer(private val uiSettings: UserInterfaceSettings) :
|
||||||
|
ViewPager2.PageTransformer {
|
||||||
override fun transformPage(view: View, position: Float) {
|
override fun transformPage(view: View, position: Float) {
|
||||||
if (position == 0.0f && uiSettings.layoutAnimations) {
|
if (position == 0.0f && uiSettings.layoutAnimations) {
|
||||||
setAnimation(view.context, view, uiSettings, 300, floatArrayOf(1.3f, 1f, 1.3f, 1f), 0.5f to 0f)
|
setAnimation(
|
||||||
ObjectAnimator.ofFloat(view, "alpha", 0f, 1.0f).setDuration((200 * uiSettings.animationSpeed).toLong()).start()
|
view.context,
|
||||||
|
view,
|
||||||
|
uiSettings,
|
||||||
|
300,
|
||||||
|
floatArrayOf(1.3f, 1f, 1.3f, 1f),
|
||||||
|
0.5f to 0f
|
||||||
|
)
|
||||||
|
ObjectAnimator.ofFloat(view, "alpha", 0f, 1.0f)
|
||||||
|
.setDuration((200 * uiSettings.animationSpeed).toLong()).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,7 +353,11 @@ class FadingEdgeRecyclerView : RecyclerView {
|
||||||
|
|
||||||
constructor(context: Context) : super(context)
|
constructor(context: Context) : super(context)
|
||||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||||
|
context,
|
||||||
|
attrs,
|
||||||
|
defStyleAttr
|
||||||
|
)
|
||||||
|
|
||||||
override fun isPaddingOffsetRequired(): Boolean {
|
override fun isPaddingOffsetRequired(): Boolean {
|
||||||
return !clipToPadding
|
return !clipToPadding
|
||||||
|
@ -423,8 +452,7 @@ fun ImageView.loadImage(url: String?, size: Int = 0) {
|
||||||
val localFile = File(url)
|
val localFile = File(url)
|
||||||
if (localFile.exists()) {
|
if (localFile.exists()) {
|
||||||
loadLocalImage(localFile, size)
|
loadLocalImage(localFile, size)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
loadImage(FileUrl(url), size)
|
loadImage(FileUrl(url), size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -434,7 +462,8 @@ fun ImageView.loadImage(file: FileUrl?, size: Int = 0) {
|
||||||
if (file?.url?.isNotEmpty() == true) {
|
if (file?.url?.isNotEmpty() == true) {
|
||||||
tryWith {
|
tryWith {
|
||||||
val glideUrl = GlideUrl(file.url) { file.headers }
|
val glideUrl = GlideUrl(file.url) { file.headers }
|
||||||
Glide.with(this.context).load(glideUrl).transition(withCrossFade()).override(size).into(this)
|
Glide.with(this.context).load(glideUrl).transition(withCrossFade()).override(size)
|
||||||
|
.into(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -442,7 +471,8 @@ fun ImageView.loadImage(file: FileUrl?, size: Int = 0) {
|
||||||
fun ImageView.loadLocalImage(file: File?, size: Int = 0) {
|
fun ImageView.loadLocalImage(file: File?, size: Int = 0) {
|
||||||
if (file?.exists() == true) {
|
if (file?.exists() == true) {
|
||||||
tryWith {
|
tryWith {
|
||||||
Glide.with(this.context).load(file).transition(withCrossFade()).override(size).into(this)
|
Glide.with(this.context).load(file).transition(withCrossFade()).override(size)
|
||||||
|
.into(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,7 +530,12 @@ abstract class GesturesListener : GestureDetector.SimpleOnGestureListener() {
|
||||||
return super.onDoubleTap(e)
|
return super.onDoubleTap(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
|
override fun onScroll(
|
||||||
|
e1: MotionEvent?,
|
||||||
|
e2: MotionEvent,
|
||||||
|
distanceX: Float,
|
||||||
|
distanceY: Float
|
||||||
|
): Boolean {
|
||||||
onScrollYClick(distanceY)
|
onScrollYClick(distanceY)
|
||||||
onScrollXClick(distanceX)
|
onScrollXClick(distanceX)
|
||||||
return super.onScroll(e1, e2, distanceX, distanceY)
|
return super.onScroll(e1, e2, distanceX, distanceY)
|
||||||
|
@ -642,9 +677,15 @@ fun countDown(media: Media, view: ViewGroup) {
|
||||||
val v = ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
|
val v = ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
|
||||||
view.addView(v.root, 0)
|
view.addView(v.root, 0)
|
||||||
v.mediaCountdownText.text =
|
v.mediaCountdownText.text =
|
||||||
currActivity()?.getString(R.string.episode_release_countdown, media.anime.nextAiringEpisode!! + 1)
|
currActivity()?.getString(
|
||||||
|
R.string.episode_release_countdown,
|
||||||
|
media.anime.nextAiringEpisode!! + 1
|
||||||
|
)
|
||||||
|
|
||||||
object : CountDownTimer((media.anime.nextAiringEpisodeTime!! + 10000) * 1000 - System.currentTimeMillis(), 1000) {
|
object : CountDownTimer(
|
||||||
|
(media.anime.nextAiringEpisodeTime!! + 10000) * 1000 - System.currentTimeMillis(),
|
||||||
|
1000
|
||||||
|
) {
|
||||||
override fun onTick(millisUntilFinished: Long) {
|
override fun onTick(millisUntilFinished: Long) {
|
||||||
val a = millisUntilFinished / 1000
|
val a = millisUntilFinished / 1000
|
||||||
v.mediaCountdown.text = currActivity()?.getString(
|
v.mediaCountdown.text = currActivity()?.getString(
|
||||||
|
@ -735,7 +776,8 @@ fun toast(string: String?) {
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
logger(string)
|
logger(string)
|
||||||
MainScope().launch {
|
MainScope().launch {
|
||||||
Toast.makeText(currActivity()?.application ?: return@launch, string, Toast.LENGTH_SHORT).show()
|
Toast.makeText(currActivity()?.application ?: return@launch, string, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -744,7 +786,11 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
(activity ?: currActivity())?.apply {
|
(activity ?: currActivity())?.apply {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
val snackBar = Snackbar.make(window.decorView.findViewById(android.R.id.content), s, Snackbar.LENGTH_SHORT)
|
val snackBar = Snackbar.make(
|
||||||
|
window.decorView.findViewById(android.R.id.content),
|
||||||
|
s,
|
||||||
|
Snackbar.LENGTH_SHORT
|
||||||
|
)
|
||||||
snackBar.view.apply {
|
snackBar.view.apply {
|
||||||
updateLayoutParams<FrameLayout.LayoutParams> {
|
updateLayoutParams<FrameLayout.LayoutParams> {
|
||||||
gravity = (Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM)
|
gravity = (Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM)
|
||||||
|
@ -769,7 +815,8 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class NoPaddingArrayAdapter<T>(context: Context, layoutId: Int, items: List<T>) : ArrayAdapter<T>(context, layoutId, items) {
|
open class NoPaddingArrayAdapter<T>(context: Context, layoutId: Int, items: List<T>) :
|
||||||
|
ArrayAdapter<T>(context, layoutId, items) {
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
val view = super.getView(position, convertView, parent)
|
val view = super.getView(position, convertView, parent)
|
||||||
view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom)
|
view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom)
|
||||||
|
@ -790,12 +837,17 @@ class SpinnerNoSwipe : androidx.appcompat.widget.AppCompatSpinner {
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||||
|
context,
|
||||||
|
attrs,
|
||||||
|
defStyleAttr
|
||||||
|
) {
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setup() {
|
private fun setup() {
|
||||||
mGestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
|
mGestureDetector =
|
||||||
|
GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
|
||||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||||
return performClick()
|
return performClick()
|
||||||
}
|
}
|
||||||
|
@ -843,7 +895,11 @@ fun getCurrentBrightnessValue(context: Context): Float {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCur(): Float {
|
fun getCur(): Float {
|
||||||
return Settings.System.getInt(context.contentResolver, Settings.System.SCREEN_BRIGHTNESS, 127).toFloat()
|
return Settings.System.getInt(
|
||||||
|
context.contentResolver,
|
||||||
|
Settings.System.SCREEN_BRIGHTNESS,
|
||||||
|
127
|
||||||
|
).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
return brightnessConverter(getCur() / getMax(), true)
|
return brightnessConverter(getCur() / getMax(), true)
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
package ani.dantotsu
|
package ani.dantotsu
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.graphics.drawable.ColorDrawable
|
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -17,18 +11,14 @@ import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AnticipateInterpolator
|
import android.view.animation.AnticipateInterpolator
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.doOnAttach
|
import androidx.core.view.doOnAttach
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
@ -37,13 +27,10 @@ import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
|
import ani.dantotsu.connections.anilist.AnilistHomeViewModel
|
||||||
import ani.dantotsu.databinding.ActivityMainBinding
|
import ani.dantotsu.databinding.ActivityMainBinding
|
||||||
import ani.dantotsu.databinding.ItemNavbarBinding
|
|
||||||
import ani.dantotsu.databinding.SplashScreenBinding
|
import ani.dantotsu.databinding.SplashScreenBinding
|
||||||
import ani.dantotsu.download.manga.OfflineMangaFragment
|
|
||||||
import ani.dantotsu.home.AnimeFragment
|
import ani.dantotsu.home.AnimeFragment
|
||||||
import ani.dantotsu.home.HomeFragment
|
import ani.dantotsu.home.HomeFragment
|
||||||
import ani.dantotsu.home.LoginFragment
|
import ani.dantotsu.home.LoginFragment
|
||||||
|
@ -51,27 +38,17 @@ import ani.dantotsu.home.MangaFragment
|
||||||
import ani.dantotsu.home.NoInternet
|
import ani.dantotsu.home.NoInternet
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.others.CustomBottomDialog
|
import ani.dantotsu.others.CustomBottomDialog
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.parsers.MangaSources
|
|
||||||
import ani.dantotsu.settings.SettingsActivity
|
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
|
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,6 +59,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private var uiSettings = UserInterfaceSettings()
|
private var uiSettings = UserInterfaceSettings()
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
LangSet.setLocale(this)
|
LangSet.setLocale(this)
|
||||||
|
@ -266,10 +244,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//ViewPager
|
//ViewPager
|
||||||
private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
|
||||||
|
|
|
@ -8,58 +8,51 @@ import ani.dantotsu.others.webview.WebViewBottomDialog
|
||||||
import com.lagradost.nicehttp.Requests
|
import com.lagradost.nicehttp.Requests
|
||||||
import com.lagradost.nicehttp.ResponseParser
|
import com.lagradost.nicehttp.ResponseParser
|
||||||
import com.lagradost.nicehttp.addGenericDns
|
import com.lagradost.nicehttp.addGenericDns
|
||||||
import kotlinx.coroutines.*
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.InternalSerializationApi
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
import okhttp3.Cache
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import java.io.File
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KFunction
|
import kotlin.reflect.KFunction
|
||||||
|
|
||||||
val defaultHeaders = mapOf(
|
lateinit var defaultHeaders: Map<String, String>
|
||||||
"User-Agent" to
|
|
||||||
"Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36"
|
|
||||||
.format(Build.VERSION.RELEASE, Build.MODEL)
|
|
||||||
)
|
|
||||||
lateinit var cache: Cache
|
|
||||||
|
|
||||||
lateinit var okHttpClient: OkHttpClient
|
lateinit var okHttpClient: OkHttpClient
|
||||||
lateinit var client: Requests
|
lateinit var client: Requests
|
||||||
|
|
||||||
fun initializeNetwork(context: Context) {
|
fun initializeNetwork(context: Context) {
|
||||||
val dns = loadData<Int>("settings_dns")
|
|
||||||
cache = Cache(
|
val networkHelper = Injekt.get<NetworkHelper>()
|
||||||
File(context.cacheDir, "http_cache"),
|
|
||||||
5 * 1024L * 1024L // 5 MiB
|
defaultHeaders = mapOf(
|
||||||
|
"User-Agent" to
|
||||||
|
Injekt.get<NetworkHelper>().defaultUserAgentProvider()
|
||||||
|
.format(Build.VERSION.RELEASE, Build.MODEL)
|
||||||
)
|
)
|
||||||
okHttpClient = OkHttpClient.Builder()
|
|
||||||
.followRedirects(true)
|
okHttpClient = networkHelper.client
|
||||||
.followSslRedirects(true)
|
|
||||||
.cache(cache)
|
|
||||||
.apply {
|
|
||||||
when (dns) {
|
|
||||||
1 -> addGoogleDns()
|
|
||||||
2 -> addCloudFlareDns()
|
|
||||||
3 -> addAdGuardDns()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
client = Requests(
|
client = Requests(
|
||||||
okHttpClient,
|
networkHelper.client,
|
||||||
defaultHeaders,
|
defaultHeaders,
|
||||||
defaultCacheTime = 6,
|
defaultCacheTime = 6,
|
||||||
defaultCacheTimeUnit = TimeUnit.HOURS,
|
defaultCacheTimeUnit = TimeUnit.HOURS,
|
||||||
responseParser = Mapper
|
responseParser = Mapper
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Mapper : ResponseParser {
|
object Mapper : ResponseParser {
|
||||||
|
@ -122,7 +115,11 @@ fun <T> tryWith(post: Boolean = false, snackbar: Boolean = true, call: () -> T):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> tryWithSuspend(post: Boolean = false, snackbar: Boolean = true, call: suspend () -> T): T? {
|
suspend fun <T> tryWithSuspend(
|
||||||
|
post: Boolean = false,
|
||||||
|
snackbar: Boolean = true,
|
||||||
|
call: suspend () -> T
|
||||||
|
): T? {
|
||||||
return try {
|
return try {
|
||||||
call.invoke()
|
call.invoke()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
@ -209,7 +206,8 @@ suspend fun webViewInterface(webViewDialog: WebViewBottomDialog): Map<String, St
|
||||||
map = it
|
map = it
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
val fragmentManager = (currContext() as FragmentActivity?)?.supportFragmentManager ?: return null
|
val fragmentManager =
|
||||||
|
(currContext() as FragmentActivity?)?.supportFragmentManager ?: return null
|
||||||
webViewDialog.show(fragmentManager, "web-view")
|
webViewDialog.show(fragmentManager, "web-view")
|
||||||
delay(0)
|
delay(0)
|
||||||
latch.await(2, TimeUnit.MINUTES)
|
latch.await(2, TimeUnit.MINUTES)
|
||||||
|
|
|
@ -4,18 +4,20 @@ package ani.dantotsu.aniyomi.anime.custom
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.media.manga.MangaCache
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
import ani.dantotsu.parsers.novel.NovelExtensionManager
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
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.anime.AnimeExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager
|
import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager
|
||||||
import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager
|
import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||||
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
import tachiyomi.domain.source.manga.service.MangaSourceManager
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
|
@ -23,7 +25,6 @@ import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
import uy.kohesive.injekt.api.addSingleton
|
import uy.kohesive.injekt.api.addSingleton
|
||||||
import uy.kohesive.injekt.api.addSingletonFactory
|
import uy.kohesive.injekt.api.addSingletonFactory
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import ani.dantotsu.download.DownloadsManager
|
|
||||||
|
|
||||||
class AppModule(val app: Application) : InjektModule {
|
class AppModule(val app: Application) : InjektModule {
|
||||||
override fun InjektRegistrar.registerInjectables() {
|
override fun InjektRegistrar.registerInjectables() {
|
||||||
|
@ -35,6 +36,7 @@ class AppModule(val app: Application) : InjektModule {
|
||||||
|
|
||||||
addSingletonFactory { AnimeExtensionManager(app) }
|
addSingletonFactory { AnimeExtensionManager(app) }
|
||||||
addSingletonFactory { MangaExtensionManager(app) }
|
addSingletonFactory { MangaExtensionManager(app) }
|
||||||
|
addSingletonFactory { NovelExtensionManager(app) }
|
||||||
|
|
||||||
addSingletonFactory<AnimeSourceManager> { AndroidAnimeSourceManager(app, get()) }
|
addSingletonFactory<AnimeSourceManager> { AndroidAnimeSourceManager(app, get()) }
|
||||||
addSingletonFactory<MangaSourceManager> { AndroidMangaSourceManager(app, get()) }
|
addSingletonFactory<MangaSourceManager> { AndroidMangaSourceManager(app, get()) }
|
||||||
|
|
|
@ -10,7 +10,7 @@ import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.Calendar
|
||||||
|
|
||||||
object Anilist {
|
object Anilist {
|
||||||
val query: AnilistQueries = AnilistQueries()
|
val query: AnilistQueries = AnilistQueries()
|
||||||
|
@ -29,7 +29,12 @@ object Anilist {
|
||||||
var tags: Map<Boolean, List<String>>? = null
|
var tags: Map<Boolean, List<String>>? = null
|
||||||
|
|
||||||
val sortBy = listOf(
|
val sortBy = listOf(
|
||||||
"SCORE_DESC","POPULARITY_DESC","TRENDING_DESC","TITLE_ENGLISH","TITLE_ENGLISH_DESC","SCORE"
|
"SCORE_DESC",
|
||||||
|
"POPULARITY_DESC",
|
||||||
|
"TRENDING_DESC",
|
||||||
|
"TITLE_ENGLISH",
|
||||||
|
"TITLE_ENGLISH_DESC",
|
||||||
|
"SCORE"
|
||||||
)
|
)
|
||||||
|
|
||||||
val seasons = listOf(
|
val seasons = listOf(
|
||||||
|
@ -132,7 +137,12 @@ object Anilist {
|
||||||
if (token != null || force) {
|
if (token != null || force) {
|
||||||
if (token != null && useToken) headers["Authorization"] = "Bearer $token"
|
if (token != null && useToken) headers["Authorization"] = "Bearer $token"
|
||||||
|
|
||||||
val json = client.post("https://graphql.anilist.co/", headers, data = data, cacheTime = cache ?: 10)
|
val json = client.post(
|
||||||
|
"https://graphql.anilist.co/",
|
||||||
|
headers,
|
||||||
|
data = data,
|
||||||
|
cacheTime = cache ?: 10
|
||||||
|
)
|
||||||
if (!json.text.startsWith("{")) throw Exception(currContext()?.getString(R.string.anilist_down))
|
if (!json.text.startsWith("{")) throw Exception(currContext()?.getString(R.string.anilist_down))
|
||||||
if (show) println("Response : ${json.text}")
|
if (show) println("Response : ${json.text}")
|
||||||
json.parsed()
|
json.parsed()
|
||||||
|
|
|
@ -2,13 +2,13 @@ package ani.dantotsu.connections.anilist
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.checkGenreTime
|
||||||
|
import ani.dantotsu.checkId
|
||||||
import ani.dantotsu.connections.anilist.Anilist.authorRoles
|
import ani.dantotsu.connections.anilist.Anilist.authorRoles
|
||||||
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
import ani.dantotsu.connections.anilist.api.Page
|
import ani.dantotsu.connections.anilist.api.Page
|
||||||
import ani.dantotsu.connections.anilist.api.Query
|
import ani.dantotsu.connections.anilist.api.Query
|
||||||
import ani.dantotsu.checkGenreTime
|
|
||||||
import ani.dantotsu.checkId
|
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.logError
|
import ani.dantotsu.logError
|
||||||
|
@ -114,8 +114,12 @@ class AnilistQueries {
|
||||||
image = i.node?.image?.medium,
|
image = i.node?.image?.medium,
|
||||||
banner = media.banner ?: media.cover,
|
banner = media.banner ?: media.cover,
|
||||||
role = when (i.role.toString()) {
|
role = when (i.role.toString()) {
|
||||||
"MAIN" -> currContext()?.getString(R.string.main_role) ?: "MAIN"
|
"MAIN" -> currContext()?.getString(R.string.main_role)
|
||||||
"SUPPORTING" -> currContext()?.getString(R.string.supporting_role) ?: "SUPPORTING"
|
?: "MAIN"
|
||||||
|
|
||||||
|
"SUPPORTING" -> currContext()?.getString(R.string.supporting_role)
|
||||||
|
?: "SUPPORTING"
|
||||||
|
|
||||||
else -> i.role.toString()
|
else -> i.role.toString()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -129,11 +133,16 @@ class AnilistQueries {
|
||||||
val m = Media(mediaEdge)
|
val m = Media(mediaEdge)
|
||||||
media.relations?.add(m)
|
media.relations?.add(m)
|
||||||
if (m.relation == "SEQUEL") {
|
if (m.relation == "SEQUEL") {
|
||||||
media.sequel = if ((media.sequel?.popularity ?: 0) < (m.popularity ?: 0)) m else media.sequel
|
media.sequel =
|
||||||
|
if ((media.sequel?.popularity ?: 0) < (m.popularity
|
||||||
|
?: 0)
|
||||||
|
) m else media.sequel
|
||||||
|
|
||||||
} else if (m.relation == "PREQUEL") {
|
} else if (m.relation == "PREQUEL") {
|
||||||
media.prequel =
|
media.prequel =
|
||||||
if ((media.prequel?.popularity ?: 0) < (m.popularity ?: 0)) m else media.prequel
|
if ((media.prequel?.popularity ?: 0) < (m.popularity
|
||||||
|
?: 0)
|
||||||
|
) m else media.prequel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
media.relations?.sortByDescending { it.popularity }
|
media.relations?.sortByDescending { it.popularity }
|
||||||
|
@ -199,17 +208,19 @@ class AnilistQueries {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
media.anime.nextAiringEpisodeTime = fetchedMedia.nextAiringEpisode?.airingAt?.toLong()
|
media.anime.nextAiringEpisodeTime =
|
||||||
|
fetchedMedia.nextAiringEpisode?.airingAt?.toLong()
|
||||||
|
|
||||||
fetchedMedia.externalLinks?.forEach { i ->
|
fetchedMedia.externalLinks?.forEach { i ->
|
||||||
when (i.site.lowercase()) {
|
when (i.site.lowercase()) {
|
||||||
"youtube" -> media.anime.youtube = i.url
|
"youtube" -> media.anime.youtube = i.url
|
||||||
"crunchyroll" -> media.crunchySlug = i.url?.split("/")?.getOrNull(3)
|
"crunchyroll" -> media.crunchySlug =
|
||||||
|
i.url?.split("/")?.getOrNull(3)
|
||||||
|
|
||||||
"vrv" -> media.vrvId = i.url?.split("/")?.getOrNull(4)
|
"vrv" -> media.vrvId = i.url?.split("/")?.getOrNull(4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (media.manga != null) {
|
||||||
else if (media.manga != null) {
|
|
||||||
fetchedMedia.staff?.edges?.find { authorRoles.contains(it.role?.trim()) }?.node?.let {
|
fetchedMedia.staff?.edges?.find { authorRoles.contains(it.role?.trim()) }?.node?.let {
|
||||||
media.manga.author = Author(
|
media.manga.author = Author(
|
||||||
it.id.toString(),
|
it.id.toString(),
|
||||||
|
@ -361,7 +372,11 @@ class AnilistQueries {
|
||||||
return default
|
return default
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getMediaLists(anime: Boolean, userId: Int, sortOrder: String? = null): MutableMap<String, ArrayList<Media>> {
|
suspend fun getMediaLists(
|
||||||
|
anime: Boolean,
|
||||||
|
userId: Int,
|
||||||
|
sortOrder: String? = null
|
||||||
|
): MutableMap<String, ArrayList<Media>> {
|
||||||
val response =
|
val response =
|
||||||
executeQuery<Query.MediaListCollection>("""{ MediaListCollection(userId: $userId, type: ${if (anime) "ANIME" else "MANGA"}) { lists { name isCustomList entries { status progress private score(format:POINT_100) updatedAt media { id idMal isAdult type status chapters episodes nextAiringEpisode {episode} bannerImage meanScore isFavourite format coverImage{large} startDate{year month day} title {english romaji userPreferred } } } } user { id mediaListOptions { rowOrder animeList { sectionOrder } mangaList { sectionOrder } } } } }""")
|
executeQuery<Query.MediaListCollection>("""{ MediaListCollection(userId: $userId, type: ${if (anime) "ANIME" else "MANGA"}) { lists { name isCustomList entries { status progress private score(format:POINT_100) updatedAt media { id idMal isAdult type status chapters episodes nextAiringEpisode {episode} bannerImage meanScore isFavourite format coverImage{large} startDate{year month day} title {english romaji userPreferred } } } } user { id mediaListOptions { rowOrder animeList { sectionOrder } mangaList { sectionOrder } } } } }""")
|
||||||
val sorted = mutableMapOf<String, ArrayList<Media>>()
|
val sorted = mutableMapOf<String, ArrayList<Media>>()
|
||||||
|
@ -399,7 +414,14 @@ class AnilistQueries {
|
||||||
val sort = sortOrder ?: options?.rowOrder
|
val sort = sortOrder ?: options?.rowOrder
|
||||||
for (i in sorted.keys) {
|
for (i in sorted.keys) {
|
||||||
when (sort) {
|
when (sort) {
|
||||||
"score" -> sorted[i]?.sortWith { b, a -> compareValuesBy(a, b, { it.userScore }, { it.meanScore }) }
|
"score" -> sorted[i]?.sortWith { b, a ->
|
||||||
|
compareValuesBy(
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
{ it.userScore },
|
||||||
|
{ it.meanScore })
|
||||||
|
}
|
||||||
|
|
||||||
"title" -> sorted[i]?.sortWith(compareBy { it.userPreferredName })
|
"title" -> sorted[i]?.sortWith(compareBy { it.userPreferredName })
|
||||||
"updatedAt" -> sorted[i]?.sortWith(compareByDescending { it.userUpdatedAt })
|
"updatedAt" -> sorted[i]?.sortWith(compareByDescending { it.userUpdatedAt })
|
||||||
"release" -> sorted[i]?.sortWith(compareByDescending { it.startDate })
|
"release" -> sorted[i]?.sortWith(compareByDescending { it.startDate })
|
||||||
|
@ -564,13 +586,31 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult:
|
||||||
${if (genres?.isNotEmpty() == true) ""","genres":[${genres.joinToString { "\"$it\"" }}]""" else ""}
|
${if (genres?.isNotEmpty() == true) ""","genres":[${genres.joinToString { "\"$it\"" }}]""" else ""}
|
||||||
${
|
${
|
||||||
if (excludedGenres?.isNotEmpty() == true)
|
if (excludedGenres?.isNotEmpty() == true)
|
||||||
""","excludedGenres":[${excludedGenres.joinToString { "\"${it.replace("Not ", "")}\"" }}]"""
|
""","excludedGenres":[${
|
||||||
|
excludedGenres.joinToString {
|
||||||
|
"\"${
|
||||||
|
it.replace(
|
||||||
|
"Not ",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}\""
|
||||||
|
}
|
||||||
|
}]"""
|
||||||
else ""
|
else ""
|
||||||
}
|
}
|
||||||
${if (tags?.isNotEmpty() == true) ""","tags":[${tags.joinToString { "\"$it\"" }}]""" else ""}
|
${if (tags?.isNotEmpty() == true) ""","tags":[${tags.joinToString { "\"$it\"" }}]""" else ""}
|
||||||
${
|
${
|
||||||
if (excludedTags?.isNotEmpty() == true)
|
if (excludedTags?.isNotEmpty() == true)
|
||||||
""","excludedTags":[${excludedTags.joinToString { "\"${it.replace("Not ", "")}\"" }}]"""
|
""","excludedTags":[${
|
||||||
|
excludedTags.joinToString {
|
||||||
|
"\"${
|
||||||
|
it.replace(
|
||||||
|
"Not ",
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}\""
|
||||||
|
}
|
||||||
|
}]"""
|
||||||
else ""
|
else ""
|
||||||
}
|
}
|
||||||
}""".replace("\n", " ").replace(""" """, "")
|
}""".replace("\n", " ").replace(""" """, "")
|
||||||
|
@ -822,7 +862,8 @@ Page(page:$page,perPage:50) {
|
||||||
var page = 0
|
var page = 0
|
||||||
while (hasNextPage) {
|
while (hasNextPage) {
|
||||||
page++
|
page++
|
||||||
hasNextPage = executeQuery<Query.Studio>(query(page), force = true)?.data?.studio?.media?.let {
|
hasNextPage =
|
||||||
|
executeQuery<Query.Studio>(query(page), force = true)?.data?.studio?.media?.let {
|
||||||
it.edges?.forEach { i ->
|
it.edges?.forEach { i ->
|
||||||
i.node?.apply {
|
i.node?.apply {
|
||||||
val status = status.toString()
|
val status = status.toString()
|
||||||
|
@ -896,7 +937,10 @@ Page(page:$page,perPage:50) {
|
||||||
|
|
||||||
while (hasNextPage) {
|
while (hasNextPage) {
|
||||||
page++
|
page++
|
||||||
hasNextPage = executeQuery<Query.Author>(query(page), force = true)?.data?.author?.staffMedia?.let {
|
hasNextPage = executeQuery<Query.Author>(
|
||||||
|
query(page),
|
||||||
|
force = true
|
||||||
|
)?.data?.author?.staffMedia?.let {
|
||||||
it.edges?.forEach { i ->
|
it.edges?.forEach { i ->
|
||||||
i.node?.apply {
|
i.node?.apply {
|
||||||
val status = status.toString()
|
val status = status.toString()
|
||||||
|
|
|
@ -7,8 +7,8 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.discord.Discord
|
import ani.dantotsu.connections.discord.Discord
|
||||||
import ani.dantotsu.loadData
|
|
||||||
import ani.dantotsu.connections.mal.MAL
|
import ani.dantotsu.connections.mal.MAL
|
||||||
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.others.AppUpdater
|
import ani.dantotsu.others.AppUpdater
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
|
@ -19,9 +19,16 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
suspend fun getUserId(context: Context, block: () -> Unit) {
|
suspend fun getUserId(context: Context, block: () -> Unit) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (Discord.userid == null && Discord.token != null) {
|
val sharedPref = context.getSharedPreferences(
|
||||||
if (!Discord.getUserData())
|
context.getString(R.string.preference_file_key),
|
||||||
snackString(context.getString(R.string.error_loading_discord_user_data))
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
val token = sharedPref.getString("discord_token", null)
|
||||||
|
val userid = sharedPref.getString("discord_id", null)
|
||||||
|
if (userid == null && token != null) {
|
||||||
|
/*if (!Discord.getUserData())
|
||||||
|
snackString(context.getString(R.string.error_loading_discord_user_data))*/
|
||||||
|
//TODO: Discord.getUserData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,35 +49,53 @@ suspend fun getUserId(context: Context, block: () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnilistHomeViewModel : ViewModel() {
|
class AnilistHomeViewModel : ViewModel() {
|
||||||
private val listImages: MutableLiveData<ArrayList<String?>> = MutableLiveData<ArrayList<String?>>(arrayListOf())
|
private val listImages: MutableLiveData<ArrayList<String?>> =
|
||||||
|
MutableLiveData<ArrayList<String?>>(arrayListOf())
|
||||||
|
|
||||||
fun getListImages(): LiveData<ArrayList<String?>> = listImages
|
fun getListImages(): LiveData<ArrayList<String?>> = listImages
|
||||||
suspend fun setListImages() = listImages.postValue(Anilist.query.getBannerImages())
|
suspend fun setListImages() = listImages.postValue(Anilist.query.getBannerImages())
|
||||||
|
|
||||||
private val animeContinue: MutableLiveData<ArrayList<Media>> = MutableLiveData<ArrayList<Media>>(null)
|
private val animeContinue: MutableLiveData<ArrayList<Media>> =
|
||||||
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
|
|
||||||
fun getAnimeContinue(): LiveData<ArrayList<Media>> = animeContinue
|
fun getAnimeContinue(): LiveData<ArrayList<Media>> = animeContinue
|
||||||
suspend fun setAnimeContinue() = animeContinue.postValue(Anilist.query.continueMedia("ANIME"))
|
suspend fun setAnimeContinue() = animeContinue.postValue(Anilist.query.continueMedia("ANIME"))
|
||||||
|
|
||||||
private val animeFav: MutableLiveData<ArrayList<Media>> = MutableLiveData<ArrayList<Media>>(null)
|
private val animeFav: MutableLiveData<ArrayList<Media>> =
|
||||||
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
|
|
||||||
fun getAnimeFav(): LiveData<ArrayList<Media>> = animeFav
|
fun getAnimeFav(): LiveData<ArrayList<Media>> = animeFav
|
||||||
suspend fun setAnimeFav() = animeFav.postValue(Anilist.query.favMedia(true))
|
suspend fun setAnimeFav() = animeFav.postValue(Anilist.query.favMedia(true))
|
||||||
|
|
||||||
private val animePlanned: MutableLiveData<ArrayList<Media>> = MutableLiveData<ArrayList<Media>>(null)
|
private val animePlanned: MutableLiveData<ArrayList<Media>> =
|
||||||
fun getAnimePlanned(): LiveData<ArrayList<Media>> = animePlanned
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
suspend fun setAnimePlanned() = animePlanned.postValue(Anilist.query.continueMedia("ANIME", true))
|
|
||||||
|
fun getAnimePlanned(): LiveData<ArrayList<Media>> = animePlanned
|
||||||
|
suspend fun setAnimePlanned() =
|
||||||
|
animePlanned.postValue(Anilist.query.continueMedia("ANIME", true))
|
||||||
|
|
||||||
|
private val mangaContinue: MutableLiveData<ArrayList<Media>> =
|
||||||
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
|
|
||||||
private val mangaContinue: MutableLiveData<ArrayList<Media>> = MutableLiveData<ArrayList<Media>>(null)
|
|
||||||
fun getMangaContinue(): LiveData<ArrayList<Media>> = mangaContinue
|
fun getMangaContinue(): LiveData<ArrayList<Media>> = mangaContinue
|
||||||
suspend fun setMangaContinue() = mangaContinue.postValue(Anilist.query.continueMedia("MANGA"))
|
suspend fun setMangaContinue() = mangaContinue.postValue(Anilist.query.continueMedia("MANGA"))
|
||||||
|
|
||||||
private val mangaFav: MutableLiveData<ArrayList<Media>> = MutableLiveData<ArrayList<Media>>(null)
|
private val mangaFav: MutableLiveData<ArrayList<Media>> =
|
||||||
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
|
|
||||||
fun getMangaFav(): LiveData<ArrayList<Media>> = mangaFav
|
fun getMangaFav(): LiveData<ArrayList<Media>> = mangaFav
|
||||||
suspend fun setMangaFav() = mangaFav.postValue(Anilist.query.favMedia(false))
|
suspend fun setMangaFav() = mangaFav.postValue(Anilist.query.favMedia(false))
|
||||||
|
|
||||||
private val mangaPlanned: MutableLiveData<ArrayList<Media>> = MutableLiveData<ArrayList<Media>>(null)
|
private val mangaPlanned: MutableLiveData<ArrayList<Media>> =
|
||||||
fun getMangaPlanned(): LiveData<ArrayList<Media>> = mangaPlanned
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
suspend fun setMangaPlanned() = mangaPlanned.postValue(Anilist.query.continueMedia("MANGA", true))
|
|
||||||
|
fun getMangaPlanned(): LiveData<ArrayList<Media>> = mangaPlanned
|
||||||
|
suspend fun setMangaPlanned() =
|
||||||
|
mangaPlanned.postValue(Anilist.query.continueMedia("MANGA", true))
|
||||||
|
|
||||||
|
private val recommendation: MutableLiveData<ArrayList<Media>> =
|
||||||
|
MutableLiveData<ArrayList<Media>>(null)
|
||||||
|
|
||||||
private val recommendation: MutableLiveData<ArrayList<Media>> = MutableLiveData<ArrayList<Media>>(null)
|
|
||||||
fun getRecommendation(): LiveData<ArrayList<Media>> = recommendation
|
fun getRecommendation(): LiveData<ArrayList<Media>> = recommendation
|
||||||
suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations())
|
suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations())
|
||||||
|
|
||||||
|
@ -93,7 +118,9 @@ class AnilistAnimeViewModel : ViewModel() {
|
||||||
var notSet = true
|
var notSet = true
|
||||||
lateinit var searchResults: SearchResults
|
lateinit var searchResults: SearchResults
|
||||||
private val type = "ANIME"
|
private val type = "ANIME"
|
||||||
private val trending: MutableLiveData<MutableList<Media>> = MutableLiveData<MutableList<Media>>(null)
|
private val trending: MutableLiveData<MutableList<Media>> =
|
||||||
|
MutableLiveData<MutableList<Media>>(null)
|
||||||
|
|
||||||
fun getTrending(): LiveData<MutableList<Media>> = trending
|
fun getTrending(): LiveData<MutableList<Media>> = trending
|
||||||
suspend fun loadTrending(i: Int) {
|
suspend fun loadTrending(i: Int) {
|
||||||
val (season, year) = Anilist.currentSeasons[i]
|
val (season, year) = Anilist.currentSeasons[i]
|
||||||
|
@ -109,7 +136,9 @@ class AnilistAnimeViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val updated: MutableLiveData<MutableList<Media>> = MutableLiveData<MutableList<Media>>(null)
|
private val updated: MutableLiveData<MutableList<Media>> =
|
||||||
|
MutableLiveData<MutableList<Media>>(null)
|
||||||
|
|
||||||
fun getUpdated(): LiveData<MutableList<Media>> = updated
|
fun getUpdated(): LiveData<MutableList<Media>> = updated
|
||||||
suspend fun loadUpdated() = updated.postValue(Anilist.query.recentlyUpdated())
|
suspend fun loadUpdated() = updated.postValue(Anilist.query.recentlyUpdated())
|
||||||
|
|
||||||
|
@ -157,15 +186,33 @@ class AnilistMangaViewModel : ViewModel() {
|
||||||
var notSet = true
|
var notSet = true
|
||||||
lateinit var searchResults: SearchResults
|
lateinit var searchResults: SearchResults
|
||||||
private val type = "MANGA"
|
private val type = "MANGA"
|
||||||
private val trending: MutableLiveData<MutableList<Media>> = MutableLiveData<MutableList<Media>>(null)
|
private val trending: MutableLiveData<MutableList<Media>> =
|
||||||
|
MutableLiveData<MutableList<Media>>(null)
|
||||||
|
|
||||||
fun getTrending(): LiveData<MutableList<Media>> = trending
|
fun getTrending(): LiveData<MutableList<Media>> = trending
|
||||||
suspend fun loadTrending() =
|
suspend fun loadTrending() =
|
||||||
trending.postValue(Anilist.query.search(type, perPage = 10, sort = Anilist.sortBy[2], hd = true)?.results)
|
trending.postValue(
|
||||||
|
Anilist.query.search(
|
||||||
|
type,
|
||||||
|
perPage = 10,
|
||||||
|
sort = Anilist.sortBy[2],
|
||||||
|
hd = true
|
||||||
|
)?.results
|
||||||
|
)
|
||||||
|
|
||||||
|
private val updated: MutableLiveData<MutableList<Media>> =
|
||||||
|
MutableLiveData<MutableList<Media>>(null)
|
||||||
|
|
||||||
private val updated: MutableLiveData<MutableList<Media>> = MutableLiveData<MutableList<Media>>(null)
|
|
||||||
fun getTrendingNovel(): LiveData<MutableList<Media>> = updated
|
fun getTrendingNovel(): LiveData<MutableList<Media>> = updated
|
||||||
suspend fun loadTrendingNovel() =
|
suspend fun loadTrendingNovel() =
|
||||||
updated.postValue(Anilist.query.search(type, perPage = 10, sort = Anilist.sortBy[2], format = "NOVEL")?.results)
|
updated.postValue(
|
||||||
|
Anilist.query.search(
|
||||||
|
type,
|
||||||
|
perPage = 10,
|
||||||
|
sort = Anilist.sortBy[2],
|
||||||
|
format = "NOVEL"
|
||||||
|
)?.results
|
||||||
|
)
|
||||||
|
|
||||||
private val mangaPopular = MutableLiveData<SearchResults?>(null)
|
private val mangaPopular = MutableLiveData<SearchResults?>(null)
|
||||||
fun getPopular(): LiveData<SearchResults?> = mangaPopular
|
fun getPopular(): LiveData<SearchResults?> = mangaPopular
|
||||||
|
|
|
@ -6,9 +6,9 @@ import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import ani.dantotsu.logError
|
import ani.dantotsu.logError
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.logger
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
|
|
||||||
class Login : AppCompatActivity() {
|
class Login : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -18,7 +18,8 @@ ThemeManager(this).applyTheme()
|
||||||
val data: Uri? = intent?.data
|
val data: Uri? = intent?.data
|
||||||
logger(data.toString())
|
logger(data.toString())
|
||||||
try {
|
try {
|
||||||
Anilist.token = Regex("""(?<=access_token=).+(?=&token_type)""").find(data.toString())!!.value
|
Anilist.token =
|
||||||
|
Regex("""(?<=access_token=).+(?=&token_type)""").find(data.toString())!!.value
|
||||||
val filename = "anilistToken"
|
val filename = "anilistToken"
|
||||||
this.openFileOutput(filename, Context.MODE_PRIVATE).use {
|
this.openFileOutput(filename, Context.MODE_PRIVATE).use {
|
||||||
it.write(Anilist.token!!.toByteArray())
|
it.write(Anilist.token!!.toByteArray())
|
||||||
|
|
|
@ -27,7 +27,15 @@ data class SearchResults(
|
||||||
val list = mutableListOf<SearchChip>()
|
val list = mutableListOf<SearchChip>()
|
||||||
sort?.let {
|
sort?.let {
|
||||||
val c = currContext()!!
|
val c = currContext()!!
|
||||||
list.add(SearchChip("SORT", c.getString(R.string.filter_sort, c.resources.getStringArray(R.array.sort_by)[Anilist.sortBy.indexOf(it)])))
|
list.add(
|
||||||
|
SearchChip(
|
||||||
|
"SORT",
|
||||||
|
c.getString(
|
||||||
|
R.string.filter_sort,
|
||||||
|
c.resources.getStringArray(R.array.sort_by)[Anilist.sortBy.indexOf(it)]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
format?.let {
|
format?.let {
|
||||||
list.add(SearchChip("FORMAT", currContext()!!.getString(R.string.filter_format, it)))
|
list.add(SearchChip("FORMAT", currContext()!!.getString(R.string.filter_format, it)))
|
||||||
|
@ -42,13 +50,23 @@ data class SearchResults(
|
||||||
list.add(SearchChip("GENRE", it))
|
list.add(SearchChip("GENRE", it))
|
||||||
}
|
}
|
||||||
excludedGenres?.forEach {
|
excludedGenres?.forEach {
|
||||||
list.add(SearchChip("EXCLUDED_GENRE", currContext()!!.getString(R.string.filter_exclude, it)))
|
list.add(
|
||||||
|
SearchChip(
|
||||||
|
"EXCLUDED_GENRE",
|
||||||
|
currContext()!!.getString(R.string.filter_exclude, it)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
tags?.forEach {
|
tags?.forEach {
|
||||||
list.add(SearchChip("TAG", it))
|
list.add(SearchChip("TAG", it))
|
||||||
}
|
}
|
||||||
excludedTags?.forEach {
|
excludedTags?.forEach {
|
||||||
list.add(SearchChip("EXCLUDED_TAG", currContext()!!.getString(R.string.filter_exclude, it)))
|
list.add(
|
||||||
|
SearchChip(
|
||||||
|
"EXCLUDED_TAG",
|
||||||
|
currContext()!!.getString(R.string.filter_exclude, it)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import ani.dantotsu.loadMedia
|
import ani.dantotsu.loadMedia
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
|
|
||||||
class UrlMedia : Activity() {
|
class UrlMedia : Activity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -23,6 +23,9 @@ ThemeManager(this).applyTheme()
|
||||||
isMAL = data?.host != "anilist.co"
|
isMAL = data?.host != "anilist.co"
|
||||||
id = data?.pathSegments?.getOrNull(1)?.toIntOrNull()
|
id = data?.pathSegments?.getOrNull(1)?.toIntOrNull()
|
||||||
} else loadMedia = id
|
} else loadMedia = id
|
||||||
startMainActivity(this, bundleOf("mediaId" to id, "mal" to isMAL, "continue" to continueMedia))
|
startMainActivity(
|
||||||
|
this,
|
||||||
|
bundleOf("mediaId" to id, "mal" to isMAL, "continue" to continueMedia)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,6 +15,7 @@ class Query{
|
||||||
val user: ani.dantotsu.connections.anilist.api.User?
|
val user: ani.dantotsu.connections.anilist.api.User?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Media(
|
data class Media(
|
||||||
@SerialName("data")
|
@SerialName("data")
|
||||||
|
|
|
@ -3,7 +3,7 @@ package ani.dantotsu.connections.anilist.api
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.text.DateFormatSymbols
|
import java.text.DateFormatSymbols
|
||||||
import java.util.*
|
import java.util.Calendar
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
@kotlinx.serialization.Serializable
|
||||||
data class FuzzyDate(
|
data class FuzzyDate(
|
||||||
|
@ -16,9 +16,11 @@ data class FuzzyDate(
|
||||||
fun isEmpty(): Boolean {
|
fun isEmpty(): Boolean {
|
||||||
return year == null && month == null && day == null
|
return year == null && month == null && day == null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return if (isEmpty()) "??" else toStringOrEmpty()
|
return if (isEmpty()) "??" else toStringOrEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toStringOrEmpty(): String {
|
fun toStringOrEmpty(): String {
|
||||||
return listOfNotNull(
|
return listOfNotNull(
|
||||||
day?.toString(),
|
day?.toString(),
|
||||||
|
@ -29,7 +31,11 @@ data class FuzzyDate(
|
||||||
|
|
||||||
fun getToday(): FuzzyDate {
|
fun getToday(): FuzzyDate {
|
||||||
val cal = Calendar.getInstance()
|
val cal = Calendar.getInstance()
|
||||||
return FuzzyDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH))
|
return FuzzyDate(
|
||||||
|
cal.get(Calendar.YEAR),
|
||||||
|
cal.get(Calendar.MONTH) + 1,
|
||||||
|
cal.get(Calendar.DAY_OF_MONTH)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toVariableString(): String {
|
fun toVariableString(): String {
|
||||||
|
@ -39,6 +45,7 @@ data class FuzzyDate(
|
||||||
day?.let { "day:$it" }
|
day?.let { "day:$it" }
|
||||||
).joinToString(",", "{", "}")
|
).joinToString(",", "{", "}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toMALString(): String {
|
fun toMALString(): String {
|
||||||
val padding = '0'
|
val padding = '0'
|
||||||
val values = listOf(
|
val values = listOf(
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ani.dantotsu.connections.anilist.api
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Recommendation(
|
data class Recommendation(
|
||||||
// The id of the recommendation
|
// The id of the recommendation
|
||||||
|
@ -22,6 +23,7 @@ data class Recommendation(
|
||||||
// The user that first created the recommendation
|
// The user that first created the recommendation
|
||||||
@SerialName("user") var user: User?,
|
@SerialName("user") var user: User?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class RecommendationConnection(
|
data class RecommendationConnection(
|
||||||
//@SerialName("edges") var edges: List<RecommendationEdge>?,
|
//@SerialName("edges") var edges: List<RecommendationEdge>?,
|
||||||
|
|
|
@ -5,14 +5,11 @@ import android.content.Intent
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.discord.serializers.User
|
|
||||||
import ani.dantotsu.others.CustomBottomDialog
|
import ani.dantotsu.others.CustomBottomDialog
|
||||||
import ani.dantotsu.toast
|
import ani.dantotsu.toast
|
||||||
import ani.dantotsu.tryWith
|
import ani.dantotsu.tryWith
|
||||||
import ani.dantotsu.tryWithSuspend
|
|
||||||
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 java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object Discord {
|
object Discord {
|
||||||
|
@ -21,7 +18,7 @@ object Discord {
|
||||||
var userid: String? = null
|
var userid: String? = null
|
||||||
var avatar: String? = null
|
var avatar: String? = null
|
||||||
|
|
||||||
private const val TOKEN = "discord_token"
|
const val TOKEN = "discord_token"
|
||||||
|
|
||||||
fun getSavedToken(context: Context): Boolean {
|
fun getSavedToken(context: Context): Boolean {
|
||||||
val sharedPref = context.getSharedPreferences(
|
val sharedPref = context.getSharedPreferences(
|
||||||
|
@ -61,16 +58,6 @@ object Discord {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var rpc: RPC? = null
|
private var rpc: RPC? = null
|
||||||
suspend fun getUserData() = tryWithSuspend(true) {
|
|
||||||
if(rpc==null) {
|
|
||||||
val rpc = RPC(token!!, Dispatchers.IO).also { rpc = it }
|
|
||||||
val user: User = rpc.getUserData()
|
|
||||||
userid = user.username
|
|
||||||
avatar = user.userAvatar()
|
|
||||||
rpc.close()
|
|
||||||
true
|
|
||||||
} else true
|
|
||||||
} ?: false
|
|
||||||
|
|
||||||
|
|
||||||
fun warning(context: Context) = CustomBottomDialog().apply {
|
fun warning(context: Context) = CustomBottomDialog().apply {
|
||||||
|
@ -97,16 +84,21 @@ object Discord {
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun defaultRPC(): RPC? {
|
const val application_Id = "1163925779692912771"
|
||||||
|
const val small_Image: String =
|
||||||
|
"mp:attachments/1167176318266380288/1176997397797277856/logo-best_of_both.png"
|
||||||
|
/*fun defaultRPC(): RPC? {
|
||||||
return token?.let {
|
return token?.let {
|
||||||
RPC(it, Dispatchers.IO).apply {
|
RPC(it, Dispatchers.IO).apply {
|
||||||
applicationId = "1163925779692912771"
|
applicationId = application_Id
|
||||||
smallImage = RPC.Link(
|
smallImage = RPC.Link(
|
||||||
"Dantotsu",
|
"Dantotsu",
|
||||||
"mp:attachments/1167176318266380288/1176997397797277856/logo-best_of_both.png"
|
small_Image
|
||||||
)
|
)
|
||||||
buttons.add(RPC.Link("Stream on Dantotsu", "https://github.com/rebelonion/Dantotsu/"))
|
buttons.add(RPC.Link("Stream on Dantotsu", "https://github.com/rebelonion/Dantotsu/"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,475 @@
|
||||||
|
package ani.dantotsu.connections.discord
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import ani.dantotsu.MainActivity
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.discord.serializers.Presence
|
||||||
|
import ani.dantotsu.connections.discord.serializers.User
|
||||||
|
import ani.dantotsu.isOnline
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.WebSocket
|
||||||
|
import okhttp3.WebSocketListener
|
||||||
|
import java.io.File
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
|
|
||||||
|
class DiscordService : Service() {
|
||||||
|
private var heartbeat: Int = 0
|
||||||
|
private var sequence: Int? = null
|
||||||
|
private var sessionId: String = ""
|
||||||
|
private var resume = false
|
||||||
|
private lateinit var logFile: File
|
||||||
|
private lateinit var webSocket: WebSocket
|
||||||
|
private lateinit var heartbeatThread: Thread
|
||||||
|
private lateinit var client: OkHttpClient
|
||||||
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
|
var presenceStore = ""
|
||||||
|
val json = Json {
|
||||||
|
encodeDefaults = true
|
||||||
|
allowStructuredMapKeys = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
coerceInputValues = true
|
||||||
|
}
|
||||||
|
var log = ""
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
log("Service onCreate()")
|
||||||
|
val powerManager = baseContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
wakeLock = powerManager.newWakeLock(
|
||||||
|
PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
|
"discordRPC:backgroundPresence"
|
||||||
|
)
|
||||||
|
wakeLock.acquire()
|
||||||
|
log("WakeLock Acquired")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val serviceChannel = NotificationChannel(
|
||||||
|
"discordPresence",
|
||||||
|
"Discord Presence Service Channel",
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
val manager = getSystemService(NotificationManager::class.java)
|
||||||
|
manager.createNotificationChannel(serviceChannel)
|
||||||
|
}
|
||||||
|
val intent = Intent(this, MainActivity::class.java).apply {
|
||||||
|
action = Intent.ACTION_MAIN
|
||||||
|
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
val pendingIntent =
|
||||||
|
PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
val builder = NotificationCompat.Builder(this, "discordPresence")
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher_round)
|
||||||
|
.setContentTitle("Discord Presence")
|
||||||
|
.setContentText("Running in the background")
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
startForeground(1, builder.build())
|
||||||
|
log("Foreground service started, notification shown")
|
||||||
|
client = OkHttpClient()
|
||||||
|
client.newWebSocket(
|
||||||
|
Request.Builder().url("wss://gateway.discord.gg/?v=10&encoding=json").build(),
|
||||||
|
DiscordWebSocketListener()
|
||||||
|
)
|
||||||
|
client.dispatcher.executorService.shutdown()
|
||||||
|
SERVICE_RUNNING = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
log("Service onStartCommand()")
|
||||||
|
if (intent != null) {
|
||||||
|
if (intent.hasExtra("presence")) {
|
||||||
|
log("Service onStartCommand() setPresence")
|
||||||
|
val lPresence = intent.getStringExtra("presence")
|
||||||
|
if (this::webSocket.isInitialized) webSocket.send(lPresence!!)
|
||||||
|
presenceStore = lPresence!!
|
||||||
|
} else {
|
||||||
|
log("Service onStartCommand() no presence")
|
||||||
|
DiscordServiceRunningSingleton.running = false
|
||||||
|
//kill the client
|
||||||
|
client = OkHttpClient()
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return START_REDELIVER_INTENT
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
log("Service Destroyed")
|
||||||
|
if (DiscordServiceRunningSingleton.running) {
|
||||||
|
log("Accidental Service Destruction, restarting service")
|
||||||
|
val intent = Intent(baseContext, DiscordService::class.java)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
baseContext.startForegroundService(intent)
|
||||||
|
} else {
|
||||||
|
baseContext.startService(intent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this::webSocket.isInitialized)
|
||||||
|
setPresence(
|
||||||
|
json.encodeToString(
|
||||||
|
Presence.Response(
|
||||||
|
3,
|
||||||
|
Presence(status = "offline")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
wakeLock.release()
|
||||||
|
}
|
||||||
|
SERVICE_RUNNING = false
|
||||||
|
client = OkHttpClient()
|
||||||
|
if (this::webSocket.isInitialized) webSocket.close(1000, "Closed by user")
|
||||||
|
super.onDestroy()
|
||||||
|
//saveLogToFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveProfile(response: String) {
|
||||||
|
val sharedPref = baseContext.getSharedPreferences(
|
||||||
|
baseContext.getString(R.string.preference_file_key),
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
val user = json.decodeFromString<User.Response>(response).d.user
|
||||||
|
log("User data: $user")
|
||||||
|
with(sharedPref.edit()) {
|
||||||
|
putString("discord_username", user.username)
|
||||||
|
putString("discord_id", user.id)
|
||||||
|
putString("discord_avatar", user.avatar)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(p0: Intent?): IBinder? = null
|
||||||
|
|
||||||
|
inner class DiscordWebSocketListener : WebSocketListener() {
|
||||||
|
|
||||||
|
var retryAttempts = 0
|
||||||
|
val maxRetryAttempts = 10
|
||||||
|
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||||
|
super.onOpen(webSocket, response)
|
||||||
|
this@DiscordService.webSocket = webSocket
|
||||||
|
log("WebSocket: Opened")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||||
|
super.onMessage(webSocket, text)
|
||||||
|
val json = JsonParser.parseString(text).asJsonObject
|
||||||
|
log("WebSocket: Received op code ${json.get("op")}")
|
||||||
|
when (json.get("op").asInt) {
|
||||||
|
0 -> {
|
||||||
|
if (json.has("s")) {
|
||||||
|
log("WebSocket: Sequence ${json.get("s")} Received")
|
||||||
|
sequence = json.get("s").asInt
|
||||||
|
}
|
||||||
|
if (json.get("t").asString != "READY") return
|
||||||
|
saveProfile(text)
|
||||||
|
log(text)
|
||||||
|
sessionId = json.get("d").asJsonObject.get("session_id").asString
|
||||||
|
log("WebSocket: SessionID ${json.get("d").asJsonObject.get("session_id")} Received")
|
||||||
|
if (presenceStore.isNotEmpty()) setPresence(presenceStore)
|
||||||
|
sendBroadcast(Intent("ServiceToConnectButton"))
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> {
|
||||||
|
log("WebSocket: Received Heartbeat request, sending heartbeat")
|
||||||
|
heartbeatThread.interrupt()
|
||||||
|
heartbeatSend(webSocket, sequence)
|
||||||
|
heartbeatThread = Thread(HeartbeatRunnable())
|
||||||
|
heartbeatThread.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
7 -> {
|
||||||
|
resume = true
|
||||||
|
log("WebSocket: Requested to Restart, restarting")
|
||||||
|
webSocket.close(1000, "Requested to Restart by the server")
|
||||||
|
client = OkHttpClient()
|
||||||
|
client.newWebSocket(
|
||||||
|
Request.Builder().url("wss://gateway.discord.gg/?v=10&encoding=json")
|
||||||
|
.build(),
|
||||||
|
DiscordWebSocketListener()
|
||||||
|
)
|
||||||
|
client.dispatcher.executorService.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
9 -> {
|
||||||
|
log("WebSocket: Invalid Session, restarting")
|
||||||
|
webSocket.close(1000, "Invalid Session")
|
||||||
|
Thread.sleep(5000)
|
||||||
|
client = OkHttpClient()
|
||||||
|
client.newWebSocket(
|
||||||
|
Request.Builder().url("wss://gateway.discord.gg/?v=10&encoding=json")
|
||||||
|
.build(),
|
||||||
|
DiscordWebSocketListener()
|
||||||
|
)
|
||||||
|
client.dispatcher.executorService.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
10 -> {
|
||||||
|
heartbeat = json.get("d").asJsonObject.get("heartbeat_interval").asInt
|
||||||
|
heartbeatThread = Thread(HeartbeatRunnable())
|
||||||
|
heartbeatThread.start()
|
||||||
|
if (resume) {
|
||||||
|
log("WebSocket: Resuming because server requested")
|
||||||
|
resume()
|
||||||
|
resume = false
|
||||||
|
} else {
|
||||||
|
identify(webSocket, baseContext)
|
||||||
|
log("WebSocket: Identified")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
11 -> {
|
||||||
|
log("WebSocket: Heartbeat ACKed")
|
||||||
|
heartbeatThread = Thread(HeartbeatRunnable())
|
||||||
|
heartbeatThread.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun identify(webSocket: WebSocket, context: Context) {
|
||||||
|
val properties = JsonObject()
|
||||||
|
properties.addProperty("os", "linux")
|
||||||
|
properties.addProperty("browser", "unknown")
|
||||||
|
properties.addProperty("device", "unknown")
|
||||||
|
val d = JsonObject()
|
||||||
|
d.addProperty("token", getToken(context))
|
||||||
|
d.addProperty("intents", 0)
|
||||||
|
d.add("properties", properties)
|
||||||
|
val payload = JsonObject()
|
||||||
|
payload.addProperty("op", 2)
|
||||||
|
payload.add("d", d)
|
||||||
|
webSocket.send(payload.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||||
|
super.onFailure(webSocket, t, response)
|
||||||
|
if (!isOnline(baseContext)) {
|
||||||
|
log("WebSocket: Error, onFailure() reason: No Internet")
|
||||||
|
errorNotification("Could not set the presence", "No Internet")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
retryAttempts++
|
||||||
|
if (retryAttempts >= maxRetryAttempts) {
|
||||||
|
log("WebSocket: Error, onFailure() reason: Max Retry Attempts")
|
||||||
|
errorNotification("Could not set the presence", "Max Retry Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.message?.let { Log.d("WebSocket", "onFailure() $it") }
|
||||||
|
log("WebSocket: Error, onFailure() reason: ${t.message}")
|
||||||
|
client = OkHttpClient()
|
||||||
|
client.newWebSocket(
|
||||||
|
Request.Builder().url("wss://gateway.discord.gg/?v=10&encoding=json").build(),
|
||||||
|
DiscordWebSocketListener()
|
||||||
|
)
|
||||||
|
client.dispatcher.executorService.shutdown()
|
||||||
|
if (::heartbeatThread.isInitialized && !heartbeatThread.isInterrupted) {
|
||||||
|
heartbeatThread.interrupt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
||||||
|
super.onClosing(webSocket, code, reason)
|
||||||
|
Log.d("WebSocket", "onClosing() $code $reason")
|
||||||
|
if (::heartbeatThread.isInitialized && !heartbeatThread.isInterrupted) {
|
||||||
|
heartbeatThread.interrupt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||||
|
super.onClosed(webSocket, code, reason)
|
||||||
|
Log.d("WebSocket", "onClosed() $code $reason")
|
||||||
|
if (code >= 4000) {
|
||||||
|
log("WebSocket: Error, code: $code reason: $reason")
|
||||||
|
client = OkHttpClient()
|
||||||
|
client.newWebSocket(
|
||||||
|
Request.Builder().url("wss://gateway.discord.gg/?v=10&encoding=json").build(),
|
||||||
|
DiscordWebSocketListener()
|
||||||
|
)
|
||||||
|
client.dispatcher.executorService.shutdown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getToken(context: Context): String {
|
||||||
|
val sharedPref = context.getSharedPreferences(
|
||||||
|
context.getString(R.string.preference_file_key),
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
val token = sharedPref.getString(Discord.TOKEN, null)
|
||||||
|
if (token == null) {
|
||||||
|
log("WebSocket: Token not found")
|
||||||
|
errorNotification("Could not set the presence", "token not found")
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun heartbeatSend(webSocket: WebSocket, seq: Int?) {
|
||||||
|
val json = JsonObject()
|
||||||
|
json.addProperty("op", 1)
|
||||||
|
json.addProperty("d", seq)
|
||||||
|
webSocket.send(json.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun errorNotification(title: String, text: String) {
|
||||||
|
val intent = Intent(this@DiscordService, MainActivity::class.java).apply {
|
||||||
|
action = Intent.ACTION_MAIN
|
||||||
|
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
val pendingIntent =
|
||||||
|
PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
val builder = NotificationCompat.Builder(this@DiscordService, "discordPresence")
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher_round)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(text)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
val notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
//TODO: Request permission
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notificationManager.notify(2, builder.build())
|
||||||
|
log("Error Notified")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveSimpleTestPresence() {
|
||||||
|
val file = File(baseContext.cacheDir, "payload")
|
||||||
|
//fill with test payload
|
||||||
|
val payload = JsonObject()
|
||||||
|
payload.addProperty("op", 3)
|
||||||
|
payload.add("d", JsonObject().apply {
|
||||||
|
addProperty("status", "online")
|
||||||
|
addProperty("afk", false)
|
||||||
|
add("activities", JsonArray().apply {
|
||||||
|
add(JsonObject().apply {
|
||||||
|
addProperty("name", "Test")
|
||||||
|
addProperty("type", 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
file.writeText(payload.toString())
|
||||||
|
log("WebSocket: Simple Test Presence Saved")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPresence(String: String) {
|
||||||
|
log("WebSocket: Sending Presence payload")
|
||||||
|
log(String)
|
||||||
|
webSocket.send(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun log(string: String) {
|
||||||
|
Log.d("WebSocket_Discord", string)
|
||||||
|
//log += "${SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().time)} $string\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveLogToFile() {
|
||||||
|
val fileName = "log_${System.currentTimeMillis()}.txt"
|
||||||
|
|
||||||
|
// ContentValues to store file metadata
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserting the file in the MediaStore
|
||||||
|
val resolver = baseContext.contentResolver
|
||||||
|
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
|
||||||
|
} else {
|
||||||
|
val directory =
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
val file = File(directory, fileName)
|
||||||
|
|
||||||
|
// Make sure the Downloads directory exists
|
||||||
|
if (!directory.exists()) {
|
||||||
|
directory.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use FileProvider to get the URI for the file
|
||||||
|
val authority =
|
||||||
|
"${baseContext.packageName}.provider" // Adjust with your app's package name
|
||||||
|
Uri.fromFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing to the file
|
||||||
|
uri?.let {
|
||||||
|
resolver.openOutputStream(it).use { outputStream ->
|
||||||
|
OutputStreamWriter(outputStream).use { writer ->
|
||||||
|
writer.write(log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
log("Error saving log file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resume() {
|
||||||
|
log("Sending Resume payload")
|
||||||
|
val d = JsonObject()
|
||||||
|
d.addProperty("token", getToken(baseContext))
|
||||||
|
d.addProperty("session_id", sessionId)
|
||||||
|
d.addProperty("seq", sequence)
|
||||||
|
val json = JsonObject()
|
||||||
|
json.addProperty("op", 6)
|
||||||
|
json.add("d", d)
|
||||||
|
log(json.toString())
|
||||||
|
webSocket.send(json.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class HeartbeatRunnable : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(heartbeat.toLong())
|
||||||
|
heartbeatSend(webSocket, sequence)
|
||||||
|
log("WebSocket: Heartbeat Sent")
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var SERVICE_RUNNING = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object DiscordServiceRunningSingleton {
|
||||||
|
var running = false
|
||||||
|
|
||||||
|
}
|
|
@ -7,15 +7,13 @@ import android.os.Bundle
|
||||||
import android.webkit.WebResourceRequest
|
import android.webkit.WebResourceRequest
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
import androidx.annotation.RequiresApi
|
import android.widget.Toast
|
||||||
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.logger
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.startMainActivity
|
import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.snackString
|
|
||||||
|
|
||||||
class Login : AppCompatActivity() {
|
class Login : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -39,17 +37,22 @@ class Login : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
WebView.setWebContentsDebuggingEnabled(true)
|
WebView.setWebContentsDebuggingEnabled(true)
|
||||||
webView.webViewClient = object : WebViewClient() {
|
webView.webViewClient = object : WebViewClient() {
|
||||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
override fun shouldOverrideUrlLoading(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?
|
||||||
|
): Boolean {
|
||||||
// Check if the URL is the one expected after a successful login
|
// Check if the URL is the one expected after a successful login
|
||||||
if (request?.url.toString() != "https://discord.com/login") {
|
if (request?.url.toString() != "https://discord.com/login") {
|
||||||
// Delay the script execution to ensure the page is fully loaded
|
// Delay the script execution to ensure the page is fully loaded
|
||||||
view?.postDelayed({
|
view?.postDelayed({
|
||||||
view.evaluateJavascript("""
|
view.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
const wreq = (webpackChunkdiscord_app.push([[''],{},e=>{m=[];for(let c in e.c)m.push(e.c[c])}]),m).find(m=>m?.exports?.default?.getToken!==void 0).exports.default.getToken();
|
const wreq = (webpackChunkdiscord_app.push([[''],{},e=>{m=[];for(let c in e.c)m.push(e.c[c])}]),m).find(m=>m?.exports?.default?.getToken!==void 0).exports.default.getToken();
|
||||||
return wreq;
|
return wreq;
|
||||||
})()
|
})()
|
||||||
""".trimIndent()) { result ->
|
""".trimIndent()
|
||||||
|
) { result ->
|
||||||
login(result.trim('"'))
|
login(result.trim('"'))
|
||||||
}
|
}
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
@ -67,11 +70,11 @@ class Login : AppCompatActivity() {
|
||||||
|
|
||||||
private fun login(token: String) {
|
private fun login(token: String) {
|
||||||
if (token.isEmpty() || token == "null") {
|
if (token.isEmpty() || token == "null") {
|
||||||
snackString("Failed to retrieve token")
|
Toast.makeText(this, "Failed to retrieve token", Toast.LENGTH_SHORT).show()
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
snackString("Logged in successfully")
|
Toast.makeText(this, "Logged in successfully", Toast.LENGTH_SHORT).show()
|
||||||
finish()
|
finish()
|
||||||
saveToken(this, token)
|
saveToken(this, token)
|
||||||
startMainActivity(this@Login)
|
startMainActivity(this@Login)
|
||||||
|
|
|
@ -1,26 +1,10 @@
|
||||||
package ani.dantotsu.connections.discord
|
package ani.dantotsu.connections.discord
|
||||||
|
|
||||||
import ani.dantotsu.connections.discord.serializers.*
|
import ani.dantotsu.connections.discord.serializers.Activity
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import ani.dantotsu.connections.discord.serializers.Presence
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import kotlinx.serialization.json.long
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import okhttp3.WebSocket
|
|
||||||
import okhttp3.WebSocketListener
|
|
||||||
import java.util.concurrent.TimeUnit.*
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import ani.dantotsu.client as app
|
import ani.dantotsu.client as app
|
||||||
|
|
||||||
|
@ -33,72 +17,30 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private val client = OkHttpClient.Builder()
|
|
||||||
.connectTimeout(10, SECONDS)
|
|
||||||
.readTimeout(10, SECONDS)
|
|
||||||
.writeTimeout(10, SECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private val request = Request.Builder()
|
|
||||||
.url("wss://gateway.discord.gg/?encoding=json&v=10")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private var webSocket = client.newWebSocket(request, Listener())
|
|
||||||
|
|
||||||
var applicationId: String? = null
|
|
||||||
var type: Type? = null
|
|
||||||
var activityName: String? = null
|
|
||||||
var details: String? = null
|
|
||||||
var state: String? = null
|
|
||||||
var largeImage: Link? = null
|
|
||||||
var smallImage: Link? = null
|
|
||||||
var status: String? = null
|
|
||||||
var startTimestamp: Long? = null
|
|
||||||
var stopTimestamp: Long? = null
|
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
PLAYING, STREAMING, LISTENING, WATCHING, COMPETING
|
PLAYING, STREAMING, LISTENING, WATCHING, COMPETING
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttons = mutableListOf<Link>()
|
|
||||||
|
|
||||||
data class Link(val label: String, val url: String)
|
data class Link(val label: String, val url: String)
|
||||||
|
|
||||||
private suspend fun createPresence(): String {
|
companion object {
|
||||||
return json.encodeToString(Presence.Response(
|
data class RPCData(
|
||||||
3,
|
val applicationId: String? = null,
|
||||||
Presence(
|
val type: Type? = null,
|
||||||
activities = listOf(
|
val activityName: String? = null,
|
||||||
Activity(
|
val details: String? = null,
|
||||||
name = activityName,
|
val state: String? = null,
|
||||||
state = state,
|
val largeImage: Link? = null,
|
||||||
details = details,
|
val smallImage: Link? = null,
|
||||||
type = type?.ordinal,
|
val status: String? = null,
|
||||||
timestamps = if (startTimestamp != null)
|
val startTimestamp: Long? = null,
|
||||||
Activity.Timestamps(startTimestamp, stopTimestamp)
|
val stopTimestamp: Long? = null,
|
||||||
else null,
|
val buttons: MutableList<Link> = mutableListOf()
|
||||||
assets = Activity.Assets(
|
|
||||||
largeImage = largeImage?.url?.discordUrl(),
|
|
||||||
largeText = largeImage?.label,
|
|
||||||
smallImage = smallImage?.url?.discordUrl(),
|
|
||||||
smallText = smallImage?.label
|
|
||||||
),
|
|
||||||
buttons = buttons.map { it.label },
|
|
||||||
metadata = Activity.Metadata(
|
|
||||||
buttonUrls = buttons.map { it.url }
|
|
||||||
),
|
|
||||||
applicationId = applicationId,
|
|
||||||
)
|
)
|
||||||
),
|
|
||||||
afk = true,
|
|
||||||
since = startTimestamp,
|
|
||||||
status = status
|
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class KizzyApi(val id: String)
|
data class KizzyApi(val id: String)
|
||||||
|
|
||||||
val api = "https://kizzy-api.vercel.app/image?url="
|
val api = "https://kizzy-api.vercel.app/image?url="
|
||||||
private suspend fun String.discordUrl(): String? {
|
private suspend fun String.discordUrl(): String? {
|
||||||
if (startsWith("mp:")) return this
|
if (startsWith("mp:")) return this
|
||||||
|
@ -106,130 +48,42 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
||||||
return json?.id
|
return json?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendIdentify() {
|
suspend fun createPresence(data: RPCData): String {
|
||||||
val response = Identity.Response(
|
val json = Json {
|
||||||
op = 2,
|
encodeDefaults = true
|
||||||
d = Identity(
|
allowStructuredMapKeys = true
|
||||||
token = token,
|
ignoreUnknownKeys = true
|
||||||
properties = Identity.Properties(
|
|
||||||
os = "windows",
|
|
||||||
browser = "Chrome",
|
|
||||||
device = "disco"
|
|
||||||
),
|
|
||||||
compress = false,
|
|
||||||
intents = 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
webSocket.send(json.encodeToString(response))
|
|
||||||
}
|
}
|
||||||
|
return json.encodeToString(Presence.Response(
|
||||||
fun send(block: RPC.() -> Unit) {
|
|
||||||
block.invoke(this)
|
|
||||||
send()
|
|
||||||
}
|
|
||||||
|
|
||||||
var started = false
|
|
||||||
var whenStarted: ((User) -> Unit)? = null
|
|
||||||
|
|
||||||
fun send() {
|
|
||||||
val send = {
|
|
||||||
CoroutineScope(coroutineContext).launch {
|
|
||||||
webSocket.send(createPresence())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!started) whenStarted = {
|
|
||||||
send.invoke()
|
|
||||||
whenStarted = null
|
|
||||||
}
|
|
||||||
else send.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun close() {
|
|
||||||
webSocket.send(
|
|
||||||
json.encodeToString(
|
|
||||||
Presence.Response(
|
|
||||||
3,
|
3,
|
||||||
Presence(status = "offline")
|
Presence(
|
||||||
|
activities = listOf(
|
||||||
|
Activity(
|
||||||
|
name = data.activityName,
|
||||||
|
state = data.state,
|
||||||
|
details = data.details,
|
||||||
|
type = data.type?.ordinal,
|
||||||
|
timestamps = if (data.startTimestamp != null)
|
||||||
|
Activity.Timestamps(data.startTimestamp, data.stopTimestamp)
|
||||||
|
else null,
|
||||||
|
assets = Activity.Assets(
|
||||||
|
largeImage = data.largeImage?.url?.discordUrl(),
|
||||||
|
largeText = data.largeImage?.label,
|
||||||
|
smallImage = data.smallImage?.url?.discordUrl(),
|
||||||
|
smallText = data.smallImage?.label
|
||||||
|
),
|
||||||
|
buttons = data.buttons.map { it.label },
|
||||||
|
metadata = Activity.Metadata(
|
||||||
|
buttonUrls = data.buttons.map { it.url }
|
||||||
|
),
|
||||||
|
applicationId = data.applicationId,
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
afk = true,
|
||||||
|
since = data.startTimestamp,
|
||||||
|
status = data.status
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
webSocket.close(4000, "Interrupt")
|
|
||||||
}
|
|
||||||
|
|
||||||
//I kinda hate this
|
|
||||||
suspend fun getUserData(): User = suspendCancellableCoroutine { continuation ->
|
|
||||||
whenStarted = {
|
|
||||||
continuation.resume(it, onCancellation = null)
|
|
||||||
whenStarted = null
|
|
||||||
}
|
|
||||||
continuation.invokeOnCancellation {
|
|
||||||
whenStarted = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var onReceiveUserData: ((User) -> Deferred<Unit>)? = null
|
|
||||||
|
|
||||||
inner class Listener : WebSocketListener() {
|
|
||||||
private var seq: Int? = null
|
|
||||||
private var heartbeatInterval: Long? = null
|
|
||||||
|
|
||||||
var scope = CoroutineScope(coroutineContext)
|
|
||||||
|
|
||||||
private fun sendHeartBeat() {
|
|
||||||
scope.cancel()
|
|
||||||
scope = CoroutineScope(coroutineContext)
|
|
||||||
scope.launch {
|
|
||||||
delay(heartbeatInterval!!)
|
|
||||||
webSocket.send("{\"op\":1, \"d\":$seq}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
|
||||||
println("Discord Message : $text")
|
|
||||||
|
|
||||||
val map = json.decodeFromString<Res>(text)
|
|
||||||
seq = map.s
|
|
||||||
|
|
||||||
when (map.op) {
|
|
||||||
10 -> {
|
|
||||||
map.d as JsonObject
|
|
||||||
heartbeatInterval = map.d["heartbeat_interval"]!!.jsonPrimitive.long
|
|
||||||
sendHeartBeat()
|
|
||||||
sendIdentify()
|
|
||||||
}
|
|
||||||
|
|
||||||
0 -> if (map.t == "READY") {
|
|
||||||
val user = json.decodeFromString<User.Response>(text).d.user
|
|
||||||
started = true
|
|
||||||
whenStarted?.invoke(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
1 -> {
|
|
||||||
if (scope.isActive) scope.cancel()
|
|
||||||
webSocket.send("{\"op\":1, \"d\":$seq}")
|
|
||||||
}
|
|
||||||
|
|
||||||
11 -> sendHeartBeat()
|
|
||||||
7 -> webSocket.close(400, "Reconnect")
|
|
||||||
9 -> {
|
|
||||||
sendHeartBeat()
|
|
||||||
sendIdentify()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
|
||||||
println("Server Closed : $code $reason")
|
|
||||||
if (code == 4000) {
|
|
||||||
scope.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
|
||||||
println("Failure : ${t.message}")
|
|
||||||
if (t.message != "Interrupt") {
|
|
||||||
this@RPC.webSocket = client.newWebSocket(request, Listener())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ani.dantotsu.connections.discord.serializers
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Activity(
|
data class Activity(
|
||||||
@SerialName("application_id")
|
@SerialName("application_id")
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
package ani.dantotsu.connections.discord.serializers
|
package ani.dantotsu.connections.discord.serializers
|
||||||
|
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.descriptors.*
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encoding.*
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.*
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class User(
|
data class User(
|
||||||
val verified: Boolean,
|
val verified: Boolean? = null,
|
||||||
val username: String,
|
val username: String,
|
||||||
|
|
||||||
@SerialName("purchased_flags")
|
@SerialName("purchased_flags")
|
||||||
val purchasedFlags: Long,
|
val purchasedFlags: Long? = null,
|
||||||
|
|
||||||
@SerialName("public_flags")
|
@SerialName("public_flags")
|
||||||
val publicFlags: Long,
|
val publicFlags: Long? = null,
|
||||||
|
|
||||||
val pronouns: String,
|
val pronouns: String? = null,
|
||||||
|
|
||||||
@SerialName("premium_type")
|
@SerialName("premium_type")
|
||||||
val premiumType: Long,
|
val premiumType: Long? = null,
|
||||||
|
|
||||||
val premium: Boolean,
|
val premium: Boolean? = null,
|
||||||
val phone: String,
|
val phone: String? = null,
|
||||||
|
|
||||||
@SerialName("nsfw_allowed")
|
@SerialName("nsfw_allowed")
|
||||||
val nsfwAllowed: Boolean,
|
val nsfwAllowed: Boolean? = null,
|
||||||
|
|
||||||
val mobile: Boolean,
|
val mobile: Boolean? = null,
|
||||||
|
|
||||||
@SerialName("mfa_enabled")
|
@SerialName("mfa_enabled")
|
||||||
val mfaEnabled: Boolean,
|
val mfaEnabled: Boolean? = null,
|
||||||
|
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
||||||
@SerialName("global_name")
|
@SerialName("global_name")
|
||||||
val globalName: String,
|
val globalName: String? = null,
|
||||||
|
|
||||||
val flags: Long,
|
val flags: Long? = null,
|
||||||
val email: String,
|
val email: String? = null,
|
||||||
val discriminator: String,
|
val discriminator: String? = null,
|
||||||
val desktop: Boolean,
|
val desktop: Boolean? = null,
|
||||||
val bio: String,
|
val bio: String? = null,
|
||||||
|
|
||||||
@SerialName("banner_color")
|
@SerialName("banner_color")
|
||||||
val bannerColor: String,
|
val bannerColor: String? = null,
|
||||||
|
|
||||||
val banner: JsonElement? = null,
|
val banner: JsonElement? = null,
|
||||||
|
|
||||||
@SerialName("avatar_decoration")
|
@SerialName("avatar_decoration")
|
||||||
val avatarDecoration: JsonElement? = null,
|
val avatarDecoration: JsonElement? = null,
|
||||||
|
|
||||||
val avatar: String,
|
val avatar: String? = null,
|
||||||
|
|
||||||
@SerialName("accent_color")
|
@SerialName("accent_color")
|
||||||
val accentColor: Long
|
val accentColor: Long? = null
|
||||||
) {
|
) {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Response(
|
data class Response(
|
||||||
|
|
|
@ -4,11 +4,17 @@ import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.client
|
||||||
import ani.dantotsu.connections.mal.MAL.clientId
|
import ani.dantotsu.connections.mal.MAL.clientId
|
||||||
import ani.dantotsu.connections.mal.MAL.saveResponse
|
import ani.dantotsu.connections.mal.MAL.saveResponse
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.loadData
|
||||||
|
import ani.dantotsu.logError
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.others.LangSet
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.startMainActivity
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.tryWithSuspend
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -46,8 +52,7 @@ ThemeManager(this).applyTheme()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (e: Exception) {
|
||||||
catch (e:Exception){
|
|
||||||
logError(e, snackbar = false)
|
logError(e, snackbar = false)
|
||||||
startMainActivity(this)
|
startMainActivity(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,13 @@ import android.net.Uri
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.client
|
||||||
|
import ani.dantotsu.currContext
|
||||||
|
import ani.dantotsu.loadData
|
||||||
|
import ani.dantotsu.openLinkInBrowser
|
||||||
|
import ani.dantotsu.saveData
|
||||||
|
import ani.dantotsu.tryWithSuspend
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ani.dantotsu.connections.mal
|
package ani.dantotsu.connections.mal
|
||||||
|
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
|
||||||
import ani.dantotsu.client
|
import ani.dantotsu.client
|
||||||
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.others.LangSet
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
|
||||||
class DownloadContainerActivity : AppCompatActivity() {
|
class DownloadContainerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ import java.io.File
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class DownloadsManager(private val context: Context) {
|
class DownloadsManager(private val context: Context) {
|
||||||
private val prefs: SharedPreferences = context.getSharedPreferences("downloads_pref", Context.MODE_PRIVATE)
|
private val prefs: SharedPreferences =
|
||||||
|
context.getSharedPreferences("downloads_pref", Context.MODE_PRIVATE)
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
private val downloadsList = loadDownloads().toMutableList()
|
private val downloadsList = loadDownloads().toMutableList()
|
||||||
|
|
||||||
|
@ -18,6 +19,8 @@ class DownloadsManager(private val context: Context) {
|
||||||
get() = downloadsList.filter { it.type == Download.Type.MANGA }
|
get() = downloadsList.filter { it.type == Download.Type.MANGA }
|
||||||
val animeDownloads: List<Download>
|
val animeDownloads: List<Download>
|
||||||
get() = downloadsList.filter { it.type == Download.Type.ANIME }
|
get() = downloadsList.filter { it.type == Download.Type.ANIME }
|
||||||
|
val novelDownloads: List<Download>
|
||||||
|
get() = downloadsList.filter { it.type == Download.Type.NOVEL }
|
||||||
|
|
||||||
private fun saveDownloads() {
|
private fun saveDownloads() {
|
||||||
val jsonString = gson.toJson(downloadsList)
|
val jsonString = gson.toJson(downloadsList)
|
||||||
|
@ -45,11 +48,52 @@ class DownloadsManager(private val context: Context) {
|
||||||
saveDownloads()
|
saveDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeMedia(title: String, type: Download.Type) {
|
||||||
|
val subDirectory = if (type == Download.Type.MANGA) {
|
||||||
|
"Manga"
|
||||||
|
} else if (type == Download.Type.ANIME) {
|
||||||
|
"Anime"
|
||||||
|
} else {
|
||||||
|
"Novel"
|
||||||
|
}
|
||||||
|
val directory = File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/$subDirectory/$title"
|
||||||
|
)
|
||||||
|
if (directory.exists()) {
|
||||||
|
val deleted = directory.deleteRecursively()
|
||||||
|
if (deleted) {
|
||||||
|
Toast.makeText(context, "Successfully deleted", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Failed to delete directory", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "Directory does not exist", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
downloadsList.removeAll { it.title == title }
|
||||||
|
saveDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryDownload(download: Download): Boolean {
|
||||||
|
return downloadsList.contains(download)
|
||||||
|
}
|
||||||
|
|
||||||
private fun removeDirectory(download: Download) {
|
private fun removeDirectory(download: Download) {
|
||||||
val directory = if (download.type == Download.Type.MANGA) {
|
val directory = if (download.type == Download.Type.MANGA) {
|
||||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga/${download.title}/${download.chapter}")
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/${download.title}/${download.chapter}"
|
||||||
|
)
|
||||||
|
} else if (download.type == Download.Type.ANIME) {
|
||||||
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Anime/${download.title}/${download.chapter}"
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime/${download.title}/${download.chapter}")
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Novel/${download.title}/${download.chapter}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the directory exists and delete it recursively
|
// Check if the directory exists and delete it recursively
|
||||||
|
@ -67,11 +111,25 @@ class DownloadsManager(private val context: Context) {
|
||||||
|
|
||||||
fun exportDownloads(download: Download) { //copies to the downloads folder available to the user
|
fun exportDownloads(download: Download) { //copies to the downloads folder available to the user
|
||||||
val directory = if (download.type == Download.Type.MANGA) {
|
val directory = if (download.type == Download.Type.MANGA) {
|
||||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga/${download.title}/${download.chapter}")
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Manga/${download.title}/${download.chapter}"
|
||||||
|
)
|
||||||
|
} else if (download.type == Download.Type.ANIME) {
|
||||||
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Anime/${download.title}/${download.chapter}"
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime/${download.title}/${download.chapter}")
|
File(
|
||||||
|
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Novel/${download.title}/${download.chapter}"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val destination = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/${download.title}/${download.chapter}")
|
val destination = File(
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/${download.title}/${download.chapter}"
|
||||||
|
)
|
||||||
if (directory.exists()) {
|
if (directory.exists()) {
|
||||||
val copied = directory.copyRecursively(destination, true)
|
val copied = directory.copyRecursively(destination, true)
|
||||||
if (copied) {
|
if (copied) {
|
||||||
|
@ -87,8 +145,10 @@ class DownloadsManager(private val context: Context) {
|
||||||
fun purgeDownloads(type: Download.Type) {
|
fun purgeDownloads(type: Download.Type) {
|
||||||
val directory = if (type == Download.Type.MANGA) {
|
val directory = if (type == Download.Type.MANGA) {
|
||||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga")
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga")
|
||||||
} else {
|
} else if (type == Download.Type.ANIME) {
|
||||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime")
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime")
|
||||||
|
} else {
|
||||||
|
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel")
|
||||||
}
|
}
|
||||||
if (directory.exists()) {
|
if (directory.exists()) {
|
||||||
val deleted = directory.deleteRecursively()
|
val deleted = directory.deleteRecursively()
|
||||||
|
@ -105,11 +165,18 @@ class DownloadsManager(private val context: Context) {
|
||||||
saveDownloads()
|
saveDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val novelLocation = "Dantotsu/Novel"
|
||||||
|
const val mangaLocation = "Dantotsu/Manga"
|
||||||
|
const val animeLocation = "Dantotsu/Anime"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Download(val title: String, val chapter: String, val type: Type) : Serializable {
|
data class Download(val title: String, val chapter: String, val type: Type) : Serializable {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
MANGA,
|
MANGA,
|
||||||
ANIME
|
ANIME,
|
||||||
|
NOVEL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import android.content.IntentFilter
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
@ -17,24 +16,13 @@ import android.widget.Toast
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.download.Download
|
import ani.dantotsu.download.Download
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.logger
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.manga.ImageData
|
import ani.dantotsu.media.manga.ImageData
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.awaitAll
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.net.URL
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED
|
||||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FINISHED
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FINISHED
|
||||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROGRESS
|
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROGRESS
|
||||||
|
@ -44,15 +32,27 @@ import ani.dantotsu.snackString
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
import java.util.Queue
|
import java.util.Queue
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
@ -82,18 +82,27 @@ class MangaDownloaderService : Service() {
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
startForeground(NOTIFICATION_ID, builder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
startForeground(
|
||||||
|
NOTIFICATION_ID,
|
||||||
|
builder.build(),
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
startForeground(NOTIFICATION_ID, builder.build())
|
startForeground(NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
ContextCompat.registerReceiver(this, cancelReceiver, IntentFilter(ACTION_CANCEL_DOWNLOAD), ContextCompat.RECEIVER_EXPORTED)
|
ContextCompat.registerReceiver(
|
||||||
|
this,
|
||||||
|
cancelReceiver,
|
||||||
|
IntentFilter(ACTION_CANCEL_DOWNLOAD),
|
||||||
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
ServiceDataSingleton.downloadQueue.clear()
|
MangaServiceDataSingleton.downloadQueue.clear()
|
||||||
downloadJobs.clear()
|
downloadJobs.clear()
|
||||||
ServiceDataSingleton.isServiceRunning = false
|
MangaServiceDataSingleton.isServiceRunning = false
|
||||||
unregisterReceiver(cancelReceiver)
|
unregisterReceiver(cancelReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,8 +123,8 @@ class MangaDownloaderService : Service() {
|
||||||
|
|
||||||
private fun processQueue() {
|
private fun processQueue() {
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
while (ServiceDataSingleton.downloadQueue.isNotEmpty()) {
|
while (MangaServiceDataSingleton.downloadQueue.isNotEmpty()) {
|
||||||
val task = ServiceDataSingleton.downloadQueue.poll()
|
val task = MangaServiceDataSingleton.downloadQueue.poll()
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
val job = launch { download(task) }
|
val job = launch { download(task) }
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
|
@ -127,7 +136,7 @@ class MangaDownloaderService : Service() {
|
||||||
}
|
}
|
||||||
updateNotification() // Update the notification after each task is completed
|
updateNotification() // Update the notification after each task is completed
|
||||||
}
|
}
|
||||||
if (ServiceDataSingleton.downloadQueue.isEmpty()) {
|
if (MangaServiceDataSingleton.downloadQueue.isEmpty()) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
stopSelf() // Stop the service when the queue is empty
|
stopSelf() // Stop the service when the queue is empty
|
||||||
}
|
}
|
||||||
|
@ -141,7 +150,7 @@ class MangaDownloaderService : Service() {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
downloadJobs[chapter]?.cancel()
|
downloadJobs[chapter]?.cancel()
|
||||||
downloadJobs.remove(chapter)
|
downloadJobs.remove(chapter)
|
||||||
ServiceDataSingleton.downloadQueue.removeAll { it.chapter == chapter }
|
MangaServiceDataSingleton.downloadQueue.removeAll { it.chapter == chapter }
|
||||||
updateNotification() // Update the notification after cancellation
|
updateNotification() // Update the notification after cancellation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +158,7 @@ class MangaDownloaderService : Service() {
|
||||||
|
|
||||||
private fun updateNotification() {
|
private fun updateNotification() {
|
||||||
// Update the notification to reflect the current state of the queue
|
// Update the notification to reflect the current state of the queue
|
||||||
val pendingDownloads = ServiceDataSingleton.downloadQueue.size
|
val pendingDownloads = MangaServiceDataSingleton.downloadQueue.size
|
||||||
val text = if (pendingDownloads > 0) {
|
val text = if (pendingDownloads > 0) {
|
||||||
"Pending downloads: $pendingDownloads"
|
"Pending downloads: $pendingDownloads"
|
||||||
} else {
|
} else {
|
||||||
|
@ -167,6 +176,7 @@ class MangaDownloaderService : Service() {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun download(task: DownloadTask) {
|
suspend fun download(task: DownloadTask) {
|
||||||
|
try {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
ContextCompat.checkSelfPermission(
|
ContextCompat.checkSelfPermission(
|
||||||
|
@ -213,7 +223,10 @@ class MangaDownloaderService : Service() {
|
||||||
}
|
}
|
||||||
farthest++
|
farthest++
|
||||||
builder.setProgress(task.imageData.size, farthest, false)
|
builder.setProgress(task.imageData.size, farthest, false)
|
||||||
broadcastDownloadProgress(task.chapter, farthest * 100 / task.imageData.size)
|
broadcastDownloadProgress(
|
||||||
|
task.chapter,
|
||||||
|
farthest * 100 / task.imageData.size
|
||||||
|
)
|
||||||
if (notifi) {
|
if (notifi) {
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
}
|
}
|
||||||
|
@ -232,10 +245,22 @@ class MangaDownloaderService : Service() {
|
||||||
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
|
||||||
saveMediaInfo(task)
|
saveMediaInfo(task)
|
||||||
downloadsManager.addDownload(Download(task.title, task.chapter, Download.Type.MANGA))
|
downloadsManager.addDownload(
|
||||||
|
Download(
|
||||||
|
task.title,
|
||||||
|
task.chapter,
|
||||||
|
Download.Type.MANGA
|
||||||
|
)
|
||||||
|
)
|
||||||
broadcastDownloadFinished(task.chapter)
|
broadcastDownloadFinished(task.chapter)
|
||||||
snackString("${task.title} - ${task.chapter} Download finished")
|
snackString("${task.title} - ${task.chapter} Download finished")
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger("Exception while downloading file: ${e.message}")
|
||||||
|
snackString("Exception while downloading file: ${e.message}")
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
broadcastDownloadFailed(task.chapter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -296,7 +321,8 @@ class MangaDownloaderService : Service() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private suspend fun downloadImage(url: String, directory: File, name: String): String? = withContext(Dispatchers.IO) {
|
private suspend fun downloadImage(url: String, directory: File, name: String): String? =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
var connection: HttpURLConnection? = null
|
var connection: HttpURLConnection? = null
|
||||||
println("Downloading url $url")
|
println("Downloading url $url")
|
||||||
try {
|
try {
|
||||||
|
@ -316,7 +342,11 @@ class MangaDownloaderService : Service() {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
Toast.makeText(this@MangaDownloaderService, "Exception while saving ${name}: ${e.message}", Toast.LENGTH_LONG).show()
|
Toast.makeText(
|
||||||
|
this@MangaDownloaderService,
|
||||||
|
"Exception while saving ${name}: ${e.message}",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
null
|
null
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -381,10 +411,11 @@ class MangaDownloaderService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ServiceDataSingleton {
|
object MangaServiceDataSingleton {
|
||||||
var imageData: List<ImageData> = listOf()
|
var imageData: List<ImageData> = listOf()
|
||||||
var sourceMedia: Media? = null
|
var sourceMedia: Media? = null
|
||||||
var downloadQueue: Queue<MangaDownloaderService.DownloadTask> = ConcurrentLinkedQueue()
|
var downloadQueue: Queue<MangaDownloaderService.DownloadTask> = ConcurrentLinkedQueue()
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var isServiceRunning: Boolean = false
|
var isServiceRunning: Boolean = false
|
||||||
}
|
}
|
|
@ -11,8 +11,15 @@ import androidx.cardview.widget.CardView
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
|
||||||
|
|
||||||
class OfflineMangaAdapter(private val context: Context, private val items: List<OfflineMangaModel>) : BaseAdapter() {
|
class OfflineMangaAdapter(
|
||||||
private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
private val context: Context,
|
||||||
|
private var items: List<OfflineMangaModel>,
|
||||||
|
private val searchListener: OfflineMangaSearchListener
|
||||||
|
) : BaseAdapter() {
|
||||||
|
private val inflater: LayoutInflater =
|
||||||
|
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||||
|
private var originalItems: List<OfflineMangaModel> = items
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return items.size
|
return items.size
|
||||||
}
|
}
|
||||||
|
@ -49,4 +56,22 @@ class OfflineMangaAdapter(private val context: Context, private val items: List<
|
||||||
}
|
}
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onSearchQuery(query: String) {
|
||||||
|
// Implement the filtering logic here, for example:
|
||||||
|
items = if (query.isEmpty()) {
|
||||||
|
// Return the original list if the query is empty
|
||||||
|
originalItems
|
||||||
|
} else {
|
||||||
|
// Filter the list based on the query
|
||||||
|
originalItems.filter { it.title.contains(query, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
notifyDataSetChanged() // Notify the adapter that the data set has changed
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setItems(items: List<OfflineMangaModel>) {
|
||||||
|
this.items = items
|
||||||
|
this.originalItems = items
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,29 +7,25 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.OvershootInterpolator
|
import android.view.animation.OvershootInterpolator
|
||||||
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.GridView
|
import android.widget.GridView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.view.updatePaddingRelative
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
|
||||||
import ani.dantotsu.currContext
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.FragmentMangaBinding
|
|
||||||
import ani.dantotsu.download.Download
|
import ani.dantotsu.download.Download
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.logger
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.manga.MangaNameAdapter
|
|
||||||
import ani.dantotsu.navBarHeight
|
|
||||||
import ani.dantotsu.px
|
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.settings.SettingsDialogFragment
|
import ani.dantotsu.settings.SettingsDialogFragment
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
|
@ -38,23 +34,27 @@ import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.imageview.ShapeableImageView
|
import com.google.android.material.imageview.ShapeableImageView
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.io.File
|
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.InstanceCreator
|
import com.google.gson.InstanceCreator
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class OfflineMangaFragment: Fragment() {
|
class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
||||||
private val downloadManager = Injekt.get<DownloadsManager>()
|
private val downloadManager = Injekt.get<DownloadsManager>()
|
||||||
private var downloads: List<OfflineMangaModel> = listOf()
|
private var downloads: List<OfflineMangaModel> = listOf()
|
||||||
private lateinit var gridView: GridView
|
private lateinit var gridView: GridView
|
||||||
private lateinit var adapter: OfflineMangaAdapter
|
private lateinit var adapter: OfflineMangaAdapter
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_manga_offline, container, false)
|
val view = inflater.inflate(R.layout.fragment_manga_offline, container, false)
|
||||||
|
|
||||||
val textInputLayout = view.findViewById<TextInputLayout>(R.id.offlineMangaSearchBar)
|
val textInputLayout = view.findViewById<TextInputLayout>(R.id.offlineMangaSearchBar)
|
||||||
|
@ -69,23 +69,42 @@ class OfflineMangaFragment: Fragment() {
|
||||||
|
|
||||||
val animeUserAvatar = view.findViewById<ShapeableImageView>(R.id.offlineMangaUserAvatar)
|
val animeUserAvatar = view.findViewById<ShapeableImageView>(R.id.offlineMangaUserAvatar)
|
||||||
animeUserAvatar.setSafeOnClickListener {
|
animeUserAvatar.setSafeOnClickListener {
|
||||||
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.HOME).show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.HOME).show(
|
||||||
|
(it.context as AppCompatActivity).supportFragmentManager,
|
||||||
|
"dialog"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean("colorOverflow", false) ?: false
|
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
|
?.getBoolean("colorOverflow", false) ?: false
|
||||||
if (!colorOverflow) {
|
if (!colorOverflow) {
|
||||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val searchView = view.findViewById<AutoCompleteTextView>(R.id.animeSearchBarText)
|
||||||
|
searchView.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
|
onSearchQuery(s.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
gridView = view.findViewById(R.id.gridView)
|
gridView = view.findViewById(R.id.gridView)
|
||||||
getDownloads()
|
getDownloads()
|
||||||
adapter = OfflineMangaAdapter(requireContext(), downloads)
|
adapter = OfflineMangaAdapter(requireContext(), downloads, this)
|
||||||
gridView.adapter = adapter
|
gridView.adapter = adapter
|
||||||
gridView.setOnItemClickListener { parent, view, position, id ->
|
gridView.setOnItemClickListener { parent, view, position, id ->
|
||||||
// Get the OfflineMangaModel that was clicked
|
// Get the OfflineMangaModel that was clicked
|
||||||
val item = adapter.getItem(position) as OfflineMangaModel
|
val item = adapter.getItem(position) as OfflineMangaModel
|
||||||
val media = downloadManager.mangaDownloads.filter { it.title == item.title }.firstOrNull()
|
val media =
|
||||||
|
downloadManager.mangaDownloads.firstOrNull { it.title == item.title }
|
||||||
|
?: downloadManager.novelDownloads.firstOrNull { it.title == item.title }
|
||||||
media?.let {
|
media?.let {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(requireContext(), MediaDetailsActivity::class.java)
|
Intent(requireContext(), MediaDetailsActivity::class.java)
|
||||||
|
@ -97,9 +116,37 @@ class OfflineMangaFragment: Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gridView.setOnItemLongClickListener { parent, view, position, id ->
|
||||||
|
// Get the OfflineMangaModel that was clicked
|
||||||
|
val item = adapter.getItem(position) as OfflineMangaModel
|
||||||
|
val type: Download.Type = if (downloadManager.mangaDownloads.any { it.title == item.title }) {
|
||||||
|
Download.Type.MANGA
|
||||||
|
} else {
|
||||||
|
Download.Type.NOVEL
|
||||||
|
}
|
||||||
|
// Alert dialog to confirm deletion
|
||||||
|
val builder = androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup)
|
||||||
|
builder.setTitle("Delete ${item.title}?")
|
||||||
|
builder.setMessage("Are you sure you want to delete ${item.title}?")
|
||||||
|
builder.setPositiveButton("Yes") { _, _ ->
|
||||||
|
downloadManager.removeMedia(item.title, type)
|
||||||
|
getDownloads()
|
||||||
|
adapter.setItems(downloads)
|
||||||
|
}
|
||||||
|
builder.setNegativeButton("No") { _, _ ->
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
builder.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSearchQuery(query: String) {
|
||||||
|
adapter.onSearchQuery(query)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
var height = statusBarHeight
|
var height = statusBarHeight
|
||||||
|
@ -139,9 +186,7 @@ class OfflineMangaFragment: Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
getDownloads()
|
getDownloads()
|
||||||
|
@ -162,25 +207,44 @@ class OfflineMangaFragment: Fragment() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
downloads = listOf()
|
downloads = listOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDownloads() {
|
private fun getDownloads() {
|
||||||
val titles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
downloads = listOf()
|
||||||
val newDownloads = mutableListOf<OfflineMangaModel>()
|
val mangaTitles = downloadManager.mangaDownloads.map { it.title }.distinct()
|
||||||
for (title in titles) {
|
val newMangaDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
|
for (title in mangaTitles) {
|
||||||
val _downloads = downloadManager.mangaDownloads.filter { it.title == title }
|
val _downloads = downloadManager.mangaDownloads.filter { it.title == title }
|
||||||
val download = _downloads.first()
|
val download = _downloads.first()
|
||||||
val offlineMangaModel = loadOfflineMangaModel(download)
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
newDownloads += offlineMangaModel
|
newMangaDownloads += offlineMangaModel
|
||||||
}
|
}
|
||||||
downloads = newDownloads
|
downloads = newMangaDownloads
|
||||||
|
val novelTitles = downloadManager.novelDownloads.map { it.title }.distinct()
|
||||||
|
val newNovelDownloads = mutableListOf<OfflineMangaModel>()
|
||||||
|
for (title in novelTitles) {
|
||||||
|
val _downloads = downloadManager.novelDownloads.filter { it.title == title }
|
||||||
|
val download = _downloads.first()
|
||||||
|
val offlineMangaModel = loadOfflineMangaModel(download)
|
||||||
|
newNovelDownloads += offlineMangaModel
|
||||||
|
}
|
||||||
|
downloads += newNovelDownloads
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMedia(download: Download): Media? {
|
private fun getMedia(download: Download): Media? {
|
||||||
|
val type = if (download.type == Download.Type.MANGA) {
|
||||||
|
"Manga"
|
||||||
|
} else if (download.type == Download.Type.ANIME) {
|
||||||
|
"Anime"
|
||||||
|
} else {
|
||||||
|
"Novel"
|
||||||
|
}
|
||||||
val directory = File(
|
val directory = File(
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"Dantotsu/Manga/${download.title}"
|
"Dantotsu/$type/${download.title}"
|
||||||
)
|
)
|
||||||
//load media.json and convert to media class with gson
|
//load media.json and convert to media class with gson
|
||||||
try {
|
return try {
|
||||||
val gson = GsonBuilder()
|
val gson = GsonBuilder()
|
||||||
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||||
SChapterImpl() // Provide an instance of SChapterImpl
|
SChapterImpl() // Provide an instance of SChapterImpl
|
||||||
|
@ -188,20 +252,26 @@ class OfflineMangaFragment: Fragment() {
|
||||||
.create()
|
.create()
|
||||||
val media = File(directory, "media.json")
|
val media = File(directory, "media.json")
|
||||||
val mediaJson = media.readText()
|
val mediaJson = media.readText()
|
||||||
return gson.fromJson(mediaJson, Media::class.java)
|
gson.fromJson(mediaJson, Media::class.java)
|
||||||
}
|
} catch (e: Exception) {
|
||||||
catch (e: Exception){
|
|
||||||
logger("Error loading media.json: ${e.message}")
|
logger("Error loading media.json: ${e.message}")
|
||||||
logger(e.printStackTrace())
|
logger(e.printStackTrace())
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadOfflineMangaModel(download: Download): OfflineMangaModel {
|
private fun loadOfflineMangaModel(download: Download): OfflineMangaModel {
|
||||||
|
val type = if (download.type == Download.Type.MANGA) {
|
||||||
|
"Manga"
|
||||||
|
} else if (download.type == Download.Type.ANIME) {
|
||||||
|
"Anime"
|
||||||
|
} else {
|
||||||
|
"Novel"
|
||||||
|
}
|
||||||
val directory = File(
|
val directory = File(
|
||||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
"Dantotsu/Manga/${download.title}"
|
"Dantotsu/$type/${download.title}"
|
||||||
)
|
)
|
||||||
//load media.json and convert to media class with gson
|
//load media.json and convert to media class with gson
|
||||||
try {
|
try {
|
||||||
|
@ -215,13 +285,12 @@ class OfflineMangaFragment: Fragment() {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val title = mediaModel.nameMAL ?: "unknown"
|
val title = mediaModel.nameMAL ?: "unknown"
|
||||||
val score = if (mediaModel.userScore != 0) mediaModel.userScore.toString() else
|
val score = ((if (mediaModel.userScore == 0) (mediaModel.meanScore
|
||||||
if (mediaModel.meanScore == null) "?" else mediaModel.meanScore.toString()
|
?: 0) else mediaModel.userScore) / 10.0).toString()
|
||||||
val isOngoing = false
|
val isOngoing = false
|
||||||
val isUserScored = mediaModel.userScore != 0
|
val isUserScored = mediaModel.userScore != 0
|
||||||
return OfflineMangaModel(title, score, isOngoing, isUserScored, coverUri)
|
return OfflineMangaModel(title, score, isOngoing, isUserScored, coverUri)
|
||||||
}
|
} catch (e: Exception) {
|
||||||
catch (e: Exception){
|
|
||||||
logger("Error loading media.json: ${e.message}")
|
logger("Error loading media.json: ${e.message}")
|
||||||
logger(e.printStackTrace())
|
logger(e.printStackTrace())
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
@ -229,3 +298,7 @@ class OfflineMangaFragment: Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OfflineMangaSearchListener {
|
||||||
|
fun onSearchQuery(query: String)
|
||||||
|
}
|
|
@ -2,5 +2,10 @@ package ani.dantotsu.download.manga
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
|
||||||
data class OfflineMangaModel(val title: String, val score: String, val isOngoing: Boolean, val isUserScored: Boolean, val image: Uri?) {
|
data class OfflineMangaModel(
|
||||||
}
|
val title: String,
|
||||||
|
val score: String,
|
||||||
|
val isOngoing: Boolean,
|
||||||
|
val isUserScored: Boolean,
|
||||||
|
val image: Uri?
|
||||||
|
)
|
|
@ -0,0 +1,478 @@
|
||||||
|
package ani.dantotsu.download.novel
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.download.Download
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.logger
|
||||||
|
import ani.dantotsu.media.Media
|
||||||
|
import ani.dantotsu.media.novel.NovelReadFragment
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.InstanceCreator
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapterImpl
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.Request
|
||||||
|
import okio.buffer
|
||||||
|
import okio.sink
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.Queue
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
class NovelDownloaderService : Service() {
|
||||||
|
|
||||||
|
private lateinit var notificationManager: NotificationManagerCompat
|
||||||
|
private lateinit var builder: NotificationCompat.Builder
|
||||||
|
private val downloadsManager: DownloadsManager = Injekt.get<DownloadsManager>()
|
||||||
|
|
||||||
|
private val downloadJobs = mutableMapOf<String, Job>()
|
||||||
|
private val mutex = Mutex()
|
||||||
|
private var isCurrentlyProcessing = false
|
||||||
|
|
||||||
|
val networkHelper = Injekt.get<NetworkHelper>()
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
|
// This is only required for bound services.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
notificationManager = NotificationManagerCompat.from(this)
|
||||||
|
builder =
|
||||||
|
NotificationCompat.Builder(this, Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply {
|
||||||
|
setContentTitle("Novel Download Progress")
|
||||||
|
setSmallIcon(R.drawable.ic_round_download_24)
|
||||||
|
priority = NotificationCompat.PRIORITY_DEFAULT
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
setProgress(0, 0, false)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
startForeground(
|
||||||
|
NOTIFICATION_ID,
|
||||||
|
builder.build(),
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startForeground(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
ContextCompat.registerReceiver(
|
||||||
|
this,
|
||||||
|
cancelReceiver,
|
||||||
|
IntentFilter(ACTION_CANCEL_DOWNLOAD),
|
||||||
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
NovelServiceDataSingleton.downloadQueue.clear()
|
||||||
|
downloadJobs.clear()
|
||||||
|
NovelServiceDataSingleton.isServiceRunning = false
|
||||||
|
unregisterReceiver(cancelReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
snackString("Download started")
|
||||||
|
val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
|
serviceScope.launch {
|
||||||
|
mutex.withLock {
|
||||||
|
if (!isCurrentlyProcessing) {
|
||||||
|
isCurrentlyProcessing = true
|
||||||
|
processQueue()
|
||||||
|
isCurrentlyProcessing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processQueue() {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
while (NovelServiceDataSingleton.downloadQueue.isNotEmpty()) {
|
||||||
|
val task = NovelServiceDataSingleton.downloadQueue.poll()
|
||||||
|
if (task != null) {
|
||||||
|
val job = launch { download(task) }
|
||||||
|
mutex.withLock {
|
||||||
|
downloadJobs[task.chapter] = job
|
||||||
|
}
|
||||||
|
job.join() // Wait for the job to complete before continuing to the next task
|
||||||
|
mutex.withLock {
|
||||||
|
downloadJobs.remove(task.chapter)
|
||||||
|
}
|
||||||
|
updateNotification() // Update the notification after each task is completed
|
||||||
|
}
|
||||||
|
if (NovelServiceDataSingleton.downloadQueue.isEmpty()) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
stopSelf() // Stop the service when the queue is empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelDownload(chapter: String) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
mutex.withLock {
|
||||||
|
downloadJobs[chapter]?.cancel()
|
||||||
|
downloadJobs.remove(chapter)
|
||||||
|
NovelServiceDataSingleton.downloadQueue.removeAll { it.chapter == chapter }
|
||||||
|
updateNotification() // Update the notification after cancellation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNotification() {
|
||||||
|
// Update the notification to reflect the current state of the queue
|
||||||
|
val pendingDownloads = NovelServiceDataSingleton.downloadQueue.size
|
||||||
|
val text = if (pendingDownloads > 0) {
|
||||||
|
"Pending downloads: $pendingDownloads"
|
||||||
|
} else {
|
||||||
|
"All downloads completed"
|
||||||
|
}
|
||||||
|
builder.setContentText(text)
|
||||||
|
if (ActivityCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun isEpubFile(urlString: String): Boolean {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(urlString)
|
||||||
|
.head()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
networkHelper.client.newCall(request).execute().use { response ->
|
||||||
|
val contentType = response.header("Content-Type")
|
||||||
|
val contentDisposition = response.header("Content-Disposition")
|
||||||
|
|
||||||
|
logger("Content-Type: $contentType")
|
||||||
|
logger("Content-Disposition: $contentDisposition")
|
||||||
|
|
||||||
|
// Return true if the Content-Type or Content-Disposition indicates an EPUB file
|
||||||
|
contentType == "application/epub+zip" ||
|
||||||
|
(contentDisposition?.contains(".epub") == true)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger("Error checking file type: ${e.message}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isAlreadyDownloaded(urlString: String): Boolean {
|
||||||
|
return urlString.contains("file://")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun download(task: DownloadTask) {
|
||||||
|
try {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val notifi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ContextCompat.checkSelfPermission(
|
||||||
|
this@NovelDownloaderService,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastDownloadStarted(task.originalLink)
|
||||||
|
|
||||||
|
if (notifi) {
|
||||||
|
builder.setContentText("Downloading ${task.title} - ${task.chapter}")
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEpubFile(task.downloadLink)) {
|
||||||
|
if (isAlreadyDownloaded(task.originalLink)) {
|
||||||
|
logger("Already downloaded")
|
||||||
|
broadcastDownloadFinished(task.originalLink)
|
||||||
|
snackString("Already downloaded")
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
logger("Download link is not an .epub file")
|
||||||
|
broadcastDownloadFailed(task.originalLink)
|
||||||
|
snackString("Download link is not an .epub file")
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the download
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(task.downloadLink)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
networkHelper.downloadClient.newCall(request).execute().use { response ->
|
||||||
|
// Ensure the response is successful and has a body
|
||||||
|
if (!response.isSuccessful || response.body == null) {
|
||||||
|
throw IOException("Failed to download file: ${response.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(
|
||||||
|
this@NovelDownloaderService.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Novel/${task.title}/${task.chapter}/0.epub"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create directories if they don't exist
|
||||||
|
file.parentFile?.takeIf { !it.exists() }?.mkdirs()
|
||||||
|
|
||||||
|
// Overwrite existing file
|
||||||
|
if (file.exists()) file.delete()
|
||||||
|
|
||||||
|
//download cover
|
||||||
|
task.coverUrl?.let {
|
||||||
|
file.parentFile?.let { it1 -> downloadImage(it, it1, "cover.jpg") }
|
||||||
|
}
|
||||||
|
|
||||||
|
val sink = file.sink().buffer()
|
||||||
|
val responseBody = response.body
|
||||||
|
val totalBytes = responseBody.contentLength()
|
||||||
|
var downloadedBytes = 0L
|
||||||
|
|
||||||
|
val notificationUpdateInterval = 1024 * 1024 // 1 MB
|
||||||
|
val broadcastUpdateInterval = 1024 * 256 // 256 KB
|
||||||
|
var lastNotificationUpdate = 0L
|
||||||
|
var lastBroadcastUpdate = 0L
|
||||||
|
|
||||||
|
responseBody.source().use { source ->
|
||||||
|
while (true) {
|
||||||
|
val read = source.read(sink.buffer, 8192)
|
||||||
|
if (read == -1L) break
|
||||||
|
downloadedBytes += read
|
||||||
|
sink.emit()
|
||||||
|
|
||||||
|
// Update progress at intervals
|
||||||
|
if (downloadedBytes - lastNotificationUpdate >= notificationUpdateInterval) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val progress =
|
||||||
|
(downloadedBytes * 100 / totalBytes).toInt()
|
||||||
|
builder.setProgress(100, progress, false)
|
||||||
|
if (notifi) {
|
||||||
|
notificationManager.notify(
|
||||||
|
NOTIFICATION_ID,
|
||||||
|
builder.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastNotificationUpdate = downloadedBytes
|
||||||
|
}
|
||||||
|
if (downloadedBytes - lastBroadcastUpdate >= broadcastUpdateInterval) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val progress =
|
||||||
|
(downloadedBytes * 100 / totalBytes).toInt()
|
||||||
|
logger("Download progress: $progress")
|
||||||
|
broadcastDownloadProgress(task.originalLink, progress)
|
||||||
|
}
|
||||||
|
lastBroadcastUpdate = downloadedBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sink.close()
|
||||||
|
//if the file is smaller than 95% of totalBytes, it means the download was interrupted
|
||||||
|
if (file.length() < totalBytes * 0.95) {
|
||||||
|
throw IOException("Failed to download file: ${response.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger("Exception while downloading .epub inside request: ${e.message}")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update notification for download completion
|
||||||
|
builder.setContentText("${task.title} - ${task.chapter} Download complete")
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
if (notifi) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
saveMediaInfo(task)
|
||||||
|
downloadsManager.addDownload(
|
||||||
|
Download(
|
||||||
|
task.title,
|
||||||
|
task.chapter,
|
||||||
|
Download.Type.NOVEL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
broadcastDownloadFinished(task.originalLink)
|
||||||
|
snackString("${task.title} - ${task.chapter} Download finished")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger("Exception while downloading .epub: ${e.message}")
|
||||||
|
snackString("Exception while downloading .epub: ${e.message}")
|
||||||
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
broadcastDownloadFailed(task.originalLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveMediaInfo(task: DownloadTask) {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val directory = File(
|
||||||
|
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"Dantotsu/Novel/${task.title}"
|
||||||
|
)
|
||||||
|
if (!directory.exists()) directory.mkdirs()
|
||||||
|
|
||||||
|
val file = File(directory, "media.json")
|
||||||
|
val gson = GsonBuilder()
|
||||||
|
.registerTypeAdapter(SChapter::class.java, InstanceCreator<SChapter> {
|
||||||
|
SChapterImpl() // Provide an instance of SChapterImpl
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
val mediaJson = gson.toJson(task.sourceMedia)
|
||||||
|
val media = gson.fromJson(mediaJson, Media::class.java)
|
||||||
|
if (media != null) {
|
||||||
|
media.cover = media.cover?.let { downloadImage(it, directory, "cover.jpg") }
|
||||||
|
media.banner = media.banner?.let { downloadImage(it, directory, "banner.jpg") }
|
||||||
|
|
||||||
|
val jsonString = gson.toJson(media)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
file.writeText(jsonString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private suspend fun downloadImage(url: String, directory: File, name: String): String? =
|
||||||
|
withContext(
|
||||||
|
Dispatchers.IO
|
||||||
|
) {
|
||||||
|
var connection: HttpURLConnection? = null
|
||||||
|
println("Downloading url $url")
|
||||||
|
try {
|
||||||
|
connection = URL(url).openConnection() as HttpURLConnection
|
||||||
|
connection.connect()
|
||||||
|
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
throw Exception("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(directory, name)
|
||||||
|
FileOutputStream(file).use { output ->
|
||||||
|
connection.inputStream.use { input ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@withContext file.absolutePath
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
this@NovelDownloaderService,
|
||||||
|
"Exception while saving ${name}: ${e.message}",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
null
|
||||||
|
} finally {
|
||||||
|
connection?.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadStarted(link: String) {
|
||||||
|
val intent = Intent(NovelReadFragment.ACTION_DOWNLOAD_STARTED).apply {
|
||||||
|
putExtra(NovelReadFragment.EXTRA_NOVEL_LINK, link)
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadFinished(link: String) {
|
||||||
|
val intent = Intent(NovelReadFragment.ACTION_DOWNLOAD_FINISHED).apply {
|
||||||
|
putExtra(NovelReadFragment.EXTRA_NOVEL_LINK, link)
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadFailed(link: String) {
|
||||||
|
val intent = Intent(NovelReadFragment.ACTION_DOWNLOAD_FAILED).apply {
|
||||||
|
putExtra(NovelReadFragment.EXTRA_NOVEL_LINK, link)
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun broadcastDownloadProgress(link: String, progress: Int) {
|
||||||
|
val intent = Intent(NovelReadFragment.ACTION_DOWNLOAD_PROGRESS).apply {
|
||||||
|
putExtra(NovelReadFragment.EXTRA_NOVEL_LINK, link)
|
||||||
|
putExtra("progress", progress)
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cancelReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action == ACTION_CANCEL_DOWNLOAD) {
|
||||||
|
val chapter = intent.getStringExtra(EXTRA_CHAPTER)
|
||||||
|
chapter?.let {
|
||||||
|
cancelDownload(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class DownloadTask(
|
||||||
|
val title: String,
|
||||||
|
val chapter: String,
|
||||||
|
val downloadLink: String,
|
||||||
|
val originalLink: String,
|
||||||
|
val sourceMedia: Media? = null,
|
||||||
|
val coverUrl: String? = null,
|
||||||
|
val retries: Int = 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val NOTIFICATION_ID = 1103
|
||||||
|
const val ACTION_CANCEL_DOWNLOAD = "action_cancel_download"
|
||||||
|
const val EXTRA_CHAPTER = "extra_chapter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object NovelServiceDataSingleton {
|
||||||
|
var sourceMedia: Media? = null
|
||||||
|
var downloadQueue: Queue<NovelDownloaderService.DownloadTask> = ConcurrentLinkedQueue()
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var isServiceRunning: Boolean = false
|
||||||
|
}
|
|
@ -40,7 +40,8 @@ object Helper {
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) {
|
fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) {
|
||||||
val dataSourceFactory = DataSource.Factory {
|
val dataSourceFactory = DataSource.Factory {
|
||||||
val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
val dataSource: HttpDataSource =
|
||||||
|
OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
||||||
defaultHeaders.forEach {
|
defaultHeaders.forEach {
|
||||||
dataSource.setRequestProperty(it.key, it.value)
|
dataSource.setRequestProperty(it.key, it.value)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +82,8 @@ object Helper {
|
||||||
)
|
)
|
||||||
downloadHelper.prepare(object : DownloadHelper.Callback {
|
downloadHelper.prepare(object : DownloadHelper.Callback {
|
||||||
override fun onPrepared(helper: DownloadHelper) {
|
override fun onPrepared(helper: DownloadHelper) {
|
||||||
TrackSelectionDialogBuilder(context,"Select thingy",helper.getTracks(0).groups
|
TrackSelectionDialogBuilder(
|
||||||
|
context, "Select thingy", helper.getTracks(0).groups
|
||||||
) { _, overrides ->
|
) { _, overrides ->
|
||||||
val params = TrackSelectionParameters.Builder(context)
|
val params = TrackSelectionParameters.Builder(context)
|
||||||
overrides.forEach {
|
overrides.forEach {
|
||||||
|
@ -124,7 +126,8 @@ object Helper {
|
||||||
//val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
//val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
||||||
val networkHelper = Injekt.get<NetworkHelper>()
|
val networkHelper = Injekt.get<NetworkHelper>()
|
||||||
val okHttpClient = networkHelper.client
|
val okHttpClient = networkHelper.client
|
||||||
val dataSource: HttpDataSource = OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
val dataSource: HttpDataSource =
|
||||||
|
OkHttpDataSource.Factory(okHttpClient).createDataSource()
|
||||||
defaultHeaders.forEach {
|
defaultHeaders.forEach {
|
||||||
dataSource.setRequestProperty(it.key, it.value)
|
dataSource.setRequestProperty(it.key, it.value)
|
||||||
}
|
}
|
||||||
|
@ -137,7 +140,8 @@ object Helper {
|
||||||
dataSourceFactory,
|
dataSourceFactory,
|
||||||
Executor(Runnable::run)
|
Executor(Runnable::run)
|
||||||
).apply {
|
).apply {
|
||||||
requirements = Requirements(Requirements.NETWORK or Requirements.DEVICE_STORAGE_NOT_LOW)
|
requirements =
|
||||||
|
Requirements(Requirements.NETWORK or Requirements.DEVICE_STORAGE_NOT_LOW)
|
||||||
maxParallelDownloads = 3
|
maxParallelDownloads = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,10 @@ class MyDownloadService : DownloadService(1, 1, "download_service", R.string.dow
|
||||||
|
|
||||||
override fun getScheduler(): Scheduler = PlatformScheduler(this, JOB_ID)
|
override fun getScheduler(): Scheduler = PlatformScheduler(this, JOB_ID)
|
||||||
|
|
||||||
override fun getForegroundNotification(downloads: MutableList<Download>, notMetRequirements: Int): Notification =
|
override fun getForegroundNotification(
|
||||||
|
downloads: MutableList<Download>,
|
||||||
|
notMetRequirements: Int
|
||||||
|
): Notification =
|
||||||
DownloadNotificationHelper(this, "download_service").buildProgressNotification(
|
DownloadNotificationHelper(this, "download_service").buildProgressNotification(
|
||||||
this,
|
this,
|
||||||
R.drawable.mono,
|
R.drawable.mono,
|
||||||
|
|
|
@ -48,7 +48,8 @@ class AnimeFragment : Fragment() {
|
||||||
private var _binding: FragmentAnimeBinding? = null
|
private var _binding: FragmentAnimeBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private var uiSettings: UserInterfaceSettings = loadData("ui_settings") ?: UserInterfaceSettings()
|
private var uiSettings: UserInterfaceSettings =
|
||||||
|
loadData("ui_settings") ?: UserInterfaceSettings()
|
||||||
|
|
||||||
val model: AnilistAnimeViewModel by activityViewModels()
|
val model: AnilistAnimeViewModel by activityViewModels()
|
||||||
|
|
||||||
|
@ -224,7 +225,8 @@ class AnimeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.animePageScrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
binding.animePageScrollTop.translationY =
|
||||||
|
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package ani.dantotsu.home
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
|
@ -18,7 +17,6 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.media.GenreActivity
|
|
||||||
import ani.dantotsu.MediaPageTransformer
|
import ani.dantotsu.MediaPageTransformer
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
@ -27,6 +25,7 @@ import ani.dantotsu.databinding.ItemAnimePageBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.media.CalendarActivity
|
import ani.dantotsu.media.CalendarActivity
|
||||||
|
import ani.dantotsu.media.GenreActivity
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.SearchActivity
|
import ani.dantotsu.media.SearchActivity
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
|
@ -45,10 +44,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
||||||
private var trendHandler: Handler? = null
|
private var trendHandler: Handler? = null
|
||||||
private lateinit var trendRun: Runnable
|
private lateinit var trendRun: Runnable
|
||||||
var trendingViewPager: ViewPager2? = null
|
var trendingViewPager: ViewPager2? = null
|
||||||
private var uiSettings: UserInterfaceSettings = loadData("ui_settings") ?: UserInterfaceSettings()
|
private var uiSettings: UserInterfaceSettings =
|
||||||
|
loadData("ui_settings") ?: UserInterfaceSettings()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimePageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimePageViewHolder {
|
||||||
val binding = ItemAnimePageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemAnimePageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return AnimePageViewHolder(binding)
|
return AnimePageViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,14 +61,16 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
||||||
val currentColor = textInputLayout.boxBackgroundColor
|
val currentColor = textInputLayout.boxBackgroundColor
|
||||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
||||||
textInputLayout.boxBackgroundColor = semiTransparentColor
|
textInputLayout.boxBackgroundColor = semiTransparentColor
|
||||||
val materialCardView = holder.itemView.findViewById<MaterialCardView>(R.id.animeUserAvatarContainer)
|
val materialCardView =
|
||||||
|
holder.itemView.findViewById<MaterialCardView>(R.id.animeUserAvatarContainer)
|
||||||
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||||
val color = typedValue.data
|
val color = typedValue.data
|
||||||
|
|
||||||
|
|
||||||
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean("colorOverflow", false) ?: false
|
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
|
?.getBoolean("colorOverflow", false) ?: false
|
||||||
if (!colorOverflow) {
|
if (!colorOverflow) {
|
||||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||||
|
@ -95,7 +98,10 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeUserAvatar.setSafeOnClickListener {
|
binding.animeUserAvatar.setSafeOnClickListener {
|
||||||
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.ANIME).show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.ANIME).show(
|
||||||
|
(it.context as AppCompatActivity).supportFragmentManager,
|
||||||
|
"dialog"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -125,7 +131,8 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeIncludeList.visibility = if(Anilist.userid!=null) View.VISIBLE else View.GONE
|
binding.animeIncludeList.visibility =
|
||||||
|
if (Anilist.userid != null) View.VISIBLE else View.GONE
|
||||||
binding.animeIncludeList.setOnCheckedChangeListener { _, isChecked ->
|
binding.animeIncludeList.setOnCheckedChangeListener { _, isChecked ->
|
||||||
onIncludeListClick.invoke(isChecked)
|
onIncludeListClick.invoke(isChecked)
|
||||||
}
|
}
|
||||||
|
@ -152,7 +159,8 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
||||||
|
|
||||||
trendHandler = Handler(Looper.getMainLooper())
|
trendHandler = Handler(Looper.getMainLooper())
|
||||||
trendRun = Runnable {
|
trendRun = Runnable {
|
||||||
binding.animeTrendingViewPager.currentItem = binding.animeTrendingViewPager.currentItem + 1
|
binding.animeTrendingViewPager.currentItem =
|
||||||
|
binding.animeTrendingViewPager.currentItem + 1
|
||||||
}
|
}
|
||||||
binding.animeTrendingViewPager.registerOnPageChangeCallback(
|
binding.animeTrendingViewPager.registerOnPageChangeCallback(
|
||||||
object : ViewPager2.OnPageChangeCallback() {
|
object : ViewPager2.OnPageChangeCallback() {
|
||||||
|
@ -164,22 +172,30 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.animeTrendingViewPager.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
binding.animeTrendingViewPager.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
binding.animeTitleContainer.startAnimation(setSlideUp(uiSettings))
|
binding.animeTitleContainer.startAnimation(setSlideUp(uiSettings))
|
||||||
binding.animeListContainer.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
binding.animeListContainer.layoutAnimation =
|
||||||
binding.animeSeasonsCont.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
|
binding.animeSeasonsCont.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateRecent(adaptor: MediaAdaptor) {
|
fun updateRecent(adaptor: MediaAdaptor) {
|
||||||
binding.animeUpdatedProgressBar.visibility = View.GONE
|
binding.animeUpdatedProgressBar.visibility = View.GONE
|
||||||
binding.animeUpdatedRecyclerView.adapter = adaptor
|
binding.animeUpdatedRecyclerView.adapter = adaptor
|
||||||
binding.animeUpdatedRecyclerView.layoutManager =
|
binding.animeUpdatedRecyclerView.layoutManager =
|
||||||
LinearLayoutManager(binding.animeUpdatedRecyclerView.context, LinearLayoutManager.HORIZONTAL, false)
|
LinearLayoutManager(
|
||||||
|
binding.animeUpdatedRecyclerView.context,
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
binding.animeUpdatedRecyclerView.visibility = View.VISIBLE
|
binding.animeUpdatedRecyclerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
binding.animeRecently.visibility = View.VISIBLE
|
binding.animeRecently.visibility = View.VISIBLE
|
||||||
binding.animeRecently.startAnimation(setSlideUp(uiSettings))
|
binding.animeRecently.startAnimation(setSlideUp(uiSettings))
|
||||||
binding.animeUpdatedRecyclerView.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
binding.animeUpdatedRecyclerView.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
binding.animePopular.visibility = View.VISIBLE
|
binding.animePopular.visibility = View.VISIBLE
|
||||||
binding.animePopular.startAnimation(setSlideUp(uiSettings))
|
binding.animePopular.startAnimation(setSlideUp(uiSettings))
|
||||||
}
|
}
|
||||||
|
@ -191,5 +207,6 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class AnimePageViewHolder(val binding: ItemAnimePageBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class AnimePageViewHolder(val binding: ItemAnimePageBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,13 @@ import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.settings.SettingsDialogFragment
|
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
|
||||||
import ani.dantotsu.media.user.ListActivity
|
import ani.dantotsu.media.user.ListActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.setSlideIn
|
import ani.dantotsu.setSlideIn
|
||||||
import ani.dantotsu.setSlideUp
|
import ani.dantotsu.setSlideUp
|
||||||
|
import ani.dantotsu.settings.SettingsDialogFragment
|
||||||
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -52,7 +52,11 @@ class HomeFragment : Fragment() {
|
||||||
private var _binding: FragmentHomeBinding? = null
|
private var _binding: FragmentHomeBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -96,10 +100,12 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings))
|
binding.homeUserAvatarContainer.startAnimation(setSlideUp(uiSettings))
|
||||||
binding.homeUserDataContainer.visibility = View.VISIBLE
|
binding.homeUserDataContainer.visibility = View.VISIBLE
|
||||||
binding.homeUserDataContainer.layoutAnimation = LayoutAnimationController(setSlideUp(uiSettings), 0.25f)
|
binding.homeUserDataContainer.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideUp(uiSettings), 0.25f)
|
||||||
binding.homeAnimeList.visibility = View.VISIBLE
|
binding.homeAnimeList.visibility = View.VISIBLE
|
||||||
binding.homeMangaList.visibility = View.VISIBLE
|
binding.homeMangaList.visibility = View.VISIBLE
|
||||||
binding.homeListContainer.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
binding.homeListContainer.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
snackString(currContext()?.getString(R.string.please_reload))
|
snackString(currContext()?.getString(R.string.please_reload))
|
||||||
|
@ -107,7 +113,10 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.homeUserAvatarContainer.setSafeOnClickListener {
|
binding.homeUserAvatarContainer.setSafeOnClickListener {
|
||||||
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.HOME).show(parentFragmentManager, "dialog")
|
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.HOME).show(
|
||||||
|
parentFragmentManager,
|
||||||
|
"dialog"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.homeContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.homeContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
@ -123,11 +132,13 @@ class HomeFragment : Fragment() {
|
||||||
if (!binding.homeScroll.canScrollVertically(1)) {
|
if (!binding.homeScroll.canScrollVertically(1)) {
|
||||||
reached = true
|
reached = true
|
||||||
bottomBar.animate().translationZ(0f).setDuration(duration).start()
|
bottomBar.animate().translationZ(0f).setDuration(duration).start()
|
||||||
ObjectAnimator.ofFloat(bottomBar, "elevation", 4f, 0f).setDuration(duration).start()
|
ObjectAnimator.ofFloat(bottomBar, "elevation", 4f, 0f).setDuration(duration)
|
||||||
|
.start()
|
||||||
} else {
|
} else {
|
||||||
if (reached) {
|
if (reached) {
|
||||||
bottomBar.animate().translationZ(12f).setDuration(duration).start()
|
bottomBar.animate().translationZ(12f).setDuration(duration).start()
|
||||||
ObjectAnimator.ofFloat(bottomBar, "elevation", 0f, 4f).setDuration(duration).start()
|
ObjectAnimator.ofFloat(bottomBar, "elevation", 0f, 4f).setDuration(duration)
|
||||||
|
.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +149,13 @@ class HomeFragment : Fragment() {
|
||||||
if (displayCutout != null) {
|
if (displayCutout != null) {
|
||||||
if (displayCutout.boundingRects.size > 0) {
|
if (displayCutout.boundingRects.size > 0) {
|
||||||
height =
|
height =
|
||||||
max(statusBarHeight, min(displayCutout.boundingRects[0].width(), displayCutout.boundingRects[0].height()))
|
max(
|
||||||
|
statusBarHeight,
|
||||||
|
min(
|
||||||
|
displayCutout.boundingRects[0].width(),
|
||||||
|
displayCutout.boundingRects[0].height()
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,7 +206,8 @@ class HomeFragment : Fragment() {
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
recyclerView.visibility = View.VISIBLE
|
recyclerView.visibility = View.VISIBLE
|
||||||
recyclerView.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
recyclerView.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
empty.visibility = View.VISIBLE
|
empty.visibility = View.VISIBLE
|
||||||
|
@ -313,7 +331,8 @@ class HomeFragment : Fragment() {
|
||||||
live.observe(viewLifecycleOwner) {
|
live.observe(viewLifecycleOwner) {
|
||||||
if (it) {
|
if (it) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
uiSettings =
|
||||||
|
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
//Get userData First
|
//Get userData First
|
||||||
getUserId(requireContext()) {
|
getUserId(requireContext()) {
|
||||||
|
|
|
@ -15,7 +15,11 @@ class LoginFragment : Fragment() {
|
||||||
private var _binding: FragmentLoginBinding? = null
|
private var _binding: FragmentLoginBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = FragmentLoginBinding.inflate(layoutInflater, container, false)
|
_binding = FragmentLoginBinding.inflate(layoutInflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,11 +44,16 @@ class MangaFragment : Fragment() {
|
||||||
private var _binding: FragmentMangaBinding? = null
|
private var _binding: FragmentMangaBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private var uiSettings: UserInterfaceSettings = loadData("ui_settings") ?: UserInterfaceSettings()
|
private var uiSettings: UserInterfaceSettings =
|
||||||
|
loadData("ui_settings") ?: UserInterfaceSettings()
|
||||||
|
|
||||||
val model: AnilistMangaViewModel by activityViewModels()
|
val model: AnilistMangaViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = FragmentMangaBinding.inflate(inflater, container, false)
|
_binding = FragmentMangaBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -100,7 +105,8 @@ class MangaFragment : Fragment() {
|
||||||
}
|
}
|
||||||
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
|
val popularAdaptor = MediaAdaptor(1, model.searchResults.results, requireActivity())
|
||||||
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
val progressAdaptor = ProgressAdapter(searched = model.searched)
|
||||||
binding.mangaPageRecyclerView.adapter = ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
|
binding.mangaPageRecyclerView.adapter =
|
||||||
|
ConcatAdapter(mangaPageAdapter, popularAdaptor, progressAdaptor)
|
||||||
val layout = LinearLayoutManager(requireContext())
|
val layout = LinearLayoutManager(requireContext())
|
||||||
binding.mangaPageRecyclerView.layoutManager = layout
|
binding.mangaPageRecyclerView.layoutManager = layout
|
||||||
|
|
||||||
|
@ -177,7 +183,8 @@ class MangaFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.mangaPageScrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
binding.mangaPageScrollTop.translationY =
|
||||||
|
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.media.GenreActivity
|
|
||||||
import ani.dantotsu.MediaPageTransformer
|
import ani.dantotsu.MediaPageTransformer
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
@ -25,6 +24,7 @@ import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.ItemMangaPageBinding
|
import ani.dantotsu.databinding.ItemMangaPageBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.media.GenreActivity
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.SearchActivity
|
import ani.dantotsu.media.SearchActivity
|
||||||
import ani.dantotsu.px
|
import ani.dantotsu.px
|
||||||
|
@ -43,10 +43,12 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
||||||
private var trendHandler: Handler? = null
|
private var trendHandler: Handler? = null
|
||||||
private lateinit var trendRun: Runnable
|
private lateinit var trendRun: Runnable
|
||||||
var trendingViewPager: ViewPager2? = null
|
var trendingViewPager: ViewPager2? = null
|
||||||
private var uiSettings: UserInterfaceSettings = loadData("ui_settings") ?: UserInterfaceSettings()
|
private var uiSettings: UserInterfaceSettings =
|
||||||
|
loadData("ui_settings") ?: UserInterfaceSettings()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaPageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MangaPageViewHolder {
|
||||||
val binding = ItemMangaPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemMangaPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return MangaPageViewHolder(binding)
|
return MangaPageViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,14 +60,16 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
||||||
val currentColor = textInputLayout.boxBackgroundColor
|
val currentColor = textInputLayout.boxBackgroundColor
|
||||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
||||||
textInputLayout.boxBackgroundColor = semiTransparentColor
|
textInputLayout.boxBackgroundColor = semiTransparentColor
|
||||||
val materialCardView = holder.itemView.findViewById<MaterialCardView>(R.id.mangaUserAvatarContainer)
|
val materialCardView =
|
||||||
|
holder.itemView.findViewById<MaterialCardView>(R.id.mangaUserAvatarContainer)
|
||||||
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||||
val color = typedValue.data
|
val color = typedValue.data
|
||||||
|
|
||||||
|
|
||||||
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean("colorOverflow", false) ?: false
|
val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
|
||||||
|
?.getBoolean("colorOverflow", false) ?: false
|
||||||
if (!colorOverflow) {
|
if (!colorOverflow) {
|
||||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||||
|
@ -89,7 +93,10 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.mangaUserAvatar.setSafeOnClickListener {
|
binding.mangaUserAvatar.setSafeOnClickListener {
|
||||||
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.MANGA).show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
SettingsDialogFragment(SettingsDialogFragment.Companion.PageType.MANGA).show(
|
||||||
|
(it.context as AppCompatActivity).supportFragmentManager,
|
||||||
|
"dialog"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.mangaSearchBar.setEndIconOnClickListener {
|
binding.mangaSearchBar.setEndIconOnClickListener {
|
||||||
|
@ -117,7 +124,8 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.mangaIncludeList.visibility = if(Anilist.userid!=null) View.VISIBLE else View.GONE
|
binding.mangaIncludeList.visibility =
|
||||||
|
if (Anilist.userid != null) View.VISIBLE else View.GONE
|
||||||
binding.mangaIncludeList.setOnCheckedChangeListener { _, isChecked ->
|
binding.mangaIncludeList.setOnCheckedChangeListener { _, isChecked ->
|
||||||
onIncludeListClick.invoke(isChecked)
|
onIncludeListClick.invoke(isChecked)
|
||||||
}
|
}
|
||||||
|
@ -142,7 +150,8 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
||||||
binding.mangaTrendingViewPager.setPageTransformer(MediaPageTransformer())
|
binding.mangaTrendingViewPager.setPageTransformer(MediaPageTransformer())
|
||||||
trendHandler = Handler(Looper.getMainLooper())
|
trendHandler = Handler(Looper.getMainLooper())
|
||||||
trendRun = Runnable {
|
trendRun = Runnable {
|
||||||
binding.mangaTrendingViewPager.currentItem = binding.mangaTrendingViewPager.currentItem + 1
|
binding.mangaTrendingViewPager.currentItem =
|
||||||
|
binding.mangaTrendingViewPager.currentItem + 1
|
||||||
}
|
}
|
||||||
binding.mangaTrendingViewPager.registerOnPageChangeCallback(
|
binding.mangaTrendingViewPager.registerOnPageChangeCallback(
|
||||||
object : ViewPager2.OnPageChangeCallback() {
|
object : ViewPager2.OnPageChangeCallback() {
|
||||||
|
@ -154,21 +163,28 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.mangaTrendingViewPager.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
binding.mangaTrendingViewPager.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
binding.mangaTitleContainer.startAnimation(setSlideUp(uiSettings))
|
binding.mangaTitleContainer.startAnimation(setSlideUp(uiSettings))
|
||||||
binding.mangaListContainer.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
binding.mangaListContainer.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateNovel(adaptor: MediaAdaptor) {
|
fun updateNovel(adaptor: MediaAdaptor) {
|
||||||
binding.mangaNovelProgressBar.visibility = View.GONE
|
binding.mangaNovelProgressBar.visibility = View.GONE
|
||||||
binding.mangaNovelRecyclerView.adapter = adaptor
|
binding.mangaNovelRecyclerView.adapter = adaptor
|
||||||
binding.mangaNovelRecyclerView.layoutManager =
|
binding.mangaNovelRecyclerView.layoutManager =
|
||||||
LinearLayoutManager(binding.mangaNovelRecyclerView.context, LinearLayoutManager.HORIZONTAL, false)
|
LinearLayoutManager(
|
||||||
|
binding.mangaNovelRecyclerView.context,
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
binding.mangaNovelRecyclerView.visibility = View.VISIBLE
|
binding.mangaNovelRecyclerView.visibility = View.VISIBLE
|
||||||
|
|
||||||
binding.mangaNovel.visibility = View.VISIBLE
|
binding.mangaNovel.visibility = View.VISIBLE
|
||||||
binding.mangaNovel.startAnimation(setSlideUp(uiSettings))
|
binding.mangaNovel.startAnimation(setSlideUp(uiSettings))
|
||||||
binding.mangaNovelRecyclerView.layoutAnimation = LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
binding.mangaNovelRecyclerView.layoutAnimation =
|
||||||
|
LayoutAnimationController(setSlideIn(uiSettings), 0.25f)
|
||||||
binding.mangaPopular.visibility = View.VISIBLE
|
binding.mangaPopular.visibility = View.VISIBLE
|
||||||
binding.mangaPopular.startAnimation(setSlideUp(uiSettings))
|
binding.mangaPopular.startAnimation(setSlideUp(uiSettings))
|
||||||
}
|
}
|
||||||
|
@ -180,5 +196,6 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class MangaPageViewHolder(val binding: ItemMangaPageBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class MangaPageViewHolder(val binding: ItemMangaPageBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,16 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.ZoomOutPageTransformer
|
import ani.dantotsu.ZoomOutPageTransformer
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
|
||||||
import ani.dantotsu.databinding.ActivityNoInternetBinding
|
import ani.dantotsu.databinding.ActivityNoInternetBinding
|
||||||
import ani.dantotsu.download.manga.OfflineMangaFragment
|
import ani.dantotsu.download.manga.OfflineMangaFragment
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.isOnline
|
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.offline.OfflineFragment
|
import ani.dantotsu.offline.OfflineFragment
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.selectedOption
|
import ani.dantotsu.selectedOption
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.startMainActivity
|
|
||||||
import ani.dantotsu.statusBarHeight
|
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
|
|
||||||
class NoInternet : AppCompatActivity() {
|
class NoInternet : AppCompatActivity() {
|
||||||
|
|
|
@ -12,11 +12,17 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.EmptyAdapter
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityAuthorBinding
|
import ani.dantotsu.databinding.ActivityAuthorBinding
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.others.LangSet
|
||||||
|
import ani.dantotsu.others.getSerialized
|
||||||
|
import ani.dantotsu.px
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
|
@ -16,9 +16,9 @@ import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityListBinding
|
import ani.dantotsu.databinding.ActivityListBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.media.user.ListViewPagerAdapter
|
import ani.dantotsu.media.user.ListViewPagerAdapter
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -43,7 +43,11 @@ ThemeManager(this).applyTheme()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true)
|
||||||
val primaryColor = typedValue.data
|
val primaryColor = typedValue.data
|
||||||
val typedValue2 = TypedValue()
|
val typedValue2 = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorOnBackground, typedValue2, true)
|
theme.resolveAttribute(
|
||||||
|
com.google.android.material.R.attr.colorOnBackground,
|
||||||
|
typedValue2,
|
||||||
|
true
|
||||||
|
)
|
||||||
val titleTextColor = typedValue2.data
|
val titleTextColor = typedValue2.data
|
||||||
val typedValue3 = TypedValue()
|
val typedValue3 = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue3, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue3, true)
|
||||||
|
@ -56,7 +60,7 @@ ThemeManager(this).applyTheme()
|
||||||
window.navigationBarColor = primaryColor
|
window.navigationBarColor = primaryColor
|
||||||
binding.listTabLayout.setBackgroundColor(primaryColor)
|
binding.listTabLayout.setBackgroundColor(primaryColor)
|
||||||
binding.listAppBar.setBackgroundColor(primaryColor)
|
binding.listAppBar.setBackgroundColor(primaryColor)
|
||||||
binding.listTitle.setTextColor(titleTextColor)
|
binding.listTitle.setTextColor(primaryTextColor)
|
||||||
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
|
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
|
||||||
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
||||||
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||||
|
@ -68,7 +72,10 @@ ThemeManager(this).applyTheme()
|
||||||
} else {
|
} else {
|
||||||
binding.root.fitsSystemWindows = false
|
binding.root.fitsSystemWindows = false
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
window.setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||||
|
)
|
||||||
}
|
}
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
@ -79,6 +86,7 @@ ThemeManager(this).applyTheme()
|
||||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||||
this@CalendarActivity.selectedTabIdx = tab?.position ?: 1
|
this@CalendarActivity.selectedTabIdx = tab?.position ?: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||||
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,11 +21,13 @@ class CharacterAdapter(
|
||||||
private val characterList: ArrayList<Character>
|
private val characterList: ArrayList<Character>
|
||||||
) : RecyclerView.Adapter<CharacterAdapter.CharacterViewHolder>() {
|
) : RecyclerView.Adapter<CharacterAdapter.CharacterViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
|
||||||
val binding = ItemCharacterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemCharacterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return CharacterViewHolder(binding)
|
return CharacterViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
private val uiSettings =
|
||||||
|
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
||||||
|
@ -38,16 +40,23 @@ class CharacterAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = characterList.size
|
override fun getItemCount(): Int = characterList.size
|
||||||
inner class CharacterViewHolder(val binding: ItemCharacterBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class CharacterViewHolder(val binding: ItemCharacterBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
val char = characterList[bindingAdapterPosition]
|
val char = characterList[bindingAdapterPosition]
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
itemView.context,
|
itemView.context,
|
||||||
Intent(itemView.context, CharacterDetailsActivity::class.java).putExtra("character", char as Serializable),
|
Intent(
|
||||||
|
itemView.context,
|
||||||
|
CharacterDetailsActivity::class.java
|
||||||
|
).putExtra("character", char as Serializable),
|
||||||
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
itemView.context as Activity,
|
itemView.context as Activity,
|
||||||
Pair.create(binding.itemCompactImage, ViewCompat.getTransitionName(binding.itemCompactImage)!!),
|
Pair.create(
|
||||||
|
binding.itemCompactImage,
|
||||||
|
ViewCompat.getTransitionName(binding.itemCompactImage)!!
|
||||||
|
),
|
||||||
).toBundle()
|
).toBundle()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,20 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityCharacterBinding
|
import ani.dantotsu.databinding.ActivityCharacterBinding
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
|
import ani.dantotsu.loadData
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
import ani.dantotsu.others.getSerialized
|
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.others.LangSet
|
||||||
|
import ani.dantotsu.others.getSerialized
|
||||||
|
import ani.dantotsu.px
|
||||||
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -42,9 +49,11 @@ ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
initActivity(this)
|
initActivity(this)
|
||||||
screenWidth = resources.displayMetrics.run { widthPixels / density }
|
screenWidth = resources.displayMetrics.run { widthPixels / density }
|
||||||
if (uiSettings.immersiveMode) this.window.statusBarColor = ContextCompat.getColor(this, R.color.status)
|
if (uiSettings.immersiveMode) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.status)
|
||||||
|
|
||||||
val banner = if (uiSettings.bannerAnimations) binding.characterBanner else binding.characterBannerNoKen
|
val banner =
|
||||||
|
if (uiSettings.bannerAnimations) binding.characterBanner else binding.characterBannerNoKen
|
||||||
|
|
||||||
banner.updateLayoutParams { height += statusBarHeight }
|
banner.updateLayoutParams { height += statusBarHeight }
|
||||||
binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
binding.characterClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||||
|
@ -61,7 +70,13 @@ ThemeManager(this).applyTheme()
|
||||||
binding.characterTitle.text = character.name
|
binding.characterTitle.text = character.name
|
||||||
banner.loadImage(character.banner)
|
banner.loadImage(character.banner)
|
||||||
binding.characterCoverImage.loadImage(character.image)
|
binding.characterCoverImage.loadImage(character.image)
|
||||||
binding.characterCoverImage.setOnLongClickListener { ImageViewDialog.newInstance(this, character.name, character.image) }
|
binding.characterCoverImage.setOnLongClickListener {
|
||||||
|
ImageViewDialog.newInstance(
|
||||||
|
this,
|
||||||
|
character.name,
|
||||||
|
character.image
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
model.getCharacter().observe(this) {
|
model.getCharacter().observe(this) {
|
||||||
if (it != null && !loaded) {
|
if (it != null && !loaded) {
|
||||||
|
@ -73,7 +88,8 @@ ThemeManager(this).applyTheme()
|
||||||
val roles = character.roles
|
val roles = character.roles
|
||||||
if (roles != null) {
|
if (roles != null) {
|
||||||
val mediaAdaptor = MediaAdaptor(0, roles, this, matchParent = true)
|
val mediaAdaptor = MediaAdaptor(0, roles, this, matchParent = true)
|
||||||
val concatAdaptor = ConcatAdapter(CharacterDetailsAdapter(character, this), mediaAdaptor)
|
val concatAdaptor =
|
||||||
|
ConcatAdapter(CharacterDetailsAdapter(character, this), mediaAdaptor)
|
||||||
|
|
||||||
val gridSize = (screenWidth / 124f).toInt()
|
val gridSize = (screenWidth / 124f).toInt()
|
||||||
val gridLayoutManager = GridLayoutManager(this, gridSize)
|
val gridLayoutManager = GridLayoutManager(this, gridSize)
|
||||||
|
@ -118,16 +134,19 @@ ThemeManager(this).applyTheme()
|
||||||
binding.characterCover.scaleY = 1f * cap
|
binding.characterCover.scaleY = 1f * cap
|
||||||
binding.characterCover.cardElevation = 32f * cap
|
binding.characterCover.cardElevation = 32f * cap
|
||||||
|
|
||||||
binding.characterCover.visibility = if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE
|
binding.characterCover.visibility =
|
||||||
|
if (binding.characterCover.scaleX == 0f) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
if (percentage >= percent && !isCollapsed) {
|
if (percentage >= percent && !isCollapsed) {
|
||||||
isCollapsed = true
|
isCollapsed = true
|
||||||
if (uiSettings.immersiveMode) this.window.statusBarColor = ContextCompat.getColor(this, R.color.nav_bg)
|
if (uiSettings.immersiveMode) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.nav_bg)
|
||||||
binding.characterAppBar.setBackgroundResource(R.color.nav_bg)
|
binding.characterAppBar.setBackgroundResource(R.color.nav_bg)
|
||||||
}
|
}
|
||||||
if (percentage <= percent && isCollapsed) {
|
if (percentage <= percent && isCollapsed) {
|
||||||
isCollapsed = false
|
isCollapsed = false
|
||||||
if (uiSettings.immersiveMode) this.window.statusBarColor = ContextCompat.getColor(this, R.color.status)
|
if (uiSettings.immersiveMode) this.window.statusBarColor =
|
||||||
|
ContextCompat.getColor(this, R.color.status)
|
||||||
binding.characterAppBar.setBackgroundResource(R.color.bg)
|
binding.characterAppBar.setBackgroundResource(R.color.bg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
class CharacterDetailsAdapter(private val character: Character, private val activity: Activity) :
|
class CharacterDetailsAdapter(private val character: Character, private val activity: Activity) :
|
||||||
RecyclerView.Adapter<CharacterDetailsAdapter.GenreViewHolder>() {
|
RecyclerView.Adapter<CharacterDetailsAdapter.GenreViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenreViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenreViewHolder {
|
||||||
val binding = ItemCharacterDetailsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemCharacterDetailsBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return GenreViewHolder(binding)
|
return GenreViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,11 +33,13 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
|
||||||
} else "") + "\n" + character.description
|
} else "") + "\n" + character.description
|
||||||
|
|
||||||
binding.characterDesc.isTextSelectable
|
binding.characterDesc.isTextSelectable
|
||||||
val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create()).usePlugin(SpoilerPlugin()).build()
|
val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create())
|
||||||
|
.usePlugin(SpoilerPlugin()).build()
|
||||||
markWon.setMarkdown(binding.characterDesc, desc)
|
markWon.setMarkdown(binding.characterDesc, desc)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
inner class GenreViewHolder(val binding: ItemCharacterDetailsBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class GenreViewHolder(val binding: ItemCharacterDetailsBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
|
@ -14,9 +14,9 @@ import ani.dantotsu.databinding.ActivityGenreBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -50,7 +50,8 @@ ThemeManager(this).applyTheme()
|
||||||
model.doneListener?.invoke()
|
model.doneListener?.invoke()
|
||||||
}
|
}
|
||||||
binding.mediaInfoGenresRecyclerView.adapter = adapter
|
binding.mediaInfoGenresRecyclerView.adapter = adapter
|
||||||
binding.mediaInfoGenresRecyclerView.layoutManager = GridLayoutManager(this, (screenWidth / 156f).toInt())
|
binding.mediaInfoGenresRecyclerView.layoutManager =
|
||||||
|
GridLayoutManager(this, (screenWidth / 156f).toInt())
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
model.loadGenres(Anilist.genres ?: loadData("genres_list") ?: arrayListOf()) {
|
model.loadGenres(Anilist.genres ?: loadData("genres_list") ?: arrayListOf()) {
|
||||||
|
|
|
@ -37,7 +37,8 @@ class GenreAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = genres.size
|
override fun getItemCount(): Int = genres.size
|
||||||
inner class GenreViewHolder(val binding: ItemGenreBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class GenreViewHolder(val binding: ItemGenreBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
import ani.dantotsu.connections.anilist.api.MediaEdge
|
import ani.dantotsu.connections.anilist.api.MediaEdge
|
||||||
import ani.dantotsu.connections.anilist.api.MediaList
|
import ani.dantotsu.connections.anilist.api.MediaList
|
||||||
|
@ -119,4 +120,5 @@ data class Media(
|
||||||
|
|
||||||
object MediaSingleton {
|
object MediaSingleton {
|
||||||
var media: Media? = null
|
var media: Media? = null
|
||||||
|
var bitmap: Bitmap? = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,14 @@ import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
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.view.animation.AccelerateDecelerateInterpolator
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
@ -37,13 +41,35 @@ class MediaAdaptor(
|
||||||
private val viewPager: ViewPager2? = null,
|
private val viewPager: ViewPager2? = null,
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
private val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
private val uiSettings =
|
||||||
|
loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
0 -> MediaViewHolder(ItemMediaCompactBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
0 -> MediaViewHolder(
|
||||||
1 -> MediaLargeViewHolder(ItemMediaLargeBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
ItemMediaCompactBinding.inflate(
|
||||||
2 -> MediaPageViewHolder(ItemMediaPageBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
1 -> MediaLargeViewHolder(
|
||||||
|
ItemMediaLargeBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
2 -> MediaPageViewHolder(
|
||||||
|
ItemMediaPageBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
3 -> MediaPageSmallViewHolder(
|
3 -> MediaPageSmallViewHolder(
|
||||||
ItemMediaPageSmallBinding.inflate(
|
ItemMediaPageSmallBinding.inflate(
|
||||||
LayoutInflater.from(parent.context),
|
LayoutInflater.from(parent.context),
|
||||||
|
@ -51,6 +77,7 @@ class MediaAdaptor(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,10 +92,12 @@ class MediaAdaptor(
|
||||||
val media = mediaList?.getOrNull(position)
|
val media = mediaList?.getOrNull(position)
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
b.itemCompactImage.loadImage(media.cover)
|
b.itemCompactImage.loadImage(media.cover)
|
||||||
b.itemCompactOngoing.visibility = if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
b.itemCompactOngoing.visibility =
|
||||||
|
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||||
b.itemCompactTitle.text = media.userPreferredName
|
b.itemCompactTitle.text = media.userPreferredName
|
||||||
b.itemCompactScore.text =
|
b.itemCompactScore.text =
|
||||||
((if (media.userScore == 0) (media.meanScore ?: 0) else media.userScore) / 10.0).toString()
|
((if (media.userScore == 0) (media.meanScore
|
||||||
|
?: 0) else media.userScore) / 10.0).toString()
|
||||||
b.itemCompactScoreBG.background = ContextCompat.getDrawable(
|
b.itemCompactScoreBG.background = ContextCompat.getDrawable(
|
||||||
b.root.context,
|
b.root.context,
|
||||||
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
|
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
|
||||||
|
@ -100,6 +129,7 @@ class MediaAdaptor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
val b = (holder as MediaLargeViewHolder).binding
|
val b = (holder as MediaLargeViewHolder).binding
|
||||||
setAnimation(activity, b.root, uiSettings)
|
setAnimation(activity, b.root, uiSettings)
|
||||||
|
@ -107,22 +137,29 @@ class MediaAdaptor(
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
b.itemCompactImage.loadImage(media.cover)
|
b.itemCompactImage.loadImage(media.cover)
|
||||||
b.itemCompactBanner.loadImage(media.banner ?: media.cover, 400)
|
b.itemCompactBanner.loadImage(media.banner ?: media.cover, 400)
|
||||||
b.itemCompactOngoing.visibility = if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
b.itemCompactOngoing.visibility =
|
||||||
|
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||||
b.itemCompactTitle.text = media.userPreferredName
|
b.itemCompactTitle.text = media.userPreferredName
|
||||||
b.itemCompactScore.text =
|
b.itemCompactScore.text =
|
||||||
((if (media.userScore == 0) (media.meanScore ?: 0) else media.userScore) / 10.0).toString()
|
((if (media.userScore == 0) (media.meanScore
|
||||||
|
?: 0) else media.userScore) / 10.0).toString()
|
||||||
b.itemCompactScoreBG.background = ContextCompat.getDrawable(
|
b.itemCompactScoreBG.background = ContextCompat.getDrawable(
|
||||||
b.root.context,
|
b.root.context,
|
||||||
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
|
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
|
||||||
)
|
)
|
||||||
if (media.anime != null) {
|
if (media.anime != null) {
|
||||||
b.itemTotal.text = " " + if ((media.anime.totalEpisodes ?: 0) != 1) currActivity()!!.getString(R.string.episode_plural)
|
b.itemTotal.text = " " + if ((media.anime.totalEpisodes
|
||||||
|
?: 0) != 1
|
||||||
|
) currActivity()!!.getString(R.string.episode_plural)
|
||||||
else currActivity()!!.getString(R.string.episode_singular)
|
else currActivity()!!.getString(R.string.episode_singular)
|
||||||
b.itemCompactTotal.text =
|
b.itemCompactTotal.text =
|
||||||
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " / " + (media.anime.totalEpisodes
|
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " / " + (media.anime.totalEpisodes
|
||||||
?: "??").toString()) else (media.anime.totalEpisodes ?: "??").toString()
|
?: "??").toString()) else (media.anime.totalEpisodes
|
||||||
|
?: "??").toString()
|
||||||
} else if (media.manga != null) {
|
} else if (media.manga != null) {
|
||||||
b.itemTotal.text = " " + if ((media.manga.totalChapters ?: 0) != 1) currActivity()!!.getString(R.string.chapter_plural)
|
b.itemTotal.text = " " + if ((media.manga.totalChapters
|
||||||
|
?: 0) != 1
|
||||||
|
) currActivity()!!.getString(R.string.chapter_plural)
|
||||||
else currActivity()!!.getString(R.string.chapter_singular)
|
else currActivity()!!.getString(R.string.chapter_singular)
|
||||||
b.itemCompactTotal.text = "${media.manga.totalChapters ?: "??"}"
|
b.itemCompactTotal.text = "${media.manga.totalChapters ?: "??"}"
|
||||||
}
|
}
|
||||||
|
@ -133,6 +170,7 @@ class MediaAdaptor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
val b = (holder as MediaPageViewHolder).binding
|
val b = (holder as MediaPageViewHolder).binding
|
||||||
val media = mediaList?.get(position)
|
val media = mediaList?.get(position)
|
||||||
|
@ -145,7 +183,8 @@ class MediaAdaptor(
|
||||||
AccelerateDecelerateInterpolator()
|
AccelerateDecelerateInterpolator()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val banner = if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
|
val banner =
|
||||||
|
if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
|
||||||
val context = b.itemCompactBanner.context
|
val context = b.itemCompactBanner.context
|
||||||
if (!(context as Activity).isDestroyed)
|
if (!(context as Activity).isDestroyed)
|
||||||
Glide.with(context as Context)
|
Glide.with(context as Context)
|
||||||
|
@ -153,22 +192,29 @@ class MediaAdaptor(
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL).override(400)
|
.diskCacheStrategy(DiskCacheStrategy.ALL).override(400)
|
||||||
.apply(RequestOptions.bitmapTransform(BlurTransformation(2, 3)))
|
.apply(RequestOptions.bitmapTransform(BlurTransformation(2, 3)))
|
||||||
.into(banner)
|
.into(banner)
|
||||||
b.itemCompactOngoing.visibility = if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
b.itemCompactOngoing.visibility =
|
||||||
|
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||||
b.itemCompactTitle.text = media.userPreferredName
|
b.itemCompactTitle.text = media.userPreferredName
|
||||||
b.itemCompactScore.text =
|
b.itemCompactScore.text =
|
||||||
((if (media.userScore == 0) (media.meanScore ?: 0) else media.userScore) / 10.0).toString()
|
((if (media.userScore == 0) (media.meanScore
|
||||||
|
?: 0) else media.userScore) / 10.0).toString()
|
||||||
b.itemCompactScoreBG.background = ContextCompat.getDrawable(
|
b.itemCompactScoreBG.background = ContextCompat.getDrawable(
|
||||||
b.root.context,
|
b.root.context,
|
||||||
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
|
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
|
||||||
)
|
)
|
||||||
if (media.anime != null) {
|
if (media.anime != null) {
|
||||||
b.itemTotal.text = " " + if ((media.anime.totalEpisodes ?: 0) != 1) currActivity()!!.getString(R.string.episode_plural)
|
b.itemTotal.text = " " + if ((media.anime.totalEpisodes
|
||||||
|
?: 0) != 1
|
||||||
|
) currActivity()!!.getString(R.string.episode_plural)
|
||||||
else currActivity()!!.getString(R.string.episode_singular)
|
else currActivity()!!.getString(R.string.episode_singular)
|
||||||
b.itemCompactTotal.text =
|
b.itemCompactTotal.text =
|
||||||
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " / " + (media.anime.totalEpisodes
|
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " / " + (media.anime.totalEpisodes
|
||||||
?: "??").toString()) else (media.anime.totalEpisodes ?: "??").toString()
|
?: "??").toString()) else (media.anime.totalEpisodes
|
||||||
|
?: "??").toString()
|
||||||
} else if (media.manga != null) {
|
} else if (media.manga != null) {
|
||||||
b.itemTotal.text =" " + if ((media.manga.totalChapters ?: 0) != 1) currActivity()!!.getString(R.string.chapter_plural)
|
b.itemTotal.text = " " + if ((media.manga.totalChapters
|
||||||
|
?: 0) != 1
|
||||||
|
) currActivity()!!.getString(R.string.chapter_plural)
|
||||||
else currActivity()!!.getString(R.string.chapter_singular)
|
else currActivity()!!.getString(R.string.chapter_singular)
|
||||||
b.itemCompactTotal.text = "${media.manga.totalChapters ?: "??"}"
|
b.itemCompactTotal.text = "${media.manga.totalChapters ?: "??"}"
|
||||||
}
|
}
|
||||||
|
@ -180,6 +226,7 @@ class MediaAdaptor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
3 -> {
|
3 -> {
|
||||||
val b = (holder as MediaPageSmallViewHolder).binding
|
val b = (holder as MediaPageSmallViewHolder).binding
|
||||||
val media = mediaList?.get(position)
|
val media = mediaList?.get(position)
|
||||||
|
@ -192,7 +239,8 @@ class MediaAdaptor(
|
||||||
AccelerateDecelerateInterpolator()
|
AccelerateDecelerateInterpolator()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val banner = if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
|
val banner =
|
||||||
|
if (uiSettings.bannerAnimations) b.itemCompactBanner else b.itemCompactBannerNoKen
|
||||||
val context = b.itemCompactBanner.context
|
val context = b.itemCompactBanner.context
|
||||||
if (!(context as Activity).isDestroyed)
|
if (!(context as Activity).isDestroyed)
|
||||||
Glide.with(context as Context)
|
Glide.with(context as Context)
|
||||||
|
@ -200,10 +248,12 @@ class MediaAdaptor(
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL).override(400)
|
.diskCacheStrategy(DiskCacheStrategy.ALL).override(400)
|
||||||
.apply(RequestOptions.bitmapTransform(BlurTransformation(2, 3)))
|
.apply(RequestOptions.bitmapTransform(BlurTransformation(2, 3)))
|
||||||
.into(banner)
|
.into(banner)
|
||||||
b.itemCompactOngoing.visibility = if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
b.itemCompactOngoing.visibility =
|
||||||
|
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||||
b.itemCompactTitle.text = media.userPreferredName
|
b.itemCompactTitle.text = media.userPreferredName
|
||||||
b.itemCompactScore.text =
|
b.itemCompactScore.text =
|
||||||
((if (media.userScore == 0) (media.meanScore ?: 0) else media.userScore) / 10.0).toString()
|
((if (media.userScore == 0) (media.meanScore
|
||||||
|
?: 0) else media.userScore) / 10.0).toString()
|
||||||
b.itemCompactScoreBG.background = ContextCompat.getDrawable(
|
b.itemCompactScoreBG.background = ContextCompat.getDrawable(
|
||||||
b.root.context,
|
b.root.context,
|
||||||
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
|
(if (media.userScore != 0) R.drawable.item_user_score else R.drawable.item_score)
|
||||||
|
@ -218,13 +268,18 @@ class MediaAdaptor(
|
||||||
}
|
}
|
||||||
b.itemCompactStatus.text = media.status ?: ""
|
b.itemCompactStatus.text = media.status ?: ""
|
||||||
if (media.anime != null) {
|
if (media.anime != null) {
|
||||||
b.itemTotal.text = " " + if ((media.anime.totalEpisodes ?: 0) != 1) currActivity()!!.getString(R.string.episode_plural)
|
b.itemTotal.text = " " + if ((media.anime.totalEpisodes
|
||||||
|
?: 0) != 1
|
||||||
|
) currActivity()!!.getString(R.string.episode_plural)
|
||||||
else currActivity()!!.getString(R.string.episode_singular)
|
else currActivity()!!.getString(R.string.episode_singular)
|
||||||
b.itemCompactTotal.text =
|
b.itemCompactTotal.text =
|
||||||
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " / " + (media.anime.totalEpisodes
|
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " / " + (media.anime.totalEpisodes
|
||||||
?: "??").toString()) else (media.anime.totalEpisodes ?: "??").toString()
|
?: "??").toString()) else (media.anime.totalEpisodes
|
||||||
|
?: "??").toString()
|
||||||
} else if (media.manga != null) {
|
} else if (media.manga != null) {
|
||||||
b.itemTotal.text = " " + if ((media.manga.totalChapters ?: 0) != 1) currActivity()!!.getString(R.string.chapter_plural)
|
b.itemTotal.text = " " + if ((media.manga.totalChapters
|
||||||
|
?: 0) != 1
|
||||||
|
) currActivity()!!.getString(R.string.chapter_plural)
|
||||||
else currActivity()!!.getString(R.string.chapter_singular)
|
else currActivity()!!.getString(R.string.chapter_singular)
|
||||||
b.itemCompactTotal.text = "${media.manga.totalChapters ?: "??"}"
|
b.itemCompactTotal.text = "${media.manga.totalChapters ?: "??"}"
|
||||||
}
|
}
|
||||||
|
@ -245,43 +300,73 @@ class MediaAdaptor(
|
||||||
return type
|
return type
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class MediaViewHolder(val binding: ItemMediaCompactBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class MediaViewHolder(val binding: ItemMediaCompactBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
if (matchParent) itemView.updateLayoutParams { width = -1 }
|
if (matchParent) itemView.updateLayoutParams { width = -1 }
|
||||||
itemView.setSafeOnClickListener { clicked(bindingAdapterPosition) }
|
itemView.setSafeOnClickListener {
|
||||||
|
clicked(
|
||||||
|
bindingAdapterPosition,
|
||||||
|
resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)
|
||||||
|
)
|
||||||
|
}
|
||||||
itemView.setOnLongClickListener { longClicked(bindingAdapterPosition) }
|
itemView.setOnLongClickListener { longClicked(bindingAdapterPosition) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class MediaLargeViewHolder(val binding: ItemMediaLargeBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class MediaLargeViewHolder(val binding: ItemMediaLargeBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setSafeOnClickListener { clicked(bindingAdapterPosition) }
|
itemView.setSafeOnClickListener {
|
||||||
|
clicked(
|
||||||
|
bindingAdapterPosition,
|
||||||
|
resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)
|
||||||
|
)
|
||||||
|
}
|
||||||
itemView.setOnLongClickListener { longClicked(bindingAdapterPosition) }
|
itemView.setOnLongClickListener { longClicked(bindingAdapterPosition) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
inner class MediaPageViewHolder(val binding: ItemMediaPageBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class MediaPageViewHolder(val binding: ItemMediaPageBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
binding.itemCompactImage.setSafeOnClickListener { clicked(bindingAdapterPosition) }
|
binding.itemCompactImage.setSafeOnClickListener {
|
||||||
|
clicked(
|
||||||
|
bindingAdapterPosition,
|
||||||
|
resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)
|
||||||
|
)
|
||||||
|
}
|
||||||
itemView.setOnTouchListener { _, _ -> true }
|
itemView.setOnTouchListener { _, _ -> true }
|
||||||
binding.itemCompactImage.setOnLongClickListener { longClicked(bindingAdapterPosition) }
|
binding.itemCompactImage.setOnLongClickListener { longClicked(bindingAdapterPosition) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
inner class MediaPageSmallViewHolder(val binding: ItemMediaPageSmallBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class MediaPageSmallViewHolder(val binding: ItemMediaPageSmallBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
binding.itemCompactImage.setSafeOnClickListener { clicked(bindingAdapterPosition) }
|
binding.itemCompactImage.setSafeOnClickListener {
|
||||||
binding.itemCompactTitleContainer.setSafeOnClickListener { clicked(bindingAdapterPosition) }
|
clicked(
|
||||||
|
bindingAdapterPosition,
|
||||||
|
resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.itemCompactTitleContainer.setSafeOnClickListener {
|
||||||
|
clicked(
|
||||||
|
bindingAdapterPosition,
|
||||||
|
resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)
|
||||||
|
)
|
||||||
|
}
|
||||||
itemView.setOnTouchListener { _, _ -> true }
|
itemView.setOnTouchListener { _, _ -> true }
|
||||||
binding.itemCompactImage.setOnLongClickListener { longClicked(bindingAdapterPosition) }
|
binding.itemCompactImage.setOnLongClickListener { longClicked(bindingAdapterPosition) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clicked(position: Int) {
|
fun clicked(position: Int, bitmap: Bitmap? = null) {
|
||||||
if ((mediaList?.size ?: 0) > position && position != -1) {
|
if ((mediaList?.size ?: 0) > position && position != -1) {
|
||||||
val media = mediaList?.get(position)
|
val media = mediaList?.get(position)
|
||||||
|
if (bitmap != null) MediaSingleton.bitmap = bitmap
|
||||||
ContextCompat.startActivity(
|
ContextCompat.startActivity(
|
||||||
activity,
|
activity,
|
||||||
Intent(activity, MediaDetailsActivity::class.java).putExtra(
|
Intent(activity, MediaDetailsActivity::class.java).putExtra(
|
||||||
|
@ -296,10 +381,53 @@ class MediaAdaptor(
|
||||||
if ((mediaList?.size ?: 0) > position && position != -1) {
|
if ((mediaList?.size ?: 0) > position && position != -1) {
|
||||||
val media = mediaList?.get(position) ?: return false
|
val media = mediaList?.get(position) ?: return false
|
||||||
if (activity.supportFragmentManager.findFragmentByTag("list") == null) {
|
if (activity.supportFragmentManager.findFragmentByTag("list") == null) {
|
||||||
MediaListDialogSmallFragment.newInstance(media).show(activity.supportFragmentManager, "list")
|
MediaListDialogSmallFragment.newInstance(media)
|
||||||
|
.show(activity.supportFragmentManager, "list")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBitmapFromImageView(imageView: ImageView): Bitmap? {
|
||||||
|
val drawable = imageView.drawable ?: return null
|
||||||
|
|
||||||
|
// If the drawable is a BitmapDrawable, then just get the bitmap
|
||||||
|
if (drawable is BitmapDrawable) {
|
||||||
|
return drawable.bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a bitmap with the same dimensions as the drawable
|
||||||
|
val bitmap = Bitmap.createBitmap(
|
||||||
|
drawable.intrinsicWidth,
|
||||||
|
drawable.intrinsicHeight,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw the drawable onto the bitmap
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||||
|
drawable.draw(canvas)
|
||||||
|
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resizeBitmap(source: Bitmap?, maxDimension: Int): Bitmap? {
|
||||||
|
if (source == null) return null
|
||||||
|
val width = source.width
|
||||||
|
val height = source.height
|
||||||
|
val newWidth: Int
|
||||||
|
val newHeight: Int
|
||||||
|
|
||||||
|
if (width > height) {
|
||||||
|
newWidth = maxDimension
|
||||||
|
newHeight = (height * (maxDimension.toFloat() / width)).toInt()
|
||||||
|
} else {
|
||||||
|
newHeight = maxDimension
|
||||||
|
newWidth = (width * (maxDimension.toFloat() / height)).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Bitmap.createScaledBitmap(source, newWidth, newHeight, true)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -31,24 +31,24 @@ import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.ZoomOutPageTransformer
|
import ani.dantotsu.ZoomOutPageTransformer
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.media.anime.AnimeWatchFragment
|
|
||||||
import ani.dantotsu.copyToClipboard
|
import ani.dantotsu.copyToClipboard
|
||||||
import ani.dantotsu.databinding.ActivityMediaBinding
|
import ani.dantotsu.databinding.ActivityMediaBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.loadImage
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.media.anime.AnimeWatchFragment
|
||||||
import ani.dantotsu.media.manga.MangaReadFragment
|
import ani.dantotsu.media.manga.MangaReadFragment
|
||||||
import ani.dantotsu.navBarHeight
|
|
||||||
import ani.dantotsu.media.novel.NovelReadFragment
|
import ani.dantotsu.media.novel.NovelReadFragment
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.openLinkInBrowser
|
import ani.dantotsu.openLinkInBrowser
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.saveData
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import com.flaviofaria.kenburnsview.RandomTransitionGenerator
|
import com.flaviofaria.kenburnsview.RandomTransitionGenerator
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
|
@ -72,9 +72,11 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
LangSet.setLocale(this)
|
LangSet.setLocale(this)
|
||||||
ThemeManager(this).applyTheme()
|
var media: Media = intent.getSerialized("media") ?: return
|
||||||
|
ThemeManager(this).applyTheme(MediaSingleton.bitmap)
|
||||||
|
MediaSingleton.bitmap = null
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityMediaBinding.inflate(layoutInflater)
|
binding = ActivityMediaBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
||||||
|
@ -119,7 +121,7 @@ ThemeManager(this).applyTheme()
|
||||||
viewPager.isUserInputEnabled = false
|
viewPager.isUserInputEnabled = false
|
||||||
viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings))
|
||||||
|
|
||||||
var media: Media = intent.getSerialized("media") ?: return
|
|
||||||
val isDownload = intent.getBooleanExtra("download", false)
|
val isDownload = intent.getBooleanExtra("download", false)
|
||||||
media.selected = model.loadSelected(media, isDownload)
|
media.selected = model.loadSelected(media, isDownload)
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
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.content.SharedPreferences
|
||||||
import android.os.Environment
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.media.anime.Episode
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.media.anime.SelectorDialogFragment
|
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.logger
|
||||||
|
import ani.dantotsu.media.anime.Episode
|
||||||
|
import ani.dantotsu.media.anime.SelectorDialogFragment
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
import ani.dantotsu.others.AniSkip
|
import ani.dantotsu.others.AniSkip
|
||||||
import ani.dantotsu.others.Jikan
|
import ani.dantotsu.others.Jikan
|
||||||
import ani.dantotsu.others.Kitsu
|
import ani.dantotsu.others.Kitsu
|
||||||
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.Book
|
import ani.dantotsu.parsers.Book
|
||||||
import ani.dantotsu.parsers.MangaImage
|
import ani.dantotsu.parsers.MangaImage
|
||||||
import ani.dantotsu.parsers.MangaReadSources
|
import ani.dantotsu.parsers.MangaReadSources
|
||||||
|
import ani.dantotsu.parsers.MangaSources
|
||||||
import ani.dantotsu.parsers.NovelSources
|
import ani.dantotsu.parsers.NovelSources
|
||||||
import ani.dantotsu.parsers.ShowResponse
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
import ani.dantotsu.parsers.VideoExtractor
|
import ani.dantotsu.parsers.VideoExtractor
|
||||||
|
@ -30,25 +31,12 @@ import ani.dantotsu.parsers.WatchSources
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.saveData
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.tryWithSuspend
|
import ani.dantotsu.tryWithSuspend
|
||||||
import ani.dantotsu.currContext
|
|
||||||
import ani.dantotsu.R
|
|
||||||
import ani.dantotsu.download.Download
|
|
||||||
import ani.dantotsu.download.DownloadsManager
|
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
|
||||||
import ani.dantotsu.parsers.AniyomiAdapter
|
|
||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
|
||||||
import ani.dantotsu.parsers.HAnimeSources
|
|
||||||
import ani.dantotsu.parsers.HMangaSources
|
|
||||||
import ani.dantotsu.parsers.MangaSources
|
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class MediaDetailsViewModel : ViewModel() {
|
class MediaDetailsViewModel : ViewModel() {
|
||||||
val scrolledToTop = MutableLiveData(true)
|
val scrolledToTop = MutableLiveData(true)
|
||||||
|
@ -70,9 +58,12 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
if (isDownload) {
|
if (isDownload) {
|
||||||
data.sourceIndex = when (media.anime != null) {
|
data.sourceIndex = if (media.anime != null) {
|
||||||
true -> AnimeSources.list.size - 1
|
AnimeSources.list.size - 1
|
||||||
else -> MangaSources.list.size - 1
|
} else if (media.format == "MANGA" || media.format == "ONE_SHOT") {
|
||||||
|
MangaSources.list.size - 1
|
||||||
|
} else {
|
||||||
|
NovelSources.list.size - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
@ -81,7 +72,9 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
fun loadSelectedStringLocation(sourceName: String): Int {
|
fun loadSelectedStringLocation(sourceName: String): Int {
|
||||||
//find the location of the source in the list
|
//find the location of the source in the list
|
||||||
var location = watchSources?.list?.indexOfFirst { it.name == sourceName } ?: 0
|
var location = watchSources?.list?.indexOfFirst { it.name == sourceName } ?: 0
|
||||||
if (location == -1) {location = 0}
|
if (location == -1) {
|
||||||
|
location = 0
|
||||||
|
}
|
||||||
return location
|
return location
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +99,9 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
|
|
||||||
|
|
||||||
//Anime
|
//Anime
|
||||||
private val kitsuEpisodes: MutableLiveData<Map<String, Episode>> = MutableLiveData<Map<String, Episode>>(null)
|
private val kitsuEpisodes: MutableLiveData<Map<String, Episode>> =
|
||||||
|
MutableLiveData<Map<String, Episode>>(null)
|
||||||
|
|
||||||
fun getKitsuEpisodes(): LiveData<Map<String, Episode>> = kitsuEpisodes
|
fun getKitsuEpisodes(): LiveData<Map<String, Episode>> = kitsuEpisodes
|
||||||
suspend fun loadKitsuEpisodes(s: Media) {
|
suspend fun loadKitsuEpisodes(s: Media) {
|
||||||
tryWithSuspend {
|
tryWithSuspend {
|
||||||
|
@ -114,7 +109,9 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val fillerEpisodes: MutableLiveData<Map<String, Episode>> = MutableLiveData<Map<String, Episode>>(null)
|
private val fillerEpisodes: MutableLiveData<Map<String, Episode>> =
|
||||||
|
MutableLiveData<Map<String, Episode>>(null)
|
||||||
|
|
||||||
fun getFillerEpisodes(): LiveData<Map<String, Episode>> = fillerEpisodes
|
fun getFillerEpisodes(): LiveData<Map<String, Episode>> = fillerEpisodes
|
||||||
suspend fun loadFillerEpisodes(s: Media) {
|
suspend fun loadFillerEpisodes(s: Media) {
|
||||||
tryWithSuspend {
|
tryWithSuspend {
|
||||||
|
@ -145,7 +142,8 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
|
|
||||||
suspend fun overrideEpisodes(i: Int, source: ShowResponse, id: Int) {
|
suspend fun overrideEpisodes(i: Int, source: ShowResponse, id: Int) {
|
||||||
watchSources?.saveResponse(i, id, source)
|
watchSources?.saveResponse(i, id, source)
|
||||||
epsLoaded[i] = watchSources?.loadEpisodes(i, source.link, source.extra, source.sAnime) ?: return
|
epsLoaded[i] =
|
||||||
|
watchSources?.loadEpisodes(i, source.link, source.extra, source.sAnime) ?: return
|
||||||
episodes.postValue(epsLoaded)
|
episodes.postValue(epsLoaded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +182,12 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
|
|
||||||
val timeStamps = MutableLiveData<List<AniSkip.Stamp>?>()
|
val timeStamps = MutableLiveData<List<AniSkip.Stamp>?>()
|
||||||
private val timeStampsMap: MutableMap<Int, List<AniSkip.Stamp>?> = mutableMapOf()
|
private val timeStampsMap: MutableMap<Int, List<AniSkip.Stamp>?> = mutableMapOf()
|
||||||
suspend fun loadTimeStamps(malId: Int?, episodeNum: Int?, duration: Long, useProxyForTimeStamps: Boolean) {
|
suspend fun loadTimeStamps(
|
||||||
|
malId: Int?,
|
||||||
|
episodeNum: Int?,
|
||||||
|
duration: Long,
|
||||||
|
useProxyForTimeStamps: Boolean
|
||||||
|
) {
|
||||||
malId ?: return
|
malId ?: return
|
||||||
episodeNum ?: return
|
episodeNum ?: return
|
||||||
if (timeStampsMap.containsKey(episodeNum))
|
if (timeStampsMap.containsKey(episodeNum))
|
||||||
|
@ -194,7 +197,11 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
timeStamps.postValue(result)
|
timeStamps.postValue(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun loadEpisodeSingleVideo(ep: Episode, selected: Selected, post: Boolean = true): Boolean {
|
suspend fun loadEpisodeSingleVideo(
|
||||||
|
ep: Episode,
|
||||||
|
selected: Selected,
|
||||||
|
post: Boolean = true
|
||||||
|
): Boolean {
|
||||||
if (ep.extractors.isNullOrEmpty()) {
|
if (ep.extractors.isNullOrEmpty()) {
|
||||||
|
|
||||||
val server = selected.server ?: return false
|
val server = selected.server ?: return false
|
||||||
|
@ -204,8 +211,10 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
selected.sourceIndex = selected.sourceIndex
|
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(
|
||||||
it1, post)
|
server, link, ep.extra,
|
||||||
|
it1, post
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} ?: return false)
|
} ?: return false)
|
||||||
ep.allStreams = false
|
ep.allStreams = false
|
||||||
|
@ -228,7 +237,13 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val epChanged = MutableLiveData(true)
|
val epChanged = MutableLiveData(true)
|
||||||
fun onEpisodeClick(media: Media, i: String, manager: FragmentManager, launch: Boolean = true, prevEp: String? = null) {
|
fun onEpisodeClick(
|
||||||
|
media: Media,
|
||||||
|
i: String,
|
||||||
|
manager: FragmentManager,
|
||||||
|
launch: Boolean = true,
|
||||||
|
prevEp: String? = null
|
||||||
|
) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
if (manager.findFragmentByTag("dialog") == null && !manager.isDestroyed) {
|
if (manager.findFragmentByTag("dialog") == null && !manager.isDestroyed) {
|
||||||
if (media.anime?.episodes?.get(i) != null) {
|
if (media.anime?.episodes?.get(i) != null) {
|
||||||
|
@ -238,7 +253,8 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
media.selected = this.loadSelected(media)
|
media.selected = this.loadSelected(media)
|
||||||
val selector = SelectorDialogFragment.newInstance(media.selected!!.server, launch, prevEp)
|
val selector =
|
||||||
|
SelectorDialogFragment.newInstance(media.selected!!.server, launch, prevEp)
|
||||||
selector.show(manager, "dialog")
|
selector.show(manager, "dialog")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,13 +264,17 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
//Manga
|
//Manga
|
||||||
var mangaReadSources: MangaReadSources? = null
|
var mangaReadSources: MangaReadSources? = null
|
||||||
|
|
||||||
private val mangaChapters = MutableLiveData<MutableMap<Int, MutableMap<String, MangaChapter>>>(null)
|
private val mangaChapters =
|
||||||
|
MutableLiveData<MutableMap<Int, MutableMap<String, MangaChapter>>>(null)
|
||||||
private val mangaLoaded = mutableMapOf<Int, MutableMap<String, MangaChapter>>()
|
private val mangaLoaded = mutableMapOf<Int, MutableMap<String, MangaChapter>>()
|
||||||
fun getMangaChapters(): LiveData<MutableMap<Int, MutableMap<String, MangaChapter>>> = mangaChapters
|
fun getMangaChapters(): LiveData<MutableMap<Int, MutableMap<String, MangaChapter>>> =
|
||||||
|
mangaChapters
|
||||||
|
|
||||||
suspend fun loadMangaChapters(media: Media, i: Int, invalidate: Boolean = false) {
|
suspend fun loadMangaChapters(media: Media, i: Int, invalidate: Boolean = false) {
|
||||||
logger("Loading Manga Chapters : $mangaLoaded")
|
logger("Loading Manga Chapters : $mangaLoaded")
|
||||||
if (!mangaLoaded.containsKey(i) || invalidate) tryWithSuspend {
|
if (!mangaLoaded.containsKey(i) || invalidate) tryWithSuspend {
|
||||||
mangaLoaded[i] = mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend
|
mangaLoaded[i] =
|
||||||
|
mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend
|
||||||
}
|
}
|
||||||
mangaChapters.postValue(mangaLoaded)
|
mangaChapters.postValue(mangaLoaded)
|
||||||
}
|
}
|
||||||
|
@ -269,11 +289,17 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
|
|
||||||
private val mangaChapter = MutableLiveData<MangaChapter?>(null)
|
private val mangaChapter = MutableLiveData<MangaChapter?>(null)
|
||||||
fun getMangaChapter(): LiveData<MangaChapter?> = mangaChapter
|
fun getMangaChapter(): LiveData<MangaChapter?> = mangaChapter
|
||||||
suspend fun loadMangaChapterImages(chapter: MangaChapter, selected: Selected, series: String, post: Boolean = true): Boolean {
|
suspend fun loadMangaChapterImages(
|
||||||
|
chapter: MangaChapter,
|
||||||
|
selected: Selected,
|
||||||
|
series: String,
|
||||||
|
post: Boolean = true
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
return tryWithSuspend(true) {
|
return tryWithSuspend(true) {
|
||||||
chapter.addImages(
|
chapter.addImages(
|
||||||
mangaReadSources?.get(selected.sourceIndex)?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false
|
mangaReadSources?.get(selected.sourceIndex)
|
||||||
|
?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false
|
||||||
)
|
)
|
||||||
if (post) mangaChapter.postValue(chapter)
|
if (post) mangaChapter.postValue(chapter)
|
||||||
true
|
true
|
||||||
|
@ -281,7 +307,8 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadTransformation(mangaImage: MangaImage, source: Int): BitmapTransformation? {
|
fun loadTransformation(mangaImage: MangaImage, source: Int): BitmapTransformation? {
|
||||||
return if (mangaImage.useTransformation) mangaReadSources?.get(source)?.getTransformation() else null
|
return if (mangaImage.useTransformation) mangaReadSources?.get(source)
|
||||||
|
?.getTransformation() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
val novelSources = NovelSources
|
val novelSources = NovelSources
|
||||||
|
@ -307,7 +334,9 @@ class MediaDetailsViewModel : ViewModel() {
|
||||||
val book: MutableLiveData<Book> = MutableLiveData(null)
|
val book: MutableLiveData<Book> = MutableLiveData(null)
|
||||||
suspend fun loadBook(novel: ShowResponse, i: Int) {
|
suspend fun loadBook(novel: ShowResponse, i: Int) {
|
||||||
tryWithSuspend {
|
tryWithSuspend {
|
||||||
book.postValue(novelSources[i]?.loadBook(novel.link, novel.extra) ?: return@tryWithSuspend)
|
book.postValue(
|
||||||
|
novelSources[i]?.loadBook(novel.link, novel.extra) ?: return@tryWithSuspend
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,11 @@ class MediaInfoFragment : Fragment() {
|
||||||
private var type = "ANIME"
|
private var type = "ANIME"
|
||||||
private val genreModel: GenresViewModel by activityViewModels()
|
private val genreModel: GenresViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = FragmentMediaInfoBinding.inflate(inflater, container, false)
|
_binding = FragmentMediaInfoBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -73,13 +77,15 @@ class MediaInfoFragment : Fragment() {
|
||||||
copyToClipboard(media.name ?: media.nameRomaji)
|
copyToClipboard(media.name ?: media.nameRomaji)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
if (media.name != null) binding.mediaInfoNameRomajiContainer.visibility = View.VISIBLE
|
if (media.name != null) binding.mediaInfoNameRomajiContainer.visibility =
|
||||||
|
View.VISIBLE
|
||||||
binding.mediaInfoNameRomaji.text = "\t\t\t" + media.nameRomaji
|
binding.mediaInfoNameRomaji.text = "\t\t\t" + media.nameRomaji
|
||||||
binding.mediaInfoNameRomaji.setOnLongClickListener {
|
binding.mediaInfoNameRomaji.setOnLongClickListener {
|
||||||
copyToClipboard(media.nameRomaji)
|
copyToClipboard(media.nameRomaji)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
binding.mediaInfoMeanScore.text = if (media.meanScore != null) (media.meanScore / 10.0).toString() else "??"
|
binding.mediaInfoMeanScore.text =
|
||||||
|
if (media.meanScore != null) (media.meanScore / 10.0).toString() else "??"
|
||||||
binding.mediaInfoStatus.text = media.status
|
binding.mediaInfoStatus.text = media.status
|
||||||
binding.mediaInfoFormat.text = media.format
|
binding.mediaInfoFormat.text = media.format
|
||||||
binding.mediaInfoSource.text = media.source
|
binding.mediaInfoSource.text = media.source
|
||||||
|
@ -246,7 +252,12 @@ class MediaInfoFragment : Fragment() {
|
||||||
val end = a.indexOf('"', first).let { if (it != -1) it else return a }
|
val end = a.indexOf('"', first).let { if (it != -1) it else return a }
|
||||||
val name = a.subSequence(first, end).toString()
|
val name = a.subSequence(first, end).toString()
|
||||||
return "${a.subSequence(0, first)}" +
|
return "${a.subSequence(0, first)}" +
|
||||||
"[$name](https://www.youtube.com/results?search_query=${URLEncoder.encode(name, "utf-8")})" +
|
"[$name](https://www.youtube.com/results?search_query=${
|
||||||
|
URLEncoder.encode(
|
||||||
|
name,
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
})" +
|
||||||
"${a.subSequence(end, a.length)}"
|
"${a.subSequence(end, a.length)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +281,11 @@ class MediaInfoFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media.anime.op.isNotEmpty()) {
|
if (media.anime.op.isNotEmpty()) {
|
||||||
val bind = ItemTitleTextBinding.inflate(LayoutInflater.from(context), parent, false)
|
val bind = ItemTitleTextBinding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
bind.itemTitle.setText(R.string.opening)
|
bind.itemTitle.setText(R.string.opening)
|
||||||
makeText(bind.itemText, media.anime.op)
|
makeText(bind.itemText, media.anime.op)
|
||||||
parent.addView(bind.root)
|
parent.addView(bind.root)
|
||||||
|
@ -278,7 +293,11 @@ class MediaInfoFragment : Fragment() {
|
||||||
|
|
||||||
|
|
||||||
if (media.anime.ed.isNotEmpty()) {
|
if (media.anime.ed.isNotEmpty()) {
|
||||||
val bind = ItemTitleTextBinding.inflate(LayoutInflater.from(context), parent, false)
|
val bind = ItemTitleTextBinding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
bind.itemTitle.setText(R.string.ending)
|
bind.itemTitle.setText(R.string.ending)
|
||||||
makeText(bind.itemText, media.anime.ed)
|
makeText(bind.itemText, media.anime.ed)
|
||||||
parent.addView(bind.root)
|
parent.addView(bind.root)
|
||||||
|
@ -458,7 +477,8 @@ class MediaInfoFragment : Fragment() {
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
val cornerTop = ObjectAnimator.ofFloat(binding.root, "radius", 0f, 32f).setDuration(200)
|
val cornerTop = ObjectAnimator.ofFloat(binding.root, "radius", 0f, 32f).setDuration(200)
|
||||||
val cornerNotTop = ObjectAnimator.ofFloat(binding.root, "radius", 32f, 0f).setDuration(200)
|
val cornerNotTop =
|
||||||
|
ObjectAnimator.ofFloat(binding.root, "radius", 32f, 0f).setDuration(200)
|
||||||
var cornered = true
|
var cornered = true
|
||||||
cornerTop.start()
|
cornerTop.start()
|
||||||
binding.mediaInfoScroll.setOnScrollChangeListener { v, _, _, _, _ ->
|
binding.mediaInfoScroll.setOnScrollChangeListener { v, _, _, _, _ ->
|
||||||
|
|
|
@ -14,8 +14,8 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
import ani.dantotsu.databinding.BottomSheetMediaListBinding
|
|
||||||
import ani.dantotsu.connections.mal.MAL
|
import ani.dantotsu.connections.mal.MAL
|
||||||
|
import ani.dantotsu.databinding.BottomSheetMediaListBinding
|
||||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -27,7 +27,11 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
|
||||||
private var _binding: BottomSheetMediaListBinding? = null
|
private var _binding: BottomSheetMediaListBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetMediaListBinding.inflate(inflater, container, false)
|
_binding = BottomSheetMediaListBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -46,8 +50,12 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
|
||||||
binding.mediaListLayout.visibility = View.VISIBLE
|
binding.mediaListLayout.visibility = View.VISIBLE
|
||||||
|
|
||||||
val statuses: Array<String> = resources.getStringArray(R.array.status)
|
val statuses: Array<String> = resources.getStringArray(R.array.status)
|
||||||
val statusStrings = if (media?.manga==null) resources.getStringArray(R.array.status_anime) else resources.getStringArray(R.array.status_manga)
|
val statusStrings =
|
||||||
val userStatus = if(media!!.userStatus != null) statusStrings[statuses.indexOf(media!!.userStatus)] else statusStrings[0]
|
if (media?.manga == null) resources.getStringArray(R.array.status_anime) else resources.getStringArray(
|
||||||
|
R.array.status_manga
|
||||||
|
)
|
||||||
|
val userStatus =
|
||||||
|
if (media!!.userStatus != null) statusStrings[statuses.indexOf(media!!.userStatus)] else statusStrings[0]
|
||||||
|
|
||||||
binding.mediaListStatus.setText(userStatus)
|
binding.mediaListStatus.setText(userStatus)
|
||||||
binding.mediaListStatus.setAdapter(
|
binding.mediaListStatus.setAdapter(
|
||||||
|
@ -160,7 +168,9 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
|
||||||
val init =
|
val init =
|
||||||
if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString()
|
if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString()
|
||||||
.toInt() else 0
|
.toInt() else 0
|
||||||
if (init < (total ?: 5000)) binding.mediaListProgress.setText((init + 1).toString())
|
if (init < (total
|
||||||
|
?: 5000)
|
||||||
|
) binding.mediaListProgress.setText((init + 1).toString())
|
||||||
if (init + 1 == (total ?: 5000)) {
|
if (init + 1 == (total ?: 5000)) {
|
||||||
binding.mediaListStatus.setText(statusStrings[2], false)
|
binding.mediaListStatus.setText(statusStrings[2], false)
|
||||||
onComplete()
|
onComplete()
|
||||||
|
@ -201,11 +211,15 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
val progress = _binding?.mediaListProgress?.text.toString().toIntOrNull()
|
val progress =
|
||||||
|
_binding?.mediaListProgress?.text.toString().toIntOrNull()
|
||||||
val score =
|
val score =
|
||||||
(_binding?.mediaListScore?.text.toString().toDoubleOrNull()?.times(10))?.toInt()
|
(_binding?.mediaListScore?.text.toString().toDoubleOrNull()
|
||||||
val status = statuses[statusStrings.indexOf(_binding?.mediaListStatus?.text.toString())]
|
?.times(10))?.toInt()
|
||||||
val rewatch = _binding?.mediaListRewatch?.text?.toString()?.toIntOrNull()
|
val status =
|
||||||
|
statuses[statusStrings.indexOf(_binding?.mediaListStatus?.text.toString())]
|
||||||
|
val rewatch =
|
||||||
|
_binding?.mediaListRewatch?.text?.toString()?.toIntOrNull()
|
||||||
val notes = _binding?.mediaListNotes?.text?.toString()
|
val notes = _binding?.mediaListNotes?.text?.toString()
|
||||||
val startD = start.date
|
val startD = start.date
|
||||||
val endD = end.date
|
val endD = end.date
|
||||||
|
|
|
@ -12,11 +12,9 @@ import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.databinding.BottomSheetMediaListSmallBinding
|
|
||||||
import ani.dantotsu.connections.mal.MAL
|
import ani.dantotsu.connections.mal.MAL
|
||||||
|
import ani.dantotsu.databinding.BottomSheetMediaListSmallBinding
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -46,7 +44,11 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
|
||||||
private var _binding: BottomSheetMediaListSmallBinding? = null
|
private var _binding: BottomSheetMediaListSmallBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetMediaListSmallBinding.inflate(inflater, container, false)
|
_binding = BottomSheetMediaListSmallBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -60,8 +62,12 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
|
||||||
binding.mediaListProgressBar.visibility = View.GONE
|
binding.mediaListProgressBar.visibility = View.GONE
|
||||||
binding.mediaListLayout.visibility = View.VISIBLE
|
binding.mediaListLayout.visibility = View.VISIBLE
|
||||||
val statuses: Array<String> = resources.getStringArray(R.array.status)
|
val statuses: Array<String> = resources.getStringArray(R.array.status)
|
||||||
val statusStrings = if (media.manga==null) resources.getStringArray(R.array.status_anime) else resources.getStringArray(R.array.status_manga)
|
val statusStrings =
|
||||||
val userStatus = if(media.userStatus != null) statusStrings[statuses.indexOf(media.userStatus)] else statusStrings[0]
|
if (media.manga == null) resources.getStringArray(R.array.status_anime) else resources.getStringArray(
|
||||||
|
R.array.status_manga
|
||||||
|
)
|
||||||
|
val userStatus =
|
||||||
|
if (media.userStatus != null) statusStrings[statuses.indexOf(media.userStatus)] else statusStrings[0]
|
||||||
|
|
||||||
binding.mediaListStatus.setText(userStatus)
|
binding.mediaListStatus.setText(userStatus)
|
||||||
binding.mediaListStatus.setAdapter(
|
binding.mediaListStatus.setAdapter(
|
||||||
|
@ -130,10 +136,26 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val progress = _binding?.mediaListProgress?.text.toString().toIntOrNull()
|
val progress = _binding?.mediaListProgress?.text.toString().toIntOrNull()
|
||||||
val score = (_binding?.mediaListScore?.text.toString().toDoubleOrNull()?.times(10))?.toInt()
|
val score = (_binding?.mediaListScore?.text.toString().toDoubleOrNull()
|
||||||
val status = statuses[statusStrings.indexOf(_binding?.mediaListStatus?.text.toString())]
|
?.times(10))?.toInt()
|
||||||
Anilist.mutation.editList(media.id, progress, score, null, null, status, media.isListPrivate)
|
val status =
|
||||||
MAL.query.editList(media.idMAL, media.anime != null, progress, score, status)
|
statuses[statusStrings.indexOf(_binding?.mediaListStatus?.text.toString())]
|
||||||
|
Anilist.mutation.editList(
|
||||||
|
media.id,
|
||||||
|
progress,
|
||||||
|
score,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
status,
|
||||||
|
media.isListPrivate
|
||||||
|
)
|
||||||
|
MAL.query.editList(
|
||||||
|
media.idMAL,
|
||||||
|
media.anime != null,
|
||||||
|
progress,
|
||||||
|
score,
|
||||||
|
status
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Refresh.all()
|
Refresh.all()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
|
||||||
class OtherDetailsViewModel : ViewModel() {
|
class OtherDetailsViewModel : ViewModel() {
|
||||||
private val character: MutableLiveData<Character> = MutableLiveData(null)
|
private val character: MutableLiveData<Character> = MutableLiveData(null)
|
||||||
|
@ -19,11 +19,13 @@ class OtherDetailsViewModel : ViewModel() {
|
||||||
suspend fun loadStudio(m: Studio) {
|
suspend fun loadStudio(m: Studio) {
|
||||||
if (studio.value == null) studio.postValue(Anilist.query.getStudioDetails(m))
|
if (studio.value == null) studio.postValue(Anilist.query.getStudioDetails(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val author: MutableLiveData<Author> = MutableLiveData(null)
|
private val author: MutableLiveData<Author> = MutableLiveData(null)
|
||||||
fun getAuthor(): LiveData<Author> = author
|
fun getAuthor(): LiveData<Author> = author
|
||||||
suspend fun loadAuthor(m: Author) {
|
suspend fun loadAuthor(m: Author) {
|
||||||
if (author.value == null) author.postValue(Anilist.query.getAuthorDetails(m))
|
if (author.value == null) author.postValue(Anilist.query.getAuthorDetails(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val calendar: MutableLiveData<Map<String, MutableList<Media>>> = MutableLiveData(null)
|
private val calendar: MutableLiveData<Map<String, MutableList<Media>>> = MutableLiveData(null)
|
||||||
fun getCalendar(): LiveData<Map<String, MutableList<Media>>> = calendar
|
fun getCalendar(): LiveData<Map<String, MutableList<Media>>> = calendar
|
||||||
suspend fun loadCalendar() {
|
suspend fun loadCalendar() {
|
||||||
|
|
|
@ -22,7 +22,8 @@ class ProgressAdapter(private val horizontal: Boolean = true, searched: Boolean)
|
||||||
var bar: ProgressBar? = null
|
var bar: ProgressBar? = null
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProgressViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProgressViewHolder {
|
||||||
val binding = ItemProgressbarBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemProgressbarBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return ProgressViewHolder(binding)
|
return ProgressViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +34,12 @@ class ProgressAdapter(private val horizontal: Boolean = true, searched: Boolean)
|
||||||
val doubleClickDetector = GestureDetector(progressBar.context, object : GesturesListener() {
|
val doubleClickDetector = GestureDetector(progressBar.context, object : GesturesListener() {
|
||||||
override fun onDoubleClick(event: MotionEvent) {
|
override fun onDoubleClick(event: MotionEvent) {
|
||||||
snackString(currContext()?.getString(R.string.cant_wait))
|
snackString(currContext()?.getString(R.string.cant_wait))
|
||||||
ObjectAnimator.ofFloat(progressBar, "translationX", progressBar.translationX, progressBar.translationX + 100f)
|
ObjectAnimator.ofFloat(
|
||||||
|
progressBar,
|
||||||
|
"translationX",
|
||||||
|
progressBar.translationX,
|
||||||
|
progressBar.translationX + 100f
|
||||||
|
)
|
||||||
.setDuration(300).start()
|
.setDuration(300).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +57,8 @@ class ProgressAdapter(private val horizontal: Boolean = true, searched: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
inner class ProgressViewHolder(val binding: ItemProgressbarBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class ProgressViewHolder(val binding: ItemProgressbarBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.updateLayoutParams { if (horizontal) width = -1 else height = -1 }
|
itemView.updateLayoutParams { if (horizontal) width = -1 else height = -1 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.anilist.AnilistSearch
|
import ani.dantotsu.connections.anilist.AnilistSearch
|
||||||
import ani.dantotsu.connections.anilist.SearchResults
|
import ani.dantotsu.connections.anilist.SearchResults
|
||||||
import ani.dantotsu.databinding.ActivitySearchBinding
|
import ani.dantotsu.databinding.ActivitySearchBinding
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.others.LangSet
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
|
@ -20,14 +20,16 @@ import ani.dantotsu.saveData
|
||||||
import com.google.android.material.checkbox.MaterialCheckBox.*
|
import com.google.android.material.checkbox.MaterialCheckBox.*
|
||||||
|
|
||||||
|
|
||||||
class SearchAdapter(private val activity: SearchActivity) : RecyclerView.Adapter<SearchAdapter.SearchHeaderViewHolder>() {
|
class SearchAdapter(private val activity: SearchActivity) :
|
||||||
|
RecyclerView.Adapter<SearchAdapter.SearchHeaderViewHolder>() {
|
||||||
private val itemViewType = 6969
|
private val itemViewType = 6969
|
||||||
var search: Runnable? = null
|
var search: Runnable? = null
|
||||||
var requestFocus: Runnable? = null
|
var requestFocus: Runnable? = null
|
||||||
private var textWatcher: TextWatcher? = null
|
private var textWatcher: TextWatcher? = null
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchHeaderViewHolder {
|
||||||
val binding = ItemSearchHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemSearchHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return SearchHeaderViewHolder(binding)
|
return SearchHeaderViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,13 +38,15 @@ class SearchAdapter(private val activity: SearchActivity) : RecyclerView.Adapter
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
|
|
||||||
|
|
||||||
val imm: InputMethodManager = activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm: InputMethodManager =
|
||||||
|
activity.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
||||||
when (activity.style) {
|
when (activity.style) {
|
||||||
0 -> {
|
0 -> {
|
||||||
binding.searchResultGrid.alpha = 1f
|
binding.searchResultGrid.alpha = 1f
|
||||||
binding.searchResultList.alpha = 0.33f
|
binding.searchResultList.alpha = 0.33f
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
binding.searchResultList.alpha = 1f
|
binding.searchResultList.alpha = 1f
|
||||||
binding.searchResultGrid.alpha = 0.33f
|
binding.searchResultGrid.alpha = 0.33f
|
||||||
|
@ -62,7 +66,8 @@ class SearchAdapter(private val activity: SearchActivity) : RecyclerView.Adapter
|
||||||
binding.searchChipRecycler.adapter = SearchChipAdapter(activity).also {
|
binding.searchChipRecycler.adapter = SearchChipAdapter(activity).also {
|
||||||
activity.updateChips = { it.update() }
|
activity.updateChips = { it.update() }
|
||||||
}
|
}
|
||||||
binding.searchChipRecycler.layoutManager = LinearLayoutManager(binding.root.context, HORIZONTAL, false)
|
binding.searchChipRecycler.layoutManager =
|
||||||
|
LinearLayoutManager(binding.root.context, HORIZONTAL, false)
|
||||||
|
|
||||||
binding.searchFilter.setOnClickListener {
|
binding.searchFilter.setOnClickListener {
|
||||||
SearchFilterBottomDialog.newInstance().show(activity.supportFragmentManager, "dialog")
|
SearchFilterBottomDialog.newInstance().show(activity.supportFragmentManager, "dialog")
|
||||||
|
@ -70,7 +75,8 @@ class SearchAdapter(private val activity: SearchActivity) : RecyclerView.Adapter
|
||||||
|
|
||||||
fun searchTitle() {
|
fun searchTitle() {
|
||||||
activity.result.apply {
|
activity.result.apply {
|
||||||
search = if (binding.searchBarText.text.toString() != "") binding.searchBarText.text.toString() else null
|
search =
|
||||||
|
if (binding.searchBarText.text.toString() != "") binding.searchBarText.text.toString() else null
|
||||||
onList = listOnly
|
onList = listOnly
|
||||||
isAdult = adult
|
isAdult = adult
|
||||||
}
|
}
|
||||||
|
@ -96,6 +102,7 @@ class SearchAdapter(private val activity: SearchActivity) : RecyclerView.Adapter
|
||||||
imm.hideSoftInputFromWindow(binding.searchBarText.windowToken, 0)
|
imm.hideSoftInputFromWindow(binding.searchBarText.windowToken, 0)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,20 +165,24 @@ class SearchAdapter(private val activity: SearchActivity) : RecyclerView.Adapter
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
inner class SearchHeaderViewHolder(val binding: ItemSearchHeaderBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class SearchHeaderViewHolder(val binding: ItemSearchHeaderBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return itemViewType
|
return itemViewType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SearchChipAdapter(val activity: SearchActivity) : RecyclerView.Adapter<SearchChipAdapter.SearchChipViewHolder>() {
|
class SearchChipAdapter(val activity: SearchActivity) :
|
||||||
|
RecyclerView.Adapter<SearchChipAdapter.SearchChipViewHolder>() {
|
||||||
private var chips = activity.result.toChipList()
|
private var chips = activity.result.toChipList()
|
||||||
|
|
||||||
inner class SearchChipViewHolder(val binding: ItemChipBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class SearchChipViewHolder(val binding: ItemChipBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchChipViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchChipViewHolder {
|
||||||
val binding = ItemChipBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemChipBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return SearchChipViewHolder(binding)
|
return SearchChipViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,17 @@ import ani.dantotsu.databinding.BottomSheetSearchFilterBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
|
|
||||||
class SearchFilterBottomDialog() : BottomSheetDialogFragment() {
|
class SearchFilterBottomDialog : BottomSheetDialogFragment() {
|
||||||
private var _binding: BottomSheetSearchFilterBinding? = null
|
private var _binding: BottomSheetSearchFilterBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private lateinit var activity: SearchActivity
|
private lateinit var activity: SearchActivity
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetSearchFilterBinding.inflate(inflater, container, false)
|
_binding = BottomSheetSearchFilterBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -99,7 +103,7 @@ class SearchFilterBottomDialog() : BottomSheetDialogFragment() {
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
binding.root.context,
|
binding.root.context,
|
||||||
R.layout.item_dropdown,
|
R.layout.item_dropdown,
|
||||||
(1970 until 2024).map { it.toString() }.reversed().toTypedArray()
|
(1970 until 2025).map { it.toString() }.reversed().toTypedArray()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -129,7 +133,8 @@ class SearchFilterBottomDialog() : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
binding.searchGenresGrid.isChecked = false
|
binding.searchGenresGrid.isChecked = false
|
||||||
|
|
||||||
binding.searchFilterTags.adapter = FilterChipAdapter(Anilist.tags?.get(activity.result.isAdult) ?: listOf()) { chip ->
|
binding.searchFilterTags.adapter =
|
||||||
|
FilterChipAdapter(Anilist.tags?.get(activity.result.isAdult) ?: listOf()) { chip ->
|
||||||
val tag = chip.text.toString()
|
val tag = chip.text.toString()
|
||||||
chip.isChecked = selectedTags.contains(tag)
|
chip.isChecked = selectedTags.contains(tag)
|
||||||
chip.isCloseIconVisible = exTags.contains(tag)
|
chip.isCloseIconVisible = exTags.contains(tag)
|
||||||
|
@ -158,10 +163,12 @@ class SearchFilterBottomDialog() : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
class FilterChipAdapter(val list: List<String>, private val perform: ((Chip) -> Unit)) :
|
class FilterChipAdapter(val list: List<String>, private val perform: ((Chip) -> Unit)) :
|
||||||
RecyclerView.Adapter<FilterChipAdapter.SearchChipViewHolder>() {
|
RecyclerView.Adapter<FilterChipAdapter.SearchChipViewHolder>() {
|
||||||
inner class SearchChipViewHolder(val binding: ItemChipBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class SearchChipViewHolder(val binding: ItemChipBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchChipViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchChipViewHolder {
|
||||||
val binding = ItemChipBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemChipBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return SearchChipViewHolder(binding)
|
return SearchChipViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@ abstract class SourceAdapter(
|
||||||
private val scope: CoroutineScope
|
private val scope: CoroutineScope
|
||||||
) : RecyclerView.Adapter<SourceAdapter.SourceViewHolder>() {
|
) : RecyclerView.Adapter<SourceAdapter.SourceViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SourceViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SourceViewHolder {
|
||||||
val binding = ItemCharacterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemCharacterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return SourceViewHolder(binding)
|
return SourceViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +35,8 @@ abstract class SourceAdapter(
|
||||||
|
|
||||||
abstract suspend fun onItemClick(source: ShowResponse)
|
abstract suspend fun onItemClick(source: ShowResponse)
|
||||||
|
|
||||||
inner class SourceViewHolder(val binding: ItemCharacterBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class SourceViewHolder(val binding: ItemCharacterBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
dialogFragment.dismiss()
|
dialogFragment.dismiss()
|
||||||
|
|
|
@ -13,8 +13,8 @@ import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import ani.dantotsu.BottomSheetDialogFragment
|
import ani.dantotsu.BottomSheetDialogFragment
|
||||||
import ani.dantotsu.media.anime.AnimeSourceAdapter
|
|
||||||
import ani.dantotsu.databinding.BottomSheetSourceSearchBinding
|
import ani.dantotsu.databinding.BottomSheetSourceSearchBinding
|
||||||
|
import ani.dantotsu.media.anime.AnimeSourceAdapter
|
||||||
import ani.dantotsu.media.manga.MangaSourceAdapter
|
import ani.dantotsu.media.manga.MangaSourceAdapter
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
|
@ -38,7 +38,11 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() {
|
||||||
var id: Int? = null
|
var id: Int? = null
|
||||||
var media: Media? = null
|
var media: Media? = null
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetSourceSearchBinding.inflate(inflater, container, false)
|
_binding = BottomSheetSourceSearchBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -47,7 +51,8 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() {
|
||||||
binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
||||||
|
|
||||||
val scope = requireActivity().lifecycleScope
|
val scope = requireActivity().lifecycleScope
|
||||||
val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm =
|
||||||
|
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
model.getMedia().observe(viewLifecycleOwner) {
|
model.getMedia().observe(viewLifecycleOwner) {
|
||||||
media = it
|
media = it
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
|
@ -65,6 +70,7 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() {
|
||||||
anime = false
|
anime = false
|
||||||
(if (media!!.isAdult) HMangaSources else MangaSources)[i!!]
|
(if (media!!.isAdult) HMangaSources else MangaSources)[i!!]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search() {
|
fun search() {
|
||||||
binding.searchBarText.clearFocus()
|
binding.searchBarText.clearFocus()
|
||||||
imm.hideSoftInputFromWindow(binding.searchBarText.windowToken, 0)
|
imm.hideSoftInputFromWindow(binding.searchBarText.windowToken, 0)
|
||||||
|
@ -86,6 +92,7 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() {
|
||||||
search()
|
search()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,7 +108,11 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() {
|
||||||
else MangaSourceAdapter(j, model, i!!, media!!.id, this, scope)
|
else MangaSourceAdapter(j, model, i!!, media!!.id, this, scope)
|
||||||
binding.searchRecyclerView.layoutManager = GridLayoutManager(
|
binding.searchRecyclerView.layoutManager = GridLayoutManager(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
clamp(requireActivity().resources.displayMetrics.widthPixels / 124f.px, 1, 4)
|
clamp(
|
||||||
|
requireActivity().resources.displayMetrics.widthPixels / 124f.px,
|
||||||
|
1,
|
||||||
|
4
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,17 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.EmptyAdapter
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityStudioBinding
|
import ani.dantotsu.databinding.ActivityStudioBinding
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.initActivity
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.navBarHeight
|
||||||
import ani.dantotsu.others.LangSet
|
import ani.dantotsu.others.LangSet
|
||||||
|
import ani.dantotsu.others.getSerialized
|
||||||
|
import ani.dantotsu.px
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
package ani.dantotsu.media
|
package ani.dantotsu.media
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Environment
|
|
||||||
import ani.dantotsu.parsers.SubtitleType
|
import ani.dantotsu.parsers.SubtitleType
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.FileInputStream
|
|
||||||
|
|
||||||
class SubtitleDownloader {
|
class SubtitleDownloader {
|
||||||
|
|
||||||
|
@ -30,7 +25,7 @@ class SubtitleDownloader {
|
||||||
|
|
||||||
// Check if response is successful
|
// Check if response is successful
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val responseBody = response.body?.string()
|
val responseBody = response.body.string()
|
||||||
|
|
||||||
|
|
||||||
val subtitleType = when {
|
val subtitleType = when {
|
||||||
|
|
|
@ -5,8 +5,10 @@ import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.databinding.ItemTitleBinding
|
import ani.dantotsu.databinding.ItemTitleBinding
|
||||||
|
|
||||||
class TitleAdapter(private val text: String) : RecyclerView.Adapter<TitleAdapter.TitleViewHolder>() {
|
class TitleAdapter(private val text: String) :
|
||||||
inner class TitleViewHolder(val binding: ItemTitleBinding) : RecyclerView.ViewHolder(binding.root)
|
RecyclerView.Adapter<TitleAdapter.TitleViewHolder>() {
|
||||||
|
inner class TitleViewHolder(val binding: ItemTitleBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TitleViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TitleViewHolder {
|
||||||
val binding = ItemTitleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = ItemTitleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
|
import java.util.regex.Matcher
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
class AnimeNameAdapter {
|
||||||
|
companion object {
|
||||||
|
fun findSeasonNumber(text: String): Int? {
|
||||||
|
val seasonRegex = "(season|s)[\\s:.\\-]*(\\d+)"
|
||||||
|
val seasonPattern: Pattern = Pattern.compile(seasonRegex, Pattern.CASE_INSENSITIVE)
|
||||||
|
val seasonMatcher: Matcher = seasonPattern.matcher(text)
|
||||||
|
|
||||||
|
return if (seasonMatcher.find()) {
|
||||||
|
seasonMatcher.group(2)?.toInt()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,17 @@
|
||||||
package ani.dantotsu.media.anime
|
package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
|
@ -27,21 +21,11 @@ import ani.dantotsu.media.SourceSearchDialogFragment
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.DynamicAnimeParser
|
import ani.dantotsu.parsers.DynamicAnimeParser
|
||||||
import ani.dantotsu.parsers.WatchSources
|
import ani.dantotsu.parsers.WatchSources
|
||||||
import ani.dantotsu.settings.ExtensionsActivity
|
|
||||||
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
|
||||||
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
||||||
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.tabs.TabLayout
|
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.lang.IndexOutOfBoundsException
|
|
||||||
|
|
||||||
class AnimeWatchAdapter(
|
class AnimeWatchAdapter(
|
||||||
private val media: Media,
|
private val media: Media,
|
||||||
|
@ -72,22 +56,32 @@ class AnimeWatchAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceDubbed.isChecked = media.selected!!.preferDub
|
binding.animeSourceDubbed.isChecked = media.selected!!.preferDub
|
||||||
binding.animeSourceDubbedText.text = if (media.selected!!.preferDub) currActivity()!!.getString(R.string.dubbed) else currActivity()!!.getString(R.string.subbed)
|
binding.animeSourceDubbedText.text =
|
||||||
|
if (media.selected!!.preferDub) currActivity()!!.getString(R.string.dubbed) else currActivity()!!.getString(
|
||||||
|
R.string.subbed
|
||||||
|
)
|
||||||
|
|
||||||
//PreferDub
|
//PreferDub
|
||||||
var changing = false
|
var changing = false
|
||||||
binding.animeSourceDubbed.setOnCheckedChangeListener { _, isChecked ->
|
binding.animeSourceDubbed.setOnCheckedChangeListener { _, isChecked ->
|
||||||
binding.animeSourceDubbedText.text = if (isChecked) currActivity()!!.getString(R.string.dubbed) else currActivity()!!.getString(R.string.subbed)
|
binding.animeSourceDubbedText.text =
|
||||||
|
if (isChecked) currActivity()!!.getString(R.string.dubbed) else currActivity()!!.getString(
|
||||||
|
R.string.subbed
|
||||||
|
)
|
||||||
if (!changing) fragment.onDubClicked(isChecked)
|
if (!changing) fragment.onDubClicked(isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Wrong Title
|
//Wrong Title
|
||||||
binding.animeSourceSearch.setOnClickListener {
|
binding.animeSourceSearch.setOnClickListener {
|
||||||
SourceSearchDialogFragment().show(fragment.requireActivity().supportFragmentManager, null)
|
SourceSearchDialogFragment().show(
|
||||||
|
fragment.requireActivity().supportFragmentManager,
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Source Selection
|
//Source Selection
|
||||||
var source = media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it }
|
var source =
|
||||||
|
media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it }
|
||||||
setLanguageList(media.selected!!.langIndex, source)
|
setLanguageList(media.selected!!.langIndex, source)
|
||||||
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
||||||
binding.animeSource.setText(watchSources.names[source])
|
binding.animeSource.setText(watchSources.names[source])
|
||||||
|
@ -100,7 +94,13 @@ class AnimeWatchAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSource.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, watchSources.names))
|
binding.animeSource.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
fragment.requireContext(),
|
||||||
|
R.layout.item_dropdown,
|
||||||
|
watchSources.names
|
||||||
|
)
|
||||||
|
)
|
||||||
binding.animeSourceTitle.isSelected = true
|
binding.animeSourceTitle.isSelected = true
|
||||||
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
||||||
fragment.onSourceChange(i).apply {
|
fragment.onSourceChange(i).apply {
|
||||||
|
@ -109,7 +109,8 @@ class AnimeWatchAdapter(
|
||||||
changing = true
|
changing = true
|
||||||
binding.animeSourceDubbed.isChecked = selectDub
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
changing = false
|
changing = false
|
||||||
binding.animeSourceDubbedCont.visibility = if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
binding.animeSourceDubbedCont.visibility =
|
||||||
|
if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
||||||
source = i
|
source = i
|
||||||
setLanguageList(0, i)
|
setLanguageList(0, i)
|
||||||
}
|
}
|
||||||
|
@ -124,11 +125,13 @@ class AnimeWatchAdapter(
|
||||||
fragment.onLangChange(i)
|
fragment.onLangChange(i)
|
||||||
fragment.onSourceChange(media.selected!!.sourceIndex).apply {
|
fragment.onSourceChange(media.selected!!.sourceIndex).apply {
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.animeSourceTitle.text = showUserText
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener =
|
||||||
|
{ MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||||
changing = true
|
changing = true
|
||||||
binding.animeSourceDubbed.isChecked = selectDub
|
binding.animeSourceDubbed.isChecked = selectDub
|
||||||
changing = false
|
changing = false
|
||||||
binding.animeSourceDubbedCont.visibility = if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
binding.animeSourceDubbedCont.visibility =
|
||||||
|
if (isDubAvailableSeparately) View.VISIBLE else View.GONE
|
||||||
setLanguageList(i, source)
|
setLanguageList(i, source)
|
||||||
}
|
}
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
|
@ -219,14 +222,26 @@ class AnimeWatchAdapter(
|
||||||
for (position in arr.indices) {
|
for (position in arr.indices) {
|
||||||
val last = if (position + 1 == arr.size) names.size else (limit * (position + 1))
|
val last = if (position + 1 == arr.size) names.size else (limit * (position + 1))
|
||||||
val chip =
|
val chip =
|
||||||
ItemChipBinding.inflate(LayoutInflater.from(fragment.context), binding.animeSourceChipGroup, false).root
|
ItemChipBinding.inflate(
|
||||||
|
LayoutInflater.from(fragment.context),
|
||||||
|
binding.animeSourceChipGroup,
|
||||||
|
false
|
||||||
|
).root
|
||||||
chip.isCheckable = true
|
chip.isCheckable = true
|
||||||
fun selected() {
|
fun selected() {
|
||||||
chip.isChecked = true
|
chip.isChecked = true
|
||||||
binding.animeWatchChipScroll.smoothScrollTo((chip.left - screenWidth / 2) + (chip.width / 2), 0)
|
binding.animeWatchChipScroll.smoothScrollTo(
|
||||||
|
(chip.left - screenWidth / 2) + (chip.width / 2),
|
||||||
|
0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
|
chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
|
||||||
chip.setTextColor(ContextCompat.getColorStateList(fragment.requireContext(), R.color.chip_text_color))
|
chip.setTextColor(
|
||||||
|
ContextCompat.getColorStateList(
|
||||||
|
fragment.requireContext(),
|
||||||
|
R.color.chip_text_color
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
chip.setOnClickListener {
|
chip.setOnClickListener {
|
||||||
selected()
|
selected()
|
||||||
|
@ -239,7 +254,14 @@ class AnimeWatchAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (select != null)
|
if (select != null)
|
||||||
binding.animeWatchChipScroll.apply { post { scrollTo((select.left - screenWidth / 2) + (select.width / 2), 0) } }
|
binding.animeWatchChipScroll.apply {
|
||||||
|
post {
|
||||||
|
scrollTo(
|
||||||
|
(select.left - screenWidth / 2) + (select.width / 2),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +303,9 @@ class AnimeWatchAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val ep = media.anime.episodes!![continueEp]!!
|
val ep = media.anime.episodes!![continueEp]!!
|
||||||
binding.itemEpisodeImage.loadImage(ep.thumb ?: FileUrl[media.banner ?: media.cover], 0)
|
binding.itemEpisodeImage.loadImage(
|
||||||
|
ep.thumb ?: FileUrl[media.banner ?: media.cover], 0
|
||||||
|
)
|
||||||
if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE
|
if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE
|
||||||
binding.animeSourceContinueText.text =
|
binding.animeSourceContinueText.text =
|
||||||
currActivity()!!.getString(R.string.continue_episode) + "${ep.number}${if (ep.filler) " - Filler" else ""}${if (ep.title != null) "\n${ep.title}" else ""}"
|
currActivity()!!.getString(R.string.continue_episode) + "${ep.number}${if (ep.filler) " - Filler" else ""}${if (ep.title != null) "\n${ep.title}" else ""}"
|
||||||
|
@ -322,9 +346,16 @@ class AnimeWatchAdapter(
|
||||||
try {
|
try {
|
||||||
binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang)
|
binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang)
|
||||||
} catch (e: IndexOutOfBoundsException) {
|
} catch (e: IndexOutOfBoundsException) {
|
||||||
binding?.animeSourceLanguage?.setText(parser.extension.sources.firstOrNull()?.lang ?: "Unknown")
|
binding?.animeSourceLanguage?.setText(
|
||||||
|
parser.extension.sources.firstOrNull()?.lang ?: "Unknown"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
binding?.animeSourceLanguage?.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, parser.extension.sources.map { it.lang }))
|
binding?.animeSourceLanguage?.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
fragment.requireContext(),
|
||||||
|
R.layout.item_dropdown,
|
||||||
|
parser.extension.sources.map { it.lang })
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,7 +363,8 @@ class AnimeWatchAdapter(
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ItemAnimeWatchBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class ViewHolder(val binding: ItemAnimeWatchBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
//Timer
|
//Timer
|
||||||
countDown(media, binding.animeSourceContainer)
|
countDown(media, binding.animeSourceContainer)
|
||||||
|
|
|
@ -8,8 +8,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.math.MathUtils
|
import androidx.core.math.MathUtils
|
||||||
|
@ -28,8 +26,6 @@ import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.parsers.AnimeParser
|
import ani.dantotsu.parsers.AnimeParser
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.parsers.AnimeSources
|
||||||
import ani.dantotsu.parsers.HAnimeSources
|
import ani.dantotsu.parsers.HAnimeSources
|
||||||
import ani.dantotsu.settings.ExtensionsActivity
|
|
||||||
import ani.dantotsu.settings.InstalledAnimeExtensionsFragment
|
|
||||||
import ani.dantotsu.settings.PlayerSettings
|
import ani.dantotsu.settings.PlayerSettings
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||||
|
@ -40,17 +36,12 @@ import ani.dantotsu.subcriptions.SubscriptionHelper
|
||||||
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
|
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.navigationrail.NavigationRailView
|
import com.google.android.material.navigationrail.NavigationRailView
|
||||||
import com.google.android.material.tabs.TabLayout
|
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
@ -96,8 +87,10 @@ class AnimeWatchFragment : Fragment() {
|
||||||
maxGridSize = max(4, maxGridSize - (maxGridSize % 2))
|
maxGridSize = max(4, maxGridSize - (maxGridSize % 2))
|
||||||
|
|
||||||
playerSettings =
|
playerSettings =
|
||||||
loadData("player_settings", toast = false) ?: PlayerSettings().apply { saveData("player_settings", this) }
|
loadData("player_settings", toast = false)
|
||||||
uiSettings = loadData("ui_settings", toast = false) ?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
?: PlayerSettings().apply { saveData("player_settings", this) }
|
||||||
|
uiSettings = loadData("ui_settings", toast = false)
|
||||||
|
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
||||||
|
|
||||||
val gridLayoutManager = GridLayoutManager(requireContext(), maxGridSize)
|
val gridLayoutManager = GridLayoutManager(requireContext(), maxGridSize)
|
||||||
|
|
||||||
|
@ -129,7 +122,8 @@ class AnimeWatchFragment : Fragment() {
|
||||||
media = it
|
media = it
|
||||||
media.selected = model.loadSelected(media)
|
media.selected = model.loadSelected(media)
|
||||||
|
|
||||||
subscribed = SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id)
|
subscribed =
|
||||||
|
SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id)
|
||||||
|
|
||||||
style = media.selected!!.recyclerStyle
|
style = media.selected!!.recyclerStyle
|
||||||
reverse = media.selected!!.recyclerReversed
|
reverse = media.selected!!.recyclerReversed
|
||||||
|
@ -141,9 +135,11 @@ class AnimeWatchFragment : Fragment() {
|
||||||
model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources
|
model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources
|
||||||
|
|
||||||
headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!)
|
headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!)
|
||||||
episodeAdapter = EpisodeAdapter(style ?: uiSettings.animeDefaultView, media, this)
|
episodeAdapter =
|
||||||
|
EpisodeAdapter(style ?: uiSettings.animeDefaultView, media, this)
|
||||||
|
|
||||||
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, episodeAdapter)
|
binding.animeSourceRecycler.adapter =
|
||||||
|
ConcatAdapter(headerAdapter, episodeAdapter)
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
awaitAll(
|
awaitAll(
|
||||||
|
@ -165,15 +161,20 @@ class AnimeWatchFragment : Fragment() {
|
||||||
episodes.forEach { (i, episode) ->
|
episodes.forEach { (i, episode) ->
|
||||||
if (media.anime?.fillerEpisodes != null) {
|
if (media.anime?.fillerEpisodes != null) {
|
||||||
if (media.anime!!.fillerEpisodes!!.containsKey(i)) {
|
if (media.anime!!.fillerEpisodes!!.containsKey(i)) {
|
||||||
episode.title = episode.title ?: media.anime!!.fillerEpisodes!![i]?.title
|
episode.title =
|
||||||
|
episode.title ?: media.anime!!.fillerEpisodes!![i]?.title
|
||||||
episode.filler = media.anime!!.fillerEpisodes!![i]?.filler ?: false
|
episode.filler = media.anime!!.fillerEpisodes!![i]?.filler ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (media.anime?.kitsuEpisodes != null) {
|
if (media.anime?.kitsuEpisodes != null) {
|
||||||
if (media.anime!!.kitsuEpisodes!!.containsKey(i)) {
|
if (media.anime!!.kitsuEpisodes!!.containsKey(i)) {
|
||||||
episode.desc = episode.desc ?: media.anime!!.kitsuEpisodes!![i]?.desc
|
episode.desc =
|
||||||
episode.title = episode.title ?: media.anime!!.kitsuEpisodes!![i]?.title
|
episode.desc ?: media.anime!!.kitsuEpisodes!![i]?.desc
|
||||||
episode.thumb = episode.thumb ?: media.anime!!.kitsuEpisodes!![i]?.thumb ?: FileUrl[media.cover]
|
episode.title =
|
||||||
|
episode.title ?: media.anime!!.kitsuEpisodes!![i]?.title
|
||||||
|
episode.thumb =
|
||||||
|
episode.thumb ?: media.anime!!.kitsuEpisodes!![i]?.thumb
|
||||||
|
?: FileUrl[media.cover]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,7 +248,12 @@ class AnimeWatchFragment : Fragment() {
|
||||||
selected.preferDub = checked
|
selected.preferDub = checked
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
media.selected = selected
|
media.selected = selected
|
||||||
lifecycleScope.launch(Dispatchers.IO) { model.forceLoadEpisode(media, selected.sourceIndex) }
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
model.forceLoadEpisode(
|
||||||
|
media,
|
||||||
|
selected.sourceIndex
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadEpisodes(i: Int, invalidate: Boolean) {
|
fun loadEpisodes(i: Int, invalidate: Boolean) {
|
||||||
|
@ -289,6 +295,7 @@ class AnimeWatchFragment : Fragment() {
|
||||||
else getString(R.string.unsubscribed_notification)
|
else getString(R.string.unsubscribed_notification)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openSettings(pkg: AnimeExtension.Installed) {
|
fun openSettings(pkg: AnimeExtension.Installed) {
|
||||||
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
||||||
val activity = requireActivity() as MediaDetailsActivity
|
val activity = requireActivity() as MediaDetailsActivity
|
||||||
|
|
|
@ -41,8 +41,22 @@ class EpisodeAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return (when (viewType) {
|
return (when (viewType) {
|
||||||
0 -> EpisodeListViewHolder(ItemEpisodeListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
0 -> EpisodeListViewHolder(
|
||||||
1 -> EpisodeGridViewHolder(ItemEpisodeGridBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
ItemEpisodeListBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
1 -> EpisodeGridViewHolder(
|
||||||
|
ItemEpisodeGridBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
2 -> EpisodeCompactViewHolder(
|
2 -> EpisodeCompactViewHolder(
|
||||||
ItemEpisodeCompactBinding.inflate(
|
ItemEpisodeCompactBinding.inflate(
|
||||||
LayoutInflater.from(parent.context),
|
LayoutInflater.from(parent.context),
|
||||||
|
@ -50,6 +64,7 @@ class EpisodeAdapter(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> throw IllegalArgumentException()
|
else -> throw IllegalArgumentException()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -62,15 +77,21 @@ class EpisodeAdapter(
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
val ep = arr[position]
|
val ep = arr[position]
|
||||||
val title =
|
val title =
|
||||||
"${if (!ep.title.isNullOrEmpty() && ep.title != "null") "" else currContext()!!.getString(R.string.episode_singular)} ${if (!ep.title.isNullOrEmpty() && ep.title != "null") ep.title else ep.number}"
|
"${
|
||||||
|
if (!ep.title.isNullOrEmpty() && ep.title != "null") "" else currContext()!!.getString(
|
||||||
|
R.string.episode_singular
|
||||||
|
)
|
||||||
|
} ${if (!ep.title.isNullOrEmpty() && ep.title != "null") ep.title else ep.number}"
|
||||||
|
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is EpisodeListViewHolder -> {
|
is EpisodeListViewHolder -> {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
||||||
|
|
||||||
val thumb = ep.thumb?.let { if(it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
|
val thumb =
|
||||||
Glide.with(binding.itemEpisodeImage).load(thumb?:media.cover).override(400,0).into(binding.itemEpisodeImage)
|
ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
|
||||||
|
Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0)
|
||||||
|
.into(binding.itemEpisodeImage)
|
||||||
binding.itemEpisodeNumber.text = ep.number
|
binding.itemEpisodeNumber.text = ep.number
|
||||||
binding.itemEpisodeTitle.text = title
|
binding.itemEpisodeTitle.text = title
|
||||||
|
|
||||||
|
@ -81,7 +102,8 @@ class EpisodeAdapter(
|
||||||
binding.itemEpisodeFiller.visibility = View.GONE
|
binding.itemEpisodeFiller.visibility = View.GONE
|
||||||
binding.itemEpisodeFillerView.visibility = View.GONE
|
binding.itemEpisodeFillerView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.itemEpisodeDesc.visibility = if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE
|
binding.itemEpisodeDesc.visibility =
|
||||||
|
if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE
|
||||||
binding.itemEpisodeDesc.text = ep.desc ?: ""
|
binding.itemEpisodeDesc.text = ep.desc ?: ""
|
||||||
|
|
||||||
if (media.userProgress != null) {
|
if (media.userProgress != null) {
|
||||||
|
@ -114,8 +136,10 @@ class EpisodeAdapter(
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
||||||
|
|
||||||
val thumb = ep.thumb?.let { if(it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
|
val thumb =
|
||||||
Glide.with(binding.itemEpisodeImage).load(thumb?:media.cover).override(400,0).into(binding.itemEpisodeImage)
|
ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null }
|
||||||
|
Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0)
|
||||||
|
.into(binding.itemEpisodeImage)
|
||||||
|
|
||||||
binding.itemEpisodeNumber.text = ep.number
|
binding.itemEpisodeNumber.text = ep.number
|
||||||
binding.itemEpisodeTitle.text = title
|
binding.itemEpisodeTitle.text = title
|
||||||
|
@ -155,7 +179,8 @@ class EpisodeAdapter(
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
||||||
binding.itemEpisodeNumber.text = ep.number
|
binding.itemEpisodeNumber.text = ep.number
|
||||||
binding.itemEpisodeFillerView.visibility = if (ep.filler) View.VISIBLE else View.GONE
|
binding.itemEpisodeFillerView.visibility =
|
||||||
|
if (ep.filler) View.VISIBLE else View.GONE
|
||||||
if (media.userProgress != null) {
|
if (media.userProgress != null) {
|
||||||
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat())
|
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat())
|
||||||
binding.itemEpisodeViewedCover.visibility = View.VISIBLE
|
binding.itemEpisodeViewedCover.visibility = View.VISIBLE
|
||||||
|
@ -180,7 +205,8 @@ class EpisodeAdapter(
|
||||||
|
|
||||||
override fun getItemCount(): Int = arr.size
|
override fun getItemCount(): Int = arr.size
|
||||||
|
|
||||||
inner class EpisodeCompactViewHolder(val binding: ItemEpisodeCompactBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class EpisodeCompactViewHolder(val binding: ItemEpisodeCompactBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
|
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
|
||||||
|
@ -189,7 +215,8 @@ class EpisodeAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class EpisodeGridViewHolder(val binding: ItemEpisodeGridBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class EpisodeGridViewHolder(val binding: ItemEpisodeGridBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
|
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
|
||||||
|
@ -198,7 +225,8 @@ class EpisodeAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class EpisodeListViewHolder(val binding: ItemEpisodeListBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class EpisodeListViewHolder(val binding: ItemEpisodeListBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
|
if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0)
|
||||||
|
|
|
@ -17,7 +17,6 @@ import android.graphics.drawable.Animatable
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.AudioManager.*
|
import android.media.AudioManager.*
|
||||||
import android.media.PlaybackParams
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -62,6 +61,8 @@ import ani.dantotsu.*
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.discord.Discord
|
import ani.dantotsu.connections.discord.Discord
|
||||||
|
import ani.dantotsu.connections.discord.DiscordService
|
||||||
|
import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton
|
||||||
import ani.dantotsu.connections.discord.RPC
|
import ani.dantotsu.connections.discord.RPC
|
||||||
import ani.dantotsu.connections.updateProgress
|
import ani.dantotsu.connections.updateProgress
|
||||||
import ani.dantotsu.databinding.ActivityExoplayerBinding
|
import ani.dantotsu.databinding.ActivityExoplayerBinding
|
||||||
|
@ -71,6 +72,7 @@ import ani.dantotsu.media.SubtitleDownloader
|
||||||
import ani.dantotsu.others.AniSkip
|
import ani.dantotsu.others.AniSkip
|
||||||
import ani.dantotsu.others.AniSkip.getType
|
import ani.dantotsu.others.AniSkip.getType
|
||||||
import ani.dantotsu.others.Download.download
|
import ani.dantotsu.others.Download.download
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.others.ResettableTimer
|
import ani.dantotsu.others.ResettableTimer
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.parsers.*
|
import ani.dantotsu.parsers.*
|
||||||
|
@ -78,7 +80,6 @@ import ani.dantotsu.settings.PlayerSettings
|
||||||
import ani.dantotsu.settings.PlayerSettingsActivity
|
import ani.dantotsu.settings.PlayerSettingsActivity
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
@ -813,15 +814,15 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
|
|
||||||
fun fastForward() {
|
fun fastForward() {
|
||||||
isFastForwarding = true
|
isFastForwarding = true
|
||||||
exoPlayer.setPlaybackSpeed(2f)
|
exoPlayer.setPlaybackSpeed(exoPlayer.playbackParameters.speed * 2)
|
||||||
snackString("Playing at 2x speed")
|
snackString("Playing at ${exoPlayer.playbackParameters.speed}x speed")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopFastForward() {
|
fun stopFastForward() {
|
||||||
if (isFastForwarding) {
|
if (isFastForwarding) {
|
||||||
isFastForwarding = false
|
isFastForwarding = false
|
||||||
exoPlayer.setPlaybackSpeed(1f)
|
exoPlayer.setPlaybackSpeed(exoPlayer.playbackParameters.speed / 2)
|
||||||
snackString("Playing at normal speed")
|
snackString("Playing at default speed: ${exoPlayer.playbackParameters.speed}x")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,6 +863,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
override fun onLongClick(event: MotionEvent) {
|
override fun onLongClick(event: MotionEvent) {
|
||||||
if (settings.fastforward) fastForward()
|
if (settings.fastforward) fastForward()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDoubleClick(event: MotionEvent) {
|
override fun onDoubleClick(event: MotionEvent) {
|
||||||
doubleTap(true, event)
|
doubleTap(true, event)
|
||||||
}
|
}
|
||||||
|
@ -994,22 +996,40 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
playbackPosition = loadData("${media.id}_${ep.number}", this) ?: 0
|
playbackPosition = loadData("${media.id}_${ep.number}", this) ?: 0
|
||||||
initPlayer()
|
initPlayer()
|
||||||
preloading = false
|
preloading = false
|
||||||
rpc = Discord.defaultRPC()
|
val context = this
|
||||||
rpc?.send {
|
|
||||||
type = RPC.Type.WATCHING
|
lifecycleScope.launch {
|
||||||
activityName = media.userPreferredName
|
val presence = RPC.createPresence(RPC.Companion.RPCData(
|
||||||
|
applicationId = Discord.application_Id,
|
||||||
|
type = RPC.Type.WATCHING,
|
||||||
|
activityName = media.userPreferredName,
|
||||||
details = ep.title?.takeIf { it.isNotEmpty() } ?: getString(
|
details = ep.title?.takeIf { it.isNotEmpty() } ?: getString(
|
||||||
R.string.episode_num,
|
R.string.episode_num,
|
||||||
ep.number
|
ep.number
|
||||||
|
),
|
||||||
|
state = "Episode : ${ep.number}/${media.anime?.totalEpisodes ?: "??"}",
|
||||||
|
largeImage = media.cover?.let { RPC.Link(media.userPreferredName, it) },
|
||||||
|
smallImage = RPC.Link(
|
||||||
|
"Dantotsu",
|
||||||
|
Discord.small_Image
|
||||||
|
),
|
||||||
|
buttons = mutableListOf(
|
||||||
|
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
||||||
|
RPC.Link(
|
||||||
|
"Stream on Dantotsu",
|
||||||
|
"https://github.com/rebelonion/Dantotsu/"
|
||||||
)
|
)
|
||||||
state = "Episode : ${ep.number}/${media.anime?.totalEpisodes ?: "??"}"
|
)
|
||||||
media.cover?.let { cover ->
|
)
|
||||||
largeImage = RPC.Link(media.userPreferredName, cover)
|
)
|
||||||
}
|
|
||||||
media.shareLink?.let { link ->
|
val intent = Intent(context, DiscordService::class.java).apply {
|
||||||
buttons.add(0, RPC.Link(getString(R.string.view_anime), link))
|
putExtra("presence", presence)
|
||||||
}
|
}
|
||||||
|
DiscordServiceRunningSingleton.running = true
|
||||||
|
startService(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress()
|
updateProgress()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1129,7 +1149,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog")
|
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog")
|
||||||
?: true else false
|
?: true else false
|
||||||
if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true)
|
if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true)
|
||||||
AlertDialog.Builder(this, R.style.DialogTheme)
|
AlertDialog.Builder(this, R.style.MyPopup)
|
||||||
.setTitle(getString(R.string.auto_update, media.userPreferredName))
|
.setTitle(getString(R.string.auto_update, media.userPreferredName))
|
||||||
.apply {
|
.apply {
|
||||||
setOnCancelListener { hideSystemBars() }
|
setOnCancelListener { hideSystemBars() }
|
||||||
|
@ -1278,6 +1298,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
|
val builder = MediaItem.Builder().setUri(video!!.file.url).setMimeType(mimeType)
|
||||||
|
logger("url: ${video!!.file.url}")
|
||||||
|
logger("mimeType: $mimeType")
|
||||||
|
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
val listofnotnullsubs = immutableListOf(sub).filterNotNull()
|
val listofnotnullsubs = immutableListOf(sub).filterNotNull()
|
||||||
|
@ -1301,7 +1323,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
.setAllowMultipleAdaptiveSelections(true)
|
.setAllowMultipleAdaptiveSelections(true)
|
||||||
.setPreferredTextLanguage(subtitle?.language ?: "en")
|
.setPreferredTextLanguage(subtitle?.language ?: "en")
|
||||||
.setPreferredTextRoleFlags(C.ROLE_FLAG_SUBTITLE)
|
.setPreferredTextRoleFlags(C.ROLE_FLAG_SUBTITLE)
|
||||||
.setRendererDisabled(C.TRACK_TYPE_VIDEO, false)
|
.setRendererDisabled(TRACK_TYPE_VIDEO, false)
|
||||||
.setRendererDisabled(C.TRACK_TYPE_AUDIO, false)
|
.setRendererDisabled(C.TRACK_TYPE_AUDIO, false)
|
||||||
.setRendererDisabled(C.TRACK_TYPE_TEXT, false)
|
.setRendererDisabled(C.TRACK_TYPE_TEXT, false)
|
||||||
.setMinVideoSize(
|
.setMinVideoSize(
|
||||||
|
@ -1404,7 +1426,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
exoPlayer.release()
|
exoPlayer.release()
|
||||||
VideoCache.release()
|
VideoCache.release()
|
||||||
mediaSession?.release()
|
mediaSession?.release()
|
||||||
rpc?.close()
|
val stopIntent = Intent(this, DiscordService::class.java)
|
||||||
|
DiscordServiceRunningSingleton.running = false
|
||||||
|
stopService(stopIntent)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
@ -1593,13 +1618,15 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||||
playerView.player?.trackSelectionParameters =
|
playerView.player?.trackSelectionParameters =
|
||||||
playerView.player?.trackSelectionParameters?.buildUpon()
|
playerView.player?.trackSelectionParameters?.buildUpon()
|
||||||
?.setOverrideForType(
|
?.setOverrideForType(
|
||||||
TrackSelectionOverride(it.mediaTrackGroup, it.length - 1))
|
TrackSelectionOverride(it.mediaTrackGroup, it.length - 1)
|
||||||
|
)
|
||||||
?.build()!!
|
?.build()!!
|
||||||
} else if (it.type == 3) {
|
} else if (it.type == 3) {
|
||||||
playerView.player?.trackSelectionParameters =
|
playerView.player?.trackSelectionParameters =
|
||||||
playerView.player?.trackSelectionParameters?.buildUpon()
|
playerView.player?.trackSelectionParameters?.buildUpon()
|
||||||
?.addOverride(
|
?.addOverride(
|
||||||
TrackSelectionOverride(it.mediaTrackGroup, listOf()))
|
TrackSelectionOverride(it.mediaTrackGroup, listOf())
|
||||||
|
)
|
||||||
?.build()!!
|
?.build()!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,6 @@ import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.others.Download.download
|
import ani.dantotsu.others.Download.download
|
||||||
import ani.dantotsu.parsers.VideoExtractor
|
import ani.dantotsu.parsers.VideoExtractor
|
||||||
import ani.dantotsu.parsers.VideoType
|
import ani.dantotsu.parsers.VideoType
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -54,7 +52,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetSelectorBinding.inflate(inflater, container, false)
|
_binding = BottomSheetSelectorBinding.inflate(inflater, container, false)
|
||||||
val window = dialog?.window
|
val window = dialog?.window
|
||||||
window?.statusBarColor = Color.TRANSPARENT
|
window?.statusBarColor = Color.TRANSPARENT
|
||||||
|
@ -92,10 +94,13 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
val size = ep.extractors?.find { it.server.name == selected }?.videos?.size
|
val size =
|
||||||
|
ep.extractors?.find { it.server.name == selected }?.videos?.size
|
||||||
if (size != null && size >= media!!.selected!!.video) {
|
if (size != null && size >= media!!.selected!!.video) {
|
||||||
media!!.anime!!.episodes?.get(media!!.anime!!.selectedEpisode!!)?.selectedExtractor = selected
|
media!!.anime!!.episodes?.get(media!!.anime!!.selectedEpisode!!)?.selectedExtractor =
|
||||||
media!!.anime!!.episodes?.get(media!!.anime!!.selectedEpisode!!)?.selectedVideo = media!!.selected!!.video
|
selected
|
||||||
|
media!!.anime!!.episodes?.get(media!!.anime!!.selectedEpisode!!)?.selectedVideo =
|
||||||
|
media!!.selected!!.video
|
||||||
startExoplayer(media!!)
|
startExoplayer(media!!)
|
||||||
} else fail()
|
} else fail()
|
||||||
}
|
}
|
||||||
|
@ -116,8 +121,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
}) fail()
|
}) fail()
|
||||||
}
|
}
|
||||||
} else load()
|
} else load()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
binding.selectorRecyclerView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.selectorRecyclerView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
bottomMargin = navBarHeight
|
bottomMargin = navBarHeight
|
||||||
}
|
}
|
||||||
|
@ -130,7 +134,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
saveData("make_default", makeDefault)
|
saveData("make_default", makeDefault)
|
||||||
}
|
}
|
||||||
binding.selectorRecyclerView.layoutManager =
|
binding.selectorRecyclerView.layoutManager =
|
||||||
LinearLayoutManager(requireActivity(), LinearLayoutManager.VERTICAL, false)
|
LinearLayoutManager(
|
||||||
|
requireActivity(),
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
val adapter = ExtractorAdapter()
|
val adapter = ExtractorAdapter()
|
||||||
binding.selectorRecyclerView.adapter = adapter
|
binding.selectorRecyclerView.adapter = adapter
|
||||||
if (!ep.allStreams) {
|
if (!ep.allStreams) {
|
||||||
|
@ -141,7 +149,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
model.getEpisode().observe(this) {
|
model.getEpisode().observe(this) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
media!!.anime?.episodes?.set(media!!.anime?.selectedEpisode!!, ep)
|
media!!.anime?.episodes?.set(
|
||||||
|
media!!.anime?.selectedEpisode!!,
|
||||||
|
ep
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
|
@ -175,7 +186,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
ExoplayerView.initialized = true
|
ExoplayerView.initialized = true
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} else {
|
} else {
|
||||||
model.setEpisode(media.anime!!.episodes!![media.anime.selectedEpisode!!]!!, "startExo no launch")
|
model.setEpisode(
|
||||||
|
media.anime!!.episodes!![media.anime.selectedEpisode!!]!!,
|
||||||
|
"startExo no launch"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,10 +200,17 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ExtractorAdapter : RecyclerView.Adapter<ExtractorAdapter.StreamViewHolder>() {
|
private inner class ExtractorAdapter :
|
||||||
|
RecyclerView.Adapter<ExtractorAdapter.StreamViewHolder>() {
|
||||||
val links = mutableListOf<VideoExtractor>()
|
val links = mutableListOf<VideoExtractor>()
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StreamViewHolder =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StreamViewHolder =
|
||||||
StreamViewHolder(ItemStreamBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
StreamViewHolder(
|
||||||
|
ItemStreamBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
|
||||||
val extractor = links[position]
|
val extractor = links[position]
|
||||||
|
@ -214,26 +235,37 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
notifyItemRangeInserted(0, extractors.size)
|
notifyItemRangeInserted(0, extractors.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class StreamViewHolder(val binding: ItemStreamBinding) : RecyclerView.ViewHolder(binding.root)
|
private inner class StreamViewHolder(val binding: ItemStreamBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class VideoAdapter(private val extractor : VideoExtractor) : RecyclerView.Adapter<VideoAdapter.UrlViewHolder>() {
|
private inner class VideoAdapter(private val extractor: VideoExtractor) :
|
||||||
|
RecyclerView.Adapter<VideoAdapter.UrlViewHolder>() {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UrlViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UrlViewHolder {
|
||||||
return UrlViewHolder(ItemUrlBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
return UrlViewHolder(
|
||||||
|
ItemUrlBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: UrlViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: UrlViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
val video = extractor.videos[position]
|
val video = extractor.videos[position]
|
||||||
binding.urlQuality.text = if(video.quality!=null) "${video.quality}p" else "Default Quality"
|
binding.urlQuality.text =
|
||||||
|
if (video.quality != null) "${video.quality}p" else "Default Quality"
|
||||||
binding.urlNote.text = video.extraNote ?: ""
|
binding.urlNote.text = video.extraNote ?: ""
|
||||||
binding.urlNote.visibility = if (video.extraNote != null) View.VISIBLE else View.GONE
|
binding.urlNote.visibility = if (video.extraNote != null) View.VISIBLE else View.GONE
|
||||||
binding.urlDownload.visibility = View.VISIBLE
|
binding.urlDownload.visibility = View.VISIBLE
|
||||||
binding.urlDownload.setSafeOnClickListener {
|
binding.urlDownload.setSafeOnClickListener {
|
||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor = extractor.server.name
|
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedExtractor =
|
||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo = position
|
extractor.server.name
|
||||||
|
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]!!.selectedVideo =
|
||||||
|
position
|
||||||
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
download(
|
download(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
|
@ -246,9 +278,10 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE
|
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE
|
||||||
binding.urlSize.text =
|
binding.urlSize.text =
|
||||||
// if video size is null or 0, show "Unknown Size" else show the size in MB
|
// if video size is null or 0, show "Unknown Size" else show the size in MB
|
||||||
(if (video.extraNote != null) " : " else "") + (if (video.size == 0.0) "Unknown Size" else (DecimalFormat("#.##").format(video.size ?: 0).toString()+ " MB"))
|
(if (video.extraNote != null) " : " else "") + (if (video.size == 0.0) "Unknown Size" else (DecimalFormat(
|
||||||
}
|
"#.##"
|
||||||
else {
|
).format(video.size ?: 0).toString() + " MB"))
|
||||||
|
} else {
|
||||||
binding.urlQuality.text = "Multi Quality"
|
binding.urlQuality.text = "Multi Quality"
|
||||||
if ((loadData<Int>("settings_download_manager") ?: 0) == 0) {
|
if ((loadData<Int>("settings_download_manager") ?: 0) == 0) {
|
||||||
binding.urlDownload.visibility = View.GONE
|
binding.urlDownload.visibility = View.GONE
|
||||||
|
@ -258,12 +291,15 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
override fun getItemCount(): Int = extractor.videos.size
|
override fun getItemCount(): Int = extractor.videos.size
|
||||||
|
|
||||||
private inner class UrlViewHolder(val binding: ItemUrlBinding) : RecyclerView.ViewHolder(binding.root) {
|
private inner class UrlViewHolder(val binding: ItemUrlBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
init {
|
init {
|
||||||
itemView.setSafeOnClickListener {
|
itemView.setSafeOnClickListener {
|
||||||
tryWith(true) {
|
tryWith(true) {
|
||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedExtractor = extractor.server.name
|
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedExtractor =
|
||||||
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedVideo = bindingAdapterPosition
|
extractor.server.name
|
||||||
|
media!!.anime!!.episodes!![media!!.anime!!.selectedEpisode!!]?.selectedVideo =
|
||||||
|
bindingAdapterPosition
|
||||||
if (makeDefault) {
|
if (makeDefault) {
|
||||||
media!!.selected!!.server = extractor.server.name
|
media!!.selected!!.server = extractor.server.name
|
||||||
media!!.selected!!.video = bindingAdapterPosition
|
media!!.selected!!.video = bindingAdapterPosition
|
||||||
|
@ -287,7 +323,11 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(server: String? = null, la: Boolean = true, prev: String? = null): SelectorDialogFragment =
|
fun newInstance(
|
||||||
|
server: String? = null,
|
||||||
|
la: Boolean = true,
|
||||||
|
prev: String? = null
|
||||||
|
): SelectorDialogFragment =
|
||||||
SelectorDialogFragment().apply {
|
SelectorDialogFragment().apply {
|
||||||
arguments = Bundle().apply {
|
arguments = Bundle().apply {
|
||||||
putString("server", server)
|
putString("server", server)
|
||||||
|
|
|
@ -24,7 +24,11 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
||||||
val model: MediaDetailsViewModel by activityViewModels()
|
val model: MediaDetailsViewModel by activityViewModels()
|
||||||
private lateinit var episode: Episode
|
private lateinit var episode: Episode
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetSubtitlesBinding.inflate(inflater, container, false)
|
_binding = BottomSheetSubtitlesBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -34,17 +38,27 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
model.getMedia().observe(viewLifecycleOwner) { media ->
|
model.getMedia().observe(viewLifecycleOwner) { media ->
|
||||||
episode = media?.anime?.episodes?.get(media.anime.selectedEpisode) ?: return@observe
|
episode = media?.anime?.episodes?.get(media.anime.selectedEpisode) ?: return@observe
|
||||||
val currentExtractor = episode.extractors?.find { it.server.name == episode.selectedExtractor } ?: return@observe
|
val currentExtractor =
|
||||||
|
episode.extractors?.find { it.server.name == episode.selectedExtractor }
|
||||||
|
?: return@observe
|
||||||
binding.subtitlesRecycler.layoutManager = LinearLayoutManager(requireContext())
|
binding.subtitlesRecycler.layoutManager = LinearLayoutManager(requireContext())
|
||||||
binding.subtitlesRecycler.adapter = SubtitleAdapter(currentExtractor.subtitles)
|
binding.subtitlesRecycler.adapter = SubtitleAdapter(currentExtractor.subtitles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class SubtitleAdapter(val subtitles: List<Subtitle>) : RecyclerView.Adapter<SubtitleAdapter.StreamViewHolder>() {
|
inner class SubtitleAdapter(val subtitles: List<Subtitle>) :
|
||||||
inner class StreamViewHolder(val binding: ItemSubtitleTextBinding) : RecyclerView.ViewHolder(binding.root)
|
RecyclerView.Adapter<SubtitleAdapter.StreamViewHolder>() {
|
||||||
|
inner class StreamViewHolder(val binding: ItemSubtitleTextBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StreamViewHolder =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StreamViewHolder =
|
||||||
StreamViewHolder(ItemSubtitleTextBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
StreamViewHolder(
|
||||||
|
ItemSubtitleTextBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
|
|
|
@ -14,7 +14,10 @@ object VideoCache {
|
||||||
val databaseProvider = StandaloneDatabaseProvider(context)
|
val databaseProvider = StandaloneDatabaseProvider(context)
|
||||||
if (simpleCache == null)
|
if (simpleCache == null)
|
||||||
simpleCache = SimpleCache(
|
simpleCache = SimpleCache(
|
||||||
File(context.cacheDir, "exoplayer").also { it.deleteOnExit() }, // Ensures always fresh file
|
File(
|
||||||
|
context.cacheDir,
|
||||||
|
"exoplayer"
|
||||||
|
).also { it.deleteOnExit() }, // Ensures always fresh file
|
||||||
LeastRecentlyUsedCacheEvictor(300L * 1024L * 1024L),
|
LeastRecentlyUsedCacheEvictor(300L * 1024L * 1024L),
|
||||||
databaseProvider
|
databaseProvider
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,6 @@ import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import android.widget.Toast
|
|
||||||
import ani.dantotsu.logger
|
import ani.dantotsu.logger
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
@ -24,7 +23,11 @@ data class ImageData(
|
||||||
val page: Page,
|
val page: Page,
|
||||||
val source: HttpSource
|
val source: HttpSource
|
||||||
) {
|
) {
|
||||||
suspend fun fetchAndProcessImage(page: Page, httpSource: HttpSource, context: Context): 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
|
||||||
|
@ -52,16 +55,26 @@ data class ImageData(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String, format: Bitmap.CompressFormat, quality: Int) {
|
fun saveImage(
|
||||||
|
bitmap: Bitmap,
|
||||||
|
contentResolver: ContentResolver,
|
||||||
|
filename: String,
|
||||||
|
format: Bitmap.CompressFormat,
|
||||||
|
quality: Int
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
val contentValues = ContentValues().apply {
|
val contentValues = ContentValues().apply {
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||||
put(MediaStore.MediaColumns.MIME_TYPE, "image/${format.name.lowercase()}")
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/${format.name.lowercase()}")
|
||||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Manga")
|
put(
|
||||||
|
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||||
|
"${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/Manga"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val uri: Uri? = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
val uri: Uri? =
|
||||||
|
contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
|
||||||
uri?.let {
|
uri?.let {
|
||||||
contentResolver.openOutputStream(it)?.use { os ->
|
contentResolver.openOutputStream(it)?.use { os ->
|
||||||
|
@ -69,7 +82,8 @@ fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val directory = File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Manga")
|
val directory =
|
||||||
|
File("${Environment.getExternalStorageDirectory()}${File.separator}Dantotsu${File.separator}Manga")
|
||||||
if (!directory.exists()) {
|
if (!directory.exists()) {
|
||||||
directory.mkdirs()
|
directory.mkdirs()
|
||||||
}
|
}
|
||||||
|
@ -85,7 +99,7 @@ fun saveImage(bitmap: Bitmap, contentResolver: ContentResolver, filename: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,14 @@ data class MangaChapter(
|
||||||
val scanlator: String? = null,
|
val scanlator: String? = null,
|
||||||
var progress: String? = ""
|
var progress: String? = ""
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
constructor(chapter: MangaChapter) : this(chapter.number, chapter.link, chapter.title, chapter.description, chapter.sChapter, chapter.scanlator)
|
constructor(chapter: MangaChapter) : this(
|
||||||
|
chapter.number,
|
||||||
|
chapter.link,
|
||||||
|
chapter.title,
|
||||||
|
chapter.description,
|
||||||
|
chapter.sChapter,
|
||||||
|
chapter.scanlator
|
||||||
|
)
|
||||||
|
|
||||||
private val images = mutableListOf<MangaImage>()
|
private val images = mutableListOf<MangaImage>()
|
||||||
fun images(): List<MangaImage> = images
|
fun images(): List<MangaImage> = images
|
||||||
|
|
|
@ -5,16 +5,15 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.LinearInterpolator
|
import android.view.animation.LinearInterpolator
|
||||||
import androidx.core.content.ContentProviderCompat.requireContext
|
|
||||||
import androidx.lifecycle.coroutineScope
|
import androidx.lifecycle.coroutineScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.updateProgress
|
||||||
|
import ani.dantotsu.currContext
|
||||||
import ani.dantotsu.databinding.ItemChapterListBinding
|
import ani.dantotsu.databinding.ItemChapterListBinding
|
||||||
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
import ani.dantotsu.connections.updateProgress
|
|
||||||
import ani.dantotsu.currContext
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -154,7 +153,8 @@ class MangaChapterAdapter(
|
||||||
// Add chapter number to active coroutines set
|
// Add chapter number to active coroutines set
|
||||||
activeCoroutines.add(chapterNumber)
|
activeCoroutines.add(chapterNumber)
|
||||||
while (activeDownloads.contains(chapterNumber)) {
|
while (activeDownloads.contains(chapterNumber)) {
|
||||||
binding.itemDownload.animate().rotationBy(360f).setDuration(1000).setInterpolator(
|
binding.itemDownload.animate().rotationBy(360f).setDuration(1000)
|
||||||
|
.setInterpolator(
|
||||||
LinearInterpolator()
|
LinearInterpolator()
|
||||||
).start()
|
).start()
|
||||||
delay(1000)
|
delay(1000)
|
||||||
|
@ -171,8 +171,16 @@ class MangaChapterAdapter(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val theme = currContext()?.theme
|
val theme = currContext()?.theme
|
||||||
theme?.resolveAttribute(com.google.android.material.R.attr.colorError, typedValue1, true)
|
theme?.resolveAttribute(
|
||||||
theme?.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue2, true)
|
com.google.android.material.R.attr.colorError,
|
||||||
|
typedValue1,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
theme?.resolveAttribute(
|
||||||
|
com.google.android.material.R.attr.colorPrimary,
|
||||||
|
typedValue2,
|
||||||
|
true
|
||||||
|
)
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
if (0 <= bindingAdapterPosition && bindingAdapterPosition < arr.size)
|
||||||
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
fragment.onMangaChapterClick(arr[bindingAdapterPosition].number)
|
||||||
|
|
|
@ -13,15 +13,12 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.App.Companion.context
|
|
||||||
import ani.dantotsu.media.anime.handleProgress
|
|
||||||
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.SourceSearchDialogFragment
|
import ani.dantotsu.media.SourceSearchDialogFragment
|
||||||
import ani.dantotsu.parsers.AnimeSources
|
import ani.dantotsu.media.anime.handleProgress
|
||||||
import ani.dantotsu.parsers.DynamicAnimeParser
|
|
||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.MangaReadSources
|
import ani.dantotsu.parsers.MangaReadSources
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
@ -30,7 +27,6 @@ import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.lang.IndexOutOfBoundsException
|
|
||||||
|
|
||||||
class MangaReadAdapter(
|
class MangaReadAdapter(
|
||||||
private val media: Media,
|
private val media: Media,
|
||||||
|
@ -57,11 +53,15 @@ class MangaReadAdapter(
|
||||||
|
|
||||||
//Wrong Title
|
//Wrong Title
|
||||||
binding.animeSourceSearch.setOnClickListener {
|
binding.animeSourceSearch.setOnClickListener {
|
||||||
SourceSearchDialogFragment().show(fragment.requireActivity().supportFragmentManager, null)
|
SourceSearchDialogFragment().show(
|
||||||
|
fragment.requireActivity().supportFragmentManager,
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Source Selection
|
//Source Selection
|
||||||
var source = media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it }
|
var source =
|
||||||
|
media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it }
|
||||||
setLanguageList(media.selected!!.langIndex, source)
|
setLanguageList(media.selected!!.langIndex, source)
|
||||||
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
||||||
binding.animeSource.setText(mangaReadSources.names[source])
|
binding.animeSource.setText(mangaReadSources.names[source])
|
||||||
|
@ -70,7 +70,13 @@ class MangaReadAdapter(
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.animeSource.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, mangaReadSources.names))
|
binding.animeSource.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
fragment.requireContext(),
|
||||||
|
R.layout.item_dropdown,
|
||||||
|
mangaReadSources.names
|
||||||
|
)
|
||||||
|
)
|
||||||
binding.animeSourceTitle.isSelected = true
|
binding.animeSourceTitle.isSelected = true
|
||||||
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
||||||
fragment.onSourceChange(i).apply {
|
fragment.onSourceChange(i).apply {
|
||||||
|
@ -92,7 +98,8 @@ class MangaReadAdapter(
|
||||||
fragment.onLangChange(i)
|
fragment.onLangChange(i)
|
||||||
fragment.onSourceChange(media.selected!!.sourceIndex).apply {
|
fragment.onSourceChange(media.selected!!.sourceIndex).apply {
|
||||||
binding.animeSourceTitle.text = showUserText
|
binding.animeSourceTitle.text = showUserText
|
||||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
showUserTextListener =
|
||||||
|
{ MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||||
setLanguageList(i, source)
|
setLanguageList(i, source)
|
||||||
}
|
}
|
||||||
subscribeButton(false)
|
subscribeButton(false)
|
||||||
|
@ -139,7 +146,8 @@ class MangaReadAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeScanlatorTop.setOnClickListener {
|
binding.animeScanlatorTop.setOnClickListener {
|
||||||
val dialogView = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
|
val dialogView =
|
||||||
|
LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
|
||||||
val checkboxContainer = dialogView.findViewById<LinearLayout>(R.id.checkboxContainer)
|
val checkboxContainer = dialogView.findViewById<LinearLayout>(R.id.checkboxContainer)
|
||||||
|
|
||||||
// Dynamically add checkboxes
|
// Dynamically add checkboxes
|
||||||
|
@ -217,14 +225,26 @@ class MangaReadAdapter(
|
||||||
for (position in arr.indices) {
|
for (position in arr.indices) {
|
||||||
val last = if (position + 1 == arr.size) names.size else (limit * (position + 1))
|
val last = if (position + 1 == arr.size) names.size else (limit * (position + 1))
|
||||||
val chip =
|
val chip =
|
||||||
ItemChipBinding.inflate(LayoutInflater.from(fragment.context), binding.animeSourceChipGroup, false).root
|
ItemChipBinding.inflate(
|
||||||
|
LayoutInflater.from(fragment.context),
|
||||||
|
binding.animeSourceChipGroup,
|
||||||
|
false
|
||||||
|
).root
|
||||||
chip.isCheckable = true
|
chip.isCheckable = true
|
||||||
fun selected() {
|
fun selected() {
|
||||||
chip.isChecked = true
|
chip.isChecked = true
|
||||||
binding.animeWatchChipScroll.smoothScrollTo((chip.left - screenWidth / 2) + (chip.width / 2), 0)
|
binding.animeWatchChipScroll.smoothScrollTo(
|
||||||
|
(chip.left - screenWidth / 2) + (chip.width / 2),
|
||||||
|
0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
|
chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
|
||||||
chip.setTextColor(ContextCompat.getColorStateList(fragment.requireContext(), R.color.chip_text_color))
|
chip.setTextColor(
|
||||||
|
ContextCompat.getColorStateList(
|
||||||
|
fragment.requireContext(),
|
||||||
|
R.color.chip_text_color
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
chip.setOnClickListener {
|
chip.setOnClickListener {
|
||||||
selected()
|
selected()
|
||||||
|
@ -237,7 +257,14 @@ class MangaReadAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (select != null)
|
if (select != null)
|
||||||
binding.animeWatchChipScroll.apply { post { scrollTo((select.left - screenWidth / 2) + (select.width / 2), 0) } }
|
binding.animeWatchChipScroll.apply {
|
||||||
|
post {
|
||||||
|
scrollTo(
|
||||||
|
(select.left - screenWidth / 2) + (select.width / 2),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +286,9 @@ class MangaReadAdapter(
|
||||||
val chapter = media.manga.chapters!![chapterKey]!!
|
val chapter = media.manga.chapters!![chapterKey]!!
|
||||||
chapter.scanlator !in hiddenScanlators
|
chapter.scanlator !in hiddenScanlators
|
||||||
}
|
}
|
||||||
val formattedChapters = filteredChapters.map { MangaNameAdapter.findChapterNumber(it)?.toInt()?.toString() }
|
val formattedChapters = filteredChapters.map {
|
||||||
|
MangaNameAdapter.findChapterNumber(it)?.toInt()?.toString()
|
||||||
|
}
|
||||||
if (formattedChapters.contains(continueEp)) {
|
if (formattedChapters.contains(continueEp)) {
|
||||||
continueEp = chapters[formattedChapters.indexOf(continueEp)]
|
continueEp = chapters[formattedChapters.indexOf(continueEp)]
|
||||||
binding.animeSourceContinue.visibility = View.VISIBLE
|
binding.animeSourceContinue.visibility = View.VISIBLE
|
||||||
|
@ -318,9 +347,16 @@ class MangaReadAdapter(
|
||||||
try {
|
try {
|
||||||
binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang)
|
binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang)
|
||||||
} catch (e: IndexOutOfBoundsException) {
|
} catch (e: IndexOutOfBoundsException) {
|
||||||
binding?.animeSourceLanguage?.setText(parser.extension.sources.firstOrNull()?.lang ?: "Unknown")
|
binding?.animeSourceLanguage?.setText(
|
||||||
|
parser.extension.sources.firstOrNull()?.lang ?: "Unknown"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
binding?.animeSourceLanguage?.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, parser.extension.sources.map { it.lang }))
|
binding?.animeSourceLanguage?.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
fragment.requireContext(),
|
||||||
|
R.layout.item_dropdown,
|
||||||
|
parser.extension.sources.map { it.lang })
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,7 +364,8 @@ class MangaReadAdapter(
|
||||||
|
|
||||||
override fun getItemCount(): Int = 1
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ItemAnimeWatchBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class ViewHolder(val binding: ItemAnimeWatchBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ScanlatorSelectionListener {
|
interface ScanlatorSelectionListener {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ani.dantotsu.media.manga
|
package ani.dantotsu.media.manga
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
|
@ -16,6 +17,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.math.MathUtils.clamp
|
import androidx.core.math.MathUtils.clamp
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
@ -30,11 +32,11 @@ import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||||
import ani.dantotsu.download.Download
|
import ani.dantotsu.download.Download
|
||||||
import ani.dantotsu.download.DownloadsManager
|
import ani.dantotsu.download.DownloadsManager
|
||||||
import ani.dantotsu.download.manga.MangaDownloaderService
|
import ani.dantotsu.download.manga.MangaDownloaderService
|
||||||
import ani.dantotsu.download.manga.ServiceDataSingleton
|
import ani.dantotsu.download.manga.MangaServiceDataSingleton
|
||||||
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
|
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
|
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
|
||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.HMangaSources
|
import ani.dantotsu.parsers.HMangaSources
|
||||||
import ani.dantotsu.parsers.MangaParser
|
import ani.dantotsu.parsers.MangaParser
|
||||||
|
@ -59,8 +61,6 @@ import uy.kohesive.injekt.api.get
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import android.Manifest
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
|
|
||||||
open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
private var _binding: FragmentAnimeWatchBinding? = null
|
private var _binding: FragmentAnimeWatchBinding? = null
|
||||||
|
@ -85,7 +85,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
var continueEp: Boolean = false
|
var continueEp: Boolean = false
|
||||||
var loaded = false
|
var loaded = false
|
||||||
|
|
||||||
val uiSettings = loadData("ui_settings", toast = false) ?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
val uiSettings = loadData("ui_settings", toast = false)
|
||||||
|
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -105,7 +106,12 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
addAction(ACTION_DOWNLOAD_PROGRESS)
|
addAction(ACTION_DOWNLOAD_PROGRESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextCompat.registerReceiver(requireContext(), downloadStatusReceiver, intentFilter, ContextCompat.RECEIVER_EXPORTED)
|
ContextCompat.registerReceiver(
|
||||||
|
requireContext(),
|
||||||
|
downloadStatusReceiver,
|
||||||
|
intentFilter,
|
||||||
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
|
|
||||||
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
||||||
screenWidth = resources.displayMetrics.widthPixels.dp
|
screenWidth = resources.displayMetrics.widthPixels.dp
|
||||||
|
@ -146,7 +152,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
if (media.format == "MANGA" || media.format == "ONE SHOT") {
|
if (media.format == "MANGA" || media.format == "ONE SHOT") {
|
||||||
media.selected = model.loadSelected(media)
|
media.selected = model.loadSelected(media)
|
||||||
|
|
||||||
subscribed = SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id)
|
subscribed =
|
||||||
|
SubscriptionHelper.getSubscriptions(requireContext()).containsKey(media.id)
|
||||||
|
|
||||||
style = media.selected!!.recyclerStyle
|
style = media.selected!!.recyclerStyle
|
||||||
reverse = media.selected!!.recyclerReversed
|
reverse = media.selected!!.recyclerReversed
|
||||||
|
@ -156,13 +163,15 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
|
|
||||||
headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!)
|
headerAdapter = MangaReadAdapter(it, this, model.mangaReadSources!!)
|
||||||
headerAdapter.scanlatorSelectionListener = this
|
headerAdapter.scanlatorSelectionListener = this
|
||||||
chapterAdapter = MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
|
chapterAdapter =
|
||||||
|
MangaChapterAdapter(style ?: uiSettings.mangaDefaultView, media, this)
|
||||||
|
|
||||||
for (download in downloadManager.mangaDownloads) {
|
for (download in downloadManager.mangaDownloads) {
|
||||||
chapterAdapter.stopDownload(download.chapter)
|
chapterAdapter.stopDownload(download.chapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, chapterAdapter)
|
binding.animeSourceRecycler.adapter =
|
||||||
|
ConcatAdapter(headerAdapter, chapterAdapter)
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
model.loadMangaChapters(media, media.selected!!.sourceIndex)
|
model.loadMangaChapters(media, media.selected!!.sourceIndex)
|
||||||
|
@ -173,7 +182,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.animeNotSupported.visibility = View.VISIBLE
|
binding.animeNotSupported.visibility = View.VISIBLE
|
||||||
binding.animeNotSupported.text = getString(R.string.not_supported, media.format ?: "")
|
binding.animeNotSupported.text =
|
||||||
|
getString(R.string.not_supported, media.format ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,7 +386,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
media.manga?.chapters?.get(i)?.let {
|
media.manga?.chapters?.get(i)?.let {
|
||||||
media.manga?.selectedChapter = i
|
media.manga?.selectedChapter = i
|
||||||
model.saveSelected(media.id, media.selected!!, requireActivity())
|
model.saveSelected(media.id, media.selected!!, requireActivity())
|
||||||
ChapterLoaderDialog.newInstance(it, true).show(requireActivity().supportFragmentManager, "dialog")
|
ChapterLoaderDialog.newInstance(it, true)
|
||||||
|
.show(requireActivity().supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +404,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
|
|
||||||
model.continueMedia = false
|
model.continueMedia = false
|
||||||
media.manga?.chapters?.get(i)?.let { chapter ->
|
media.manga?.chapters?.get(i)?.let { chapter ->
|
||||||
val parser = model.mangaReadSources?.get(media.selected!!.sourceIndex) as? DynamicMangaParser
|
val parser =
|
||||||
|
model.mangaReadSources?.get(media.selected!!.sourceIndex) as? DynamicMangaParser
|
||||||
parser?.let {
|
parser?.let {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val images = parser.imageList("", chapter.sChapter)
|
val images = parser.imageList("", chapter.sChapter)
|
||||||
|
@ -408,15 +420,15 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
simultaneousDownloads = 2
|
simultaneousDownloads = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
ServiceDataSingleton.downloadQueue.offer(downloadTask)
|
MangaServiceDataSingleton.downloadQueue.offer(downloadTask)
|
||||||
|
|
||||||
// If the service is not already running, start it
|
// If the service is not already running, start it
|
||||||
if (!ServiceDataSingleton.isServiceRunning) {
|
if (!MangaServiceDataSingleton.isServiceRunning) {
|
||||||
val intent = Intent(context, MangaDownloaderService::class.java)
|
val intent = Intent(context, MangaDownloaderService::class.java)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
ContextCompat.startForegroundService(requireContext(), intent)
|
ContextCompat.startForegroundService(requireContext(), intent)
|
||||||
}
|
}
|
||||||
ServiceDataSingleton.isServiceRunning = true
|
MangaServiceDataSingleton.isServiceRunning = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inform the adapter that the download has started
|
// Inform the adapter that the download has started
|
||||||
|
@ -440,9 +452,16 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
|
|
||||||
|
|
||||||
fun onMangaChapterRemoveDownloadClick(i: String) {
|
fun onMangaChapterRemoveDownloadClick(i: String) {
|
||||||
downloadManager.removeDownload(Download(media.nameMAL?:media.nameRomaji, i, Download.Type.MANGA))
|
downloadManager.removeDownload(
|
||||||
|
Download(
|
||||||
|
media.nameMAL ?: media.nameRomaji,
|
||||||
|
i,
|
||||||
|
Download.Type.MANGA
|
||||||
|
)
|
||||||
|
)
|
||||||
chapterAdapter.deleteDownload(i)
|
chapterAdapter.deleteDownload(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMangaChapterStopDownloadClick(i: String) {
|
fun onMangaChapterStopDownloadClick(i: String) {
|
||||||
val cancelIntent = Intent().apply {
|
val cancelIntent = Intent().apply {
|
||||||
action = MangaDownloaderService.ACTION_CANCEL_DOWNLOAD
|
action = MangaDownloaderService.ACTION_CANCEL_DOWNLOAD
|
||||||
|
@ -451,11 +470,19 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
requireContext().sendBroadcast(cancelIntent)
|
requireContext().sendBroadcast(cancelIntent)
|
||||||
|
|
||||||
// Remove the download from the manager and update the UI
|
// Remove the download from the manager and update the UI
|
||||||
downloadManager.removeDownload(Download(media.nameMAL?:media.nameRomaji, i, Download.Type.MANGA))
|
downloadManager.removeDownload(
|
||||||
|
Download(
|
||||||
|
media.nameMAL ?: media.nameRomaji,
|
||||||
|
i,
|
||||||
|
Download.Type.MANGA
|
||||||
|
)
|
||||||
|
)
|
||||||
chapterAdapter.purgeDownload(i)
|
chapterAdapter.purgeDownload(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (!this@MangaReadFragment::chapterAdapter.isInitialized) return
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_DOWNLOAD_STARTED -> {
|
ACTION_DOWNLOAD_STARTED -> {
|
||||||
val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER)
|
val chapterNumber = intent.getStringExtra(EXTRA_CHAPTER_NUMBER)
|
||||||
|
@ -491,8 +518,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
val selected = model.loadSelected(media)
|
val selected = model.loadSelected(media)
|
||||||
|
|
||||||
//Find latest chapter for subscription
|
//Find latest chapter for subscription
|
||||||
selected.latest = media.manga?.chapters?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
selected.latest =
|
||||||
selected.latest = media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
media.manga?.chapters?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f
|
||||||
|
selected.latest =
|
||||||
|
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest
|
||||||
|
|
||||||
model.saveSelected(media.id, selected, requireActivity())
|
model.saveSelected(media.id, selected, requireActivity())
|
||||||
headerAdapter.handleChapters()
|
headerAdapter.handleChapters()
|
||||||
|
@ -501,7 +530,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
if (media.manga!!.chapters != null) {
|
if (media.manga!!.chapters != null) {
|
||||||
val end = if (end != null && end!! < media.manga!!.chapters!!.size) end else null
|
val end = if (end != null && end!! < media.manga!!.chapters!!.size) end else null
|
||||||
arr.addAll(
|
arr.addAll(
|
||||||
media.manga!!.chapters!!.values.toList().slice(start..(end ?: (media.manga!!.chapters!!.size - 1)))
|
media.manga!!.chapters!!.values.toList()
|
||||||
|
.slice(start..(end ?: (media.manga!!.chapters!!.size - 1)))
|
||||||
)
|
)
|
||||||
if (reverse)
|
if (reverse)
|
||||||
arr = (arr.reversed() as? ArrayList<MangaChapter>) ?: arr
|
arr = (arr.reversed() as? ArrayList<MangaChapter>) ?: arr
|
||||||
|
|
|
@ -14,8 +14,8 @@ import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
|
import ani.dantotsu.media.manga.MangaCache
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
|
||||||
import ani.dantotsu.settings.CurrentReaderSettings
|
import ani.dantotsu.settings.CurrentReaderSettings
|
||||||
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
@ -23,12 +23,9 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import ani.dantotsu.media.manga.MangaCache
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -118,7 +115,10 @@ abstract class BaseImageAdapter(
|
||||||
abstract suspend fun loadImage(position: Int, parent: View): Boolean
|
abstract suspend fun loadImage(position: Int, parent: View): Boolean
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun Context.loadBitmap_old(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? { //still used in some places
|
suspend fun Context.loadBitmap_old(
|
||||||
|
link: FileUrl,
|
||||||
|
transforms: List<BitmapTransformation>
|
||||||
|
): Bitmap? { //still used in some places
|
||||||
return tryWithSuspend {
|
return tryWithSuspend {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Glide.with(this@loadBitmap_old)
|
Glide.with(this@loadBitmap_old)
|
||||||
|
@ -135,8 +135,7 @@ abstract class BaseImageAdapter(
|
||||||
.let {
|
.let {
|
||||||
if (transforms.isNotEmpty()) {
|
if (transforms.isNotEmpty()) {
|
||||||
it.transform(*transforms.toTypedArray())
|
it.transform(*transforms.toTypedArray())
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,7 +145,10 @@ abstract class BaseImageAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Context.loadBitmap(link: FileUrl, transforms: List<BitmapTransformation>): Bitmap? {
|
suspend fun Context.loadBitmap(
|
||||||
|
link: FileUrl,
|
||||||
|
transforms: List<BitmapTransformation>
|
||||||
|
): Bitmap? {
|
||||||
return tryWithSuspend {
|
return tryWithSuspend {
|
||||||
val mangaCache = uy.kohesive.injekt.Injekt.get<MangaCache>()
|
val mangaCache = uy.kohesive.injekt.Injekt.get<MangaCache>()
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -161,7 +163,11 @@ abstract class BaseImageAdapter(
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
} else {
|
} else {
|
||||||
mangaCache.get(link.url)?.let { imageData ->
|
mangaCache.get(link.url)?.let { imageData ->
|
||||||
val bitmap = imageData.fetchAndProcessImage(imageData.page, imageData.source, context = this@loadBitmap)
|
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)
|
||||||
|
|
|
@ -14,9 +14,9 @@ import ani.dantotsu.BottomSheetDialogFragment
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.currActivity
|
import ani.dantotsu.currActivity
|
||||||
import ani.dantotsu.databinding.BottomSheetSelectorBinding
|
import ani.dantotsu.databinding.BottomSheetSelectorBinding
|
||||||
import ani.dantotsu.media.manga.MangaChapter
|
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.media.MediaSingleton
|
import ani.dantotsu.media.MediaSingleton
|
||||||
|
import ani.dantotsu.media.manga.MangaChapter
|
||||||
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
|
||||||
|
@ -47,13 +47,21 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
|
||||||
loaded = true
|
loaded = true
|
||||||
binding.selectorAutoText.text = chp.title
|
binding.selectorAutoText.text = chp.title
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
if(model.loadMangaChapterImages(chp, m.selected!!, m.nameMAL?:m.nameRomaji)) {
|
if (model.loadMangaChapterImages(
|
||||||
|
chp,
|
||||||
|
m.selected!!,
|
||||||
|
m.nameMAL ?: m.nameRomaji
|
||||||
|
)
|
||||||
|
) {
|
||||||
val activity = currActivity()
|
val activity = currActivity()
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
tryWith { dismiss() }
|
tryWith { dismiss() }
|
||||||
if (launch) {
|
if (launch) {
|
||||||
MediaSingleton.media = m
|
MediaSingleton.media = m
|
||||||
val intent = Intent(activity, MangaReaderActivity::class.java)//.apply { putExtra("media", m) }
|
val intent = Intent(
|
||||||
|
activity,
|
||||||
|
MangaReaderActivity::class.java
|
||||||
|
)//.apply { putExtra("media", m) }
|
||||||
activity.startActivity(intent)
|
activity.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +71,11 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetSelectorBinding.inflate(inflater, container, false)
|
_binding = BottomSheetSelectorBinding.inflate(inflater, container, false)
|
||||||
val window = dialog?.window
|
val window = dialog?.window
|
||||||
window?.statusBarColor = Color.TRANSPARENT
|
window?.statusBarColor = Color.TRANSPARENT
|
||||||
|
|
|
@ -47,7 +47,8 @@ open class ImageAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadImage(position: Int, parent: View): Boolean {
|
override suspend fun loadImage(position: Int, parent: View): Boolean {
|
||||||
val imageView = parent.findViewById<SubsamplingScaleImageView>(R.id.imgProgImageNoGestures) ?: return false
|
val imageView = parent.findViewById<SubsamplingScaleImageView>(R.id.imgProgImageNoGestures)
|
||||||
|
?: return false
|
||||||
val progress = parent.findViewById<View>(R.id.imgProgProgress) ?: return false
|
val progress = parent.findViewById<View>(R.id.imgProgProgress) ?: return false
|
||||||
imageView.recycle()
|
imageView.recycle()
|
||||||
imageView.visibility = View.GONE
|
imageView.visibility = View.GONE
|
||||||
|
@ -60,10 +61,12 @@ open class ImageAdapter(
|
||||||
if (settings.layout != PAGED)
|
if (settings.layout != PAGED)
|
||||||
parent.updateLayoutParams {
|
parent.updateLayoutParams {
|
||||||
if (settings.direction != LEFT_TO_RIGHT && settings.direction != RIGHT_TO_LEFT) {
|
if (settings.direction != LEFT_TO_RIGHT && settings.direction != RIGHT_TO_LEFT) {
|
||||||
sHeight = if (settings.wrapImages) bitmap.height else (sWidth * bitmap.height * 1f / bitmap.width).toInt()
|
sHeight =
|
||||||
|
if (settings.wrapImages) bitmap.height else (sWidth * bitmap.height * 1f / bitmap.width).toInt()
|
||||||
height = sHeight
|
height = sHeight
|
||||||
} else {
|
} else {
|
||||||
sWidth = if (settings.wrapImages) bitmap.width else (sHeight * bitmap.width * 1f / bitmap.height).toInt()
|
sWidth =
|
||||||
|
if (settings.wrapImages) bitmap.width else (sHeight * bitmap.width * 1f / bitmap.height).toInt()
|
||||||
width = sWidth
|
width = sWidth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +76,8 @@ open class ImageAdapter(
|
||||||
|
|
||||||
val parentArea = sWidth * sHeight * 1f
|
val parentArea = sWidth * sHeight * 1f
|
||||||
val bitmapArea = bitmap.width * bitmap.height * 1f
|
val bitmapArea = bitmap.width * bitmap.height * 1f
|
||||||
val scale = if (parentArea < bitmapArea) (bitmapArea / parentArea) else (parentArea / bitmapArea)
|
val scale =
|
||||||
|
if (parentArea < bitmapArea) (bitmapArea / parentArea) else (parentArea / bitmapArea)
|
||||||
|
|
||||||
imageView.maxScale = scale * 1.1f
|
imageView.maxScale = scale * 1.1f
|
||||||
imageView.minScale = scale
|
imageView.minScale = scale
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ani.dantotsu.media.manga.mangareader
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -25,6 +26,8 @@ import androidx.viewpager2.widget.ViewPager2
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.connections.discord.Discord
|
import ani.dantotsu.connections.discord.Discord
|
||||||
|
import ani.dantotsu.connections.discord.DiscordService
|
||||||
|
import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton
|
||||||
import ani.dantotsu.connections.discord.RPC
|
import ani.dantotsu.connections.discord.RPC
|
||||||
import ani.dantotsu.connections.updateProgress
|
import ani.dantotsu.connections.updateProgress
|
||||||
import ani.dantotsu.databinding.ActivityMangaReaderBinding
|
import ani.dantotsu.databinding.ActivityMangaReaderBinding
|
||||||
|
@ -35,7 +38,7 @@ 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.media.manga.MangaNameAdapter
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.parsers.HMangaSources
|
import ani.dantotsu.parsers.HMangaSources
|
||||||
import ani.dantotsu.parsers.MangaImage
|
import ani.dantotsu.parsers.MangaImage
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
@ -46,7 +49,6 @@ import ani.dantotsu.settings.CurrentReaderSettings.Layouts.*
|
||||||
import ani.dantotsu.settings.ReaderSettings
|
import ani.dantotsu.settings.ReaderSettings
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
@ -54,8 +56,6 @@ import com.google.firebase.crashlytics.ktx.crashlytics
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
|
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
|
||||||
|
@ -101,7 +101,10 @@ class MangaReaderActivity : AppCompatActivity() {
|
||||||
val displayCutout = window.decorView.rootWindowInsets.displayCutout
|
val displayCutout = window.decorView.rootWindowInsets.displayCutout
|
||||||
if (displayCutout != null) {
|
if (displayCutout != null) {
|
||||||
if (displayCutout.boundingRects.size > 0) {
|
if (displayCutout.boundingRects.size > 0) {
|
||||||
notchHeight = min(displayCutout.boundingRects[0].width(), displayCutout.boundingRects[0].height())
|
notchHeight = min(
|
||||||
|
displayCutout.boundingRects[0].width(),
|
||||||
|
displayCutout.boundingRects[0].height()
|
||||||
|
)
|
||||||
checkNotch()
|
checkNotch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +124,11 @@ class MangaReaderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
mangaCache.clear()
|
mangaCache.clear()
|
||||||
rpc?.close()
|
if (isOnline(baseContext)) { //TODO:
|
||||||
|
DiscordServiceRunningSingleton.running = false
|
||||||
|
val stopIntent = Intent(this, DiscordService::class.java)
|
||||||
|
stopService(stopIntent)
|
||||||
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,8 +147,14 @@ ThemeManager(this).applyTheme()
|
||||||
progress { finish() }
|
progress { finish() }
|
||||||
}
|
}
|
||||||
|
|
||||||
settings = loadData("reader_settings", this) ?: ReaderSettings().apply { saveData("reader_settings", this) }
|
settings = loadData("reader_settings", this)
|
||||||
uiSettings = loadData("ui_settings", this) ?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
?: ReaderSettings().apply { saveData("reader_settings", this) }
|
||||||
|
uiSettings = loadData("ui_settings", this) ?: UserInterfaceSettings().apply {
|
||||||
|
saveData(
|
||||||
|
"ui_settings",
|
||||||
|
this
|
||||||
|
)
|
||||||
|
}
|
||||||
controllerDuration = (uiSettings.animationSpeed * 200).toLong()
|
controllerDuration = (uiSettings.animationSpeed * 200).toLong()
|
||||||
|
|
||||||
hideBars()
|
hideBars()
|
||||||
|
@ -166,9 +179,11 @@ ThemeManager(this).applyTheme()
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
sliding = true
|
sliding = true
|
||||||
if (settings.default.layout != PAGED)
|
if (settings.default.layout != PAGED)
|
||||||
binding.mangaReaderRecycler.scrollToPosition((value.toInt() - 1) / (dualPage { 2 } ?: 1))
|
binding.mangaReaderRecycler.scrollToPosition((value.toInt() - 1) / (dualPage { 2 }
|
||||||
|
?: 1))
|
||||||
else
|
else
|
||||||
binding.mangaReaderPager.currentItem = (value.toInt() - 1) / (dualPage { 2 } ?: 1)
|
binding.mangaReaderPager.currentItem =
|
||||||
|
(value.toInt() - 1) / (dualPage { 2 } ?: 1)
|
||||||
pageSliderHide()
|
pageSliderHide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +236,8 @@ ThemeManager(this).applyTheme()
|
||||||
if (media.selected!!.sourceIndex >= model.mangaReadSources!!.names.size) {
|
if (media.selected!!.sourceIndex >= model.mangaReadSources!!.names.size) {
|
||||||
media.selected!!.sourceIndex = 0
|
media.selected!!.sourceIndex = 0
|
||||||
}
|
}
|
||||||
binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex]
|
binding.mangaReaderSource.text =
|
||||||
|
model.mangaReadSources!!.names[media.selected!!.sourceIndex]
|
||||||
|
|
||||||
binding.mangaReaderTitle.text = media.userPreferredName
|
binding.mangaReaderTitle.text = media.userPreferredName
|
||||||
|
|
||||||
|
@ -234,10 +250,12 @@ ThemeManager(this).applyTheme()
|
||||||
chaptersTitleArr.add("${if (!chapter.title.isNullOrEmpty() && chapter.title != "null") "" else "Chapter "}${chapter.number}${if (!chapter.title.isNullOrEmpty() && chapter.title != "null") " : " + chapter.title else ""}")
|
chaptersTitleArr.add("${if (!chapter.title.isNullOrEmpty() && chapter.title != "null") "" else "Chapter "}${chapter.number}${if (!chapter.title.isNullOrEmpty() && chapter.title != "null") " : " + chapter.title else ""}")
|
||||||
}
|
}
|
||||||
|
|
||||||
showProgressDialog = if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog") != true else false
|
showProgressDialog =
|
||||||
|
if (settings.askIndividual) loadData<Boolean>("${media.id}_progressDialog") != true else false
|
||||||
progressDialog =
|
progressDialog =
|
||||||
if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true)
|
if (showProgressDialog && Anilist.userid != null && if (media.isAdult) settings.updateForH else true)
|
||||||
AlertDialog.Builder(this, R.style.DialogTheme).setTitle(getString(R.string.title_update_progress)).apply {
|
AlertDialog.Builder(this, R.style.MyPopup)
|
||||||
|
.setTitle(getString(R.string.title_update_progress)).apply {
|
||||||
setMultiChoiceItems(
|
setMultiChoiceItems(
|
||||||
arrayOf(getString(R.string.dont_ask_again, media.userPreferredName)),
|
arrayOf(getString(R.string.dont_ask_again, media.userPreferredName)),
|
||||||
booleanArrayOf(false)
|
booleanArrayOf(false)
|
||||||
|
@ -254,14 +272,22 @@ ThemeManager(this).applyTheme()
|
||||||
fun change(index: Int) {
|
fun change(index: Int) {
|
||||||
mangaCache.clear()
|
mangaCache.clear()
|
||||||
saveData("${media.id}_${chaptersArr[currentChapterIndex]}", currentChapterPage, this)
|
saveData("${media.id}_${chaptersArr[currentChapterIndex]}", currentChapterPage, this)
|
||||||
ChapterLoaderDialog.newInstance(chapters[chaptersArr[index]]!!).show(supportFragmentManager, "dialog")
|
ChapterLoaderDialog.newInstance(chapters[chaptersArr[index]]!!)
|
||||||
|
.show(supportFragmentManager, "dialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
//ChapterSelector
|
//ChapterSelector
|
||||||
binding.mangaReaderChapterSelect.adapter = NoPaddingArrayAdapter(this, R.layout.item_dropdown, chaptersTitleArr)
|
binding.mangaReaderChapterSelect.adapter =
|
||||||
|
NoPaddingArrayAdapter(this, R.layout.item_dropdown, chaptersTitleArr)
|
||||||
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
|
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
|
||||||
binding.mangaReaderChapterSelect.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.mangaReaderChapterSelect.onItemSelectedListener =
|
||||||
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
|
object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(
|
||||||
|
p0: AdapterView<*>?,
|
||||||
|
p1: View?,
|
||||||
|
position: Int,
|
||||||
|
p3: Long
|
||||||
|
) {
|
||||||
if (position != currentChapterIndex) change(position)
|
if (position != currentChapterIndex) change(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,27 +323,55 @@ ThemeManager(this).applyTheme()
|
||||||
saveData("${media.id}_current_chp", chap.number, this)
|
saveData("${media.id}_current_chp", chap.number, this)
|
||||||
currentChapterIndex = chaptersArr.indexOf(chap.number)
|
currentChapterIndex = chaptersArr.indexOf(chap.number)
|
||||||
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
|
binding.mangaReaderChapterSelect.setSelection(currentChapterIndex)
|
||||||
binding.mangaReaderNextChap.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
binding.mangaReaderNextChap.text =
|
||||||
binding.mangaReaderPrevChap.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: ""
|
||||||
|
binding.mangaReaderPrevChap.text =
|
||||||
|
chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: ""
|
||||||
applySettings()
|
applySettings()
|
||||||
rpc?.close()
|
val context = this
|
||||||
rpc = Discord.defaultRPC()
|
if (isOnline(context)) {
|
||||||
rpc?.send {
|
lifecycleScope.launch {
|
||||||
type = RPC.Type.WATCHING
|
val presence = RPC.createPresence(
|
||||||
activityName = media.userPreferredName
|
RPC.Companion.RPCData(
|
||||||
details = chap.title?.takeIf { it.isNotEmpty() } ?: getString(R.string.chapter_num, chap.number)
|
applicationId = Discord.application_Id,
|
||||||
state = "${chap.number}/${media.manga?.totalChapters ?: "??"}"
|
type = RPC.Type.WATCHING,
|
||||||
media.cover?.let { cover ->
|
activityName = media.userPreferredName,
|
||||||
largeImage = RPC.Link(media.userPreferredName, cover)
|
details = chap.title?.takeIf { it.isNotEmpty() }
|
||||||
|
?: getString(R.string.chapter_num, chap.number),
|
||||||
|
state = "${chap.number}/${media.manga?.totalChapters ?: "??"}",
|
||||||
|
largeImage = media.cover?.let { cover ->
|
||||||
|
RPC.Link(media.userPreferredName, cover)
|
||||||
|
},
|
||||||
|
smallImage = RPC.Link(
|
||||||
|
"Dantotsu",
|
||||||
|
Discord.small_Image
|
||||||
|
),
|
||||||
|
buttons = mutableListOf(
|
||||||
|
RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""),
|
||||||
|
RPC.Link(
|
||||||
|
"Stream on Dantotsu",
|
||||||
|
"https://github.com/rebelonion/Dantotsu/"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val intent = Intent(context, DiscordService::class.java).apply {
|
||||||
|
putExtra("presence", presence)
|
||||||
}
|
}
|
||||||
media.shareLink?.let { link ->
|
DiscordServiceRunningSingleton.running = true
|
||||||
buttons.add(0, RPC.Link(getString(R.string.view_manga), link))
|
startService(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) { model.loadMangaChapterImages(chapter, media.selected!!, media.nameMAL?:media.nameRomaji) }
|
scope.launch(Dispatchers.IO) {
|
||||||
|
model.loadMangaChapterImages(
|
||||||
|
chapter,
|
||||||
|
media.selected!!,
|
||||||
|
media.nameMAL ?: media.nameRomaji
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val snapHelper = PagerSnapHelper()
|
private val snapHelper = PagerSnapHelper()
|
||||||
|
@ -330,6 +384,7 @@ ThemeManager(this).applyTheme()
|
||||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) callback.invoke()
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) callback.invoke()
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
|
|
||||||
Force -> callback.invoke()
|
Force -> callback.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,7 +416,8 @@ ThemeManager(this).applyTheme()
|
||||||
maxChapterPage = chapImages.size.toLong()
|
maxChapterPage = chapImages.size.toLong()
|
||||||
saveData("${media.id}_${chapter.number}_max", maxChapterPage)
|
saveData("${media.id}_${chapter.number}_max", maxChapterPage)
|
||||||
|
|
||||||
imageAdapter = dualPage { DualPageAdapter(this, chapter) } ?: ImageAdapter(this, chapter)
|
imageAdapter =
|
||||||
|
dualPage { DualPageAdapter(this, chapter) } ?: ImageAdapter(this, chapter)
|
||||||
|
|
||||||
if (chapImages.size > 1) {
|
if (chapImages.size > 1) {
|
||||||
binding.mangaReaderSlider.apply {
|
binding.mangaReaderSlider.apply {
|
||||||
|
@ -382,8 +438,10 @@ ThemeManager(this).applyTheme()
|
||||||
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)) {
|
if ((settings.default.direction == TOP_TO_BOTTOM || settings.default.direction == BOTTOM_TO_TOP)) {
|
||||||
binding.mangaReaderSwipy.vertical = true
|
binding.mangaReaderSwipy.vertical = true
|
||||||
if (settings.default.direction == TOP_TO_BOTTOM) {
|
if (settings.default.direction == TOP_TO_BOTTOM) {
|
||||||
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: getString(R.string.no_chapter)
|
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
||||||
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
|
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
||||||
|
?: getString(R.string.no_chapter)
|
||||||
binding.mangaReaderSwipy.onTopSwiped = {
|
binding.mangaReaderSwipy.onTopSwiped = {
|
||||||
binding.mangaReaderPreviousChapter.performClick()
|
binding.mangaReaderPreviousChapter.performClick()
|
||||||
}
|
}
|
||||||
|
@ -391,8 +449,10 @@ ThemeManager(this).applyTheme()
|
||||||
binding.mangaReaderNextChapter.performClick()
|
binding.mangaReaderNextChapter.performClick()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: getString(R.string.no_chapter)
|
binding.BottomSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
||||||
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
|
binding.TopSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
||||||
|
?: getString(R.string.no_chapter)
|
||||||
binding.mangaReaderSwipy.onTopSwiped = {
|
binding.mangaReaderSwipy.onTopSwiped = {
|
||||||
binding.mangaReaderNextChapter.performClick()
|
binding.mangaReaderNextChapter.performClick()
|
||||||
}
|
}
|
||||||
|
@ -415,8 +475,10 @@ ThemeManager(this).applyTheme()
|
||||||
} else {
|
} else {
|
||||||
binding.mangaReaderSwipy.vertical = false
|
binding.mangaReaderSwipy.vertical = false
|
||||||
if (settings.default.direction == RIGHT_TO_LEFT) {
|
if (settings.default.direction == RIGHT_TO_LEFT) {
|
||||||
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: getString(R.string.no_chapter)
|
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
||||||
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
|
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
||||||
|
?: getString(R.string.no_chapter)
|
||||||
binding.mangaReaderSwipy.onLeftSwiped = {
|
binding.mangaReaderSwipy.onLeftSwiped = {
|
||||||
binding.mangaReaderNextChapter.performClick()
|
binding.mangaReaderNextChapter.performClick()
|
||||||
}
|
}
|
||||||
|
@ -424,8 +486,10 @@ ThemeManager(this).applyTheme()
|
||||||
binding.mangaReaderPreviousChapter.performClick()
|
binding.mangaReaderPreviousChapter.performClick()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1) ?: getString(R.string.no_chapter)
|
binding.LeftSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex - 1)
|
||||||
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1) ?: getString(R.string.no_chapter)
|
?: getString(R.string.no_chapter)
|
||||||
|
binding.RightSwipeText.text = chaptersTitleArr.getOrNull(currentChapterIndex + 1)
|
||||||
|
?: getString(R.string.no_chapter)
|
||||||
binding.mangaReaderSwipy.onLeftSwiped = {
|
binding.mangaReaderSwipy.onLeftSwiped = {
|
||||||
binding.mangaReaderPreviousChapter.performClick()
|
binding.mangaReaderPreviousChapter.performClick()
|
||||||
}
|
}
|
||||||
|
@ -450,7 +514,8 @@ ThemeManager(this).applyTheme()
|
||||||
if (settings.default.layout != PAGED) {
|
if (settings.default.layout != PAGED) {
|
||||||
|
|
||||||
binding.mangaReaderRecyclerContainer.visibility = View.VISIBLE
|
binding.mangaReaderRecyclerContainer.visibility = View.VISIBLE
|
||||||
binding.mangaReaderRecyclerContainer.controller.settings.isRotationEnabled = settings.default.rotation
|
binding.mangaReaderRecyclerContainer.controller.settings.isRotationEnabled =
|
||||||
|
settings.default.rotation
|
||||||
|
|
||||||
val detector = GestureDetectorCompat(this, object : GesturesListener() {
|
val detector = GestureDetectorCompat(this, object : GesturesListener() {
|
||||||
override fun onLongPress(e: MotionEvent) {
|
override fun onLongPress(e: MotionEvent) {
|
||||||
|
@ -458,18 +523,31 @@ ThemeManager(this).applyTheme()
|
||||||
child ?: return@let false
|
child ?: return@let false
|
||||||
val pos = binding.mangaReaderRecycler.getChildAdapterPosition(child)
|
val pos = binding.mangaReaderRecycler.getChildAdapterPosition(child)
|
||||||
val callback: (ImageViewDialog) -> Unit = { dialog ->
|
val callback: (ImageViewDialog) -> Unit = { dialog ->
|
||||||
lifecycleScope.launch { imageAdapter?.loadImage(pos, child as GestureFrameLayout) }
|
lifecycleScope.launch {
|
||||||
binding.mangaReaderRecycler.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
imageAdapter?.loadImage(
|
||||||
|
pos,
|
||||||
|
child as GestureFrameLayout
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.mangaReaderRecycler.performHapticFeedback(
|
||||||
|
HapticFeedbackConstants.LONG_PRESS
|
||||||
|
)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
dualPage {
|
dualPage {
|
||||||
val page = chapter.dualPages().getOrNull(pos) ?: return@dualPage false
|
val page =
|
||||||
|
chapter.dualPages().getOrNull(pos) ?: return@dualPage false
|
||||||
val nextPage = page.second
|
val nextPage = page.second
|
||||||
if (settings.default.direction != LEFT_TO_RIGHT && nextPage != null)
|
if (settings.default.direction != LEFT_TO_RIGHT && nextPage != null)
|
||||||
onImageLongClicked(pos * 2, nextPage, page.first, callback)
|
onImageLongClicked(pos * 2, nextPage, page.first, callback)
|
||||||
else
|
else
|
||||||
onImageLongClicked(pos * 2, page.first, nextPage, callback)
|
onImageLongClicked(pos * 2, page.first, nextPage, callback)
|
||||||
} ?: onImageLongClicked(pos, chapImages.getOrNull(pos) ?: return@let false, null, callback)
|
} ?: onImageLongClicked(
|
||||||
|
pos,
|
||||||
|
chapImages.getOrNull(pos) ?: return@let false,
|
||||||
|
null,
|
||||||
|
callback
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) binding.mangaReaderRecycler.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
) binding.mangaReaderRecycler.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
super.onLongPress(e)
|
super.onLongPress(e)
|
||||||
|
@ -511,12 +589,16 @@ ThemeManager(this).applyTheme()
|
||||||
&& (!v.canScrollVertically(-1) || !v.canScrollVertically(1)))
|
&& (!v.canScrollVertically(-1) || !v.canScrollVertically(1)))
|
||||||
||
|
||
|
||||||
((direction == LEFT_TO_RIGHT || direction == RIGHT_TO_LEFT)
|
((direction == LEFT_TO_RIGHT || direction == RIGHT_TO_LEFT)
|
||||||
&& (!v.canScrollHorizontally(-1) || !v.canScrollHorizontally(1)))
|
&& (!v.canScrollHorizontally(-1) || !v.canScrollHorizontally(
|
||||||
|
1
|
||||||
|
)))
|
||||||
) {
|
) {
|
||||||
handleController(true)
|
handleController(true)
|
||||||
} else handleController(false)
|
} else handleController(false)
|
||||||
}
|
}
|
||||||
updatePageNumber(manager.findLastVisibleItemPosition().toLong() * (dualPage { 2 } ?: 1) + 1)
|
updatePageNumber(
|
||||||
|
manager.findLastVisibleItemPosition().toLong() * (dualPage { 2 }
|
||||||
|
?: 1) + 1)
|
||||||
super.onScrolled(v, dx, dy)
|
super.onScrolled(v, dx, dy)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -587,6 +669,7 @@ ThemeManager(this).applyTheme()
|
||||||
true
|
true
|
||||||
} else false
|
} else false
|
||||||
}
|
}
|
||||||
|
|
||||||
KEYCODE_VOLUME_DOWN, KEYCODE_DPAD_DOWN, KEYCODE_PAGE_DOWN -> {
|
KEYCODE_VOLUME_DOWN, KEYCODE_DPAD_DOWN, KEYCODE_PAGE_DOWN -> {
|
||||||
if (event.keyCode == KEYCODE_VOLUME_DOWN)
|
if (event.keyCode == KEYCODE_VOLUME_DOWN)
|
||||||
if (!settings.default.volumeButtons)
|
if (!settings.default.volumeButtons)
|
||||||
|
@ -596,6 +679,7 @@ ThemeManager(this).applyTheme()
|
||||||
true
|
true
|
||||||
} else false
|
} else false
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
super.dispatchKeyEvent(event)
|
super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
@ -670,8 +754,14 @@ ThemeManager(this).applyTheme()
|
||||||
isContVisible = false
|
isContVisible = false
|
||||||
if (!isAnimating) {
|
if (!isAnimating) {
|
||||||
isAnimating = true
|
isAnimating = true
|
||||||
ObjectAnimator.ofFloat(binding.mangaReaderCont, "alpha", 1f, 0f).setDuration(controllerDuration).start()
|
ObjectAnimator.ofFloat(binding.mangaReaderCont, "alpha", 1f, 0f)
|
||||||
ObjectAnimator.ofFloat(binding.mangaReaderBottomLayout, "translationY", 0f, 128f)
|
.setDuration(controllerDuration).start()
|
||||||
|
ObjectAnimator.ofFloat(
|
||||||
|
binding.mangaReaderBottomLayout,
|
||||||
|
"translationY",
|
||||||
|
0f,
|
||||||
|
128f
|
||||||
|
)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
ObjectAnimator.ofFloat(binding.mangaReaderTopLayout, "translationY", 0f, -128f)
|
ObjectAnimator.ofFloat(binding.mangaReaderTopLayout, "translationY", 0f, -128f)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
|
@ -680,7 +770,8 @@ ThemeManager(this).applyTheme()
|
||||||
} else {
|
} else {
|
||||||
isContVisible = true
|
isContVisible = true
|
||||||
binding.mangaReaderCont.visibility = View.VISIBLE
|
binding.mangaReaderCont.visibility = View.VISIBLE
|
||||||
ObjectAnimator.ofFloat(binding.mangaReaderCont, "alpha", 0f, 1f).setDuration(controllerDuration).start()
|
ObjectAnimator.ofFloat(binding.mangaReaderCont, "alpha", 0f, 1f)
|
||||||
|
.setDuration(controllerDuration).start()
|
||||||
ObjectAnimator.ofFloat(binding.mangaReaderTopLayout, "translationY", -128f, 0f)
|
ObjectAnimator.ofFloat(binding.mangaReaderTopLayout, "translationY", -128f, 0f)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
ObjectAnimator.ofFloat(binding.mangaReaderBottomLayout, "translationY", 128f, 0f)
|
ObjectAnimator.ofFloat(binding.mangaReaderBottomLayout, "translationY", 128f, 0f)
|
||||||
|
@ -719,7 +810,11 @@ ThemeManager(this).applyTheme()
|
||||||
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, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString())
|
updateProgress(
|
||||||
|
media,
|
||||||
|
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
runnable.run()
|
runnable.run()
|
||||||
}
|
}
|
||||||
|
@ -731,7 +826,11 @@ ThemeManager(this).applyTheme()
|
||||||
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, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString())
|
updateProgress(
|
||||||
|
media,
|
||||||
|
MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!)
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
runnable.run()
|
runnable.run()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,7 +10,8 @@ import kotlin.math.max
|
||||||
|
|
||||||
class PreloadLinearLayoutManager(context: Context, orientation: Int, reverseLayout: Boolean) :
|
class PreloadLinearLayoutManager(context: Context, orientation: Int, reverseLayout: Boolean) :
|
||||||
LinearLayoutManager(context, orientation, reverseLayout) {
|
LinearLayoutManager(context, orientation, reverseLayout) {
|
||||||
private val mOrientationHelper: OrientationHelper = OrientationHelper.createOrientationHelper(this, orientation)
|
private val mOrientationHelper: OrientationHelper =
|
||||||
|
OrientationHelper.createOrientationHelper(this, orientation)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As [LinearLayoutManager.collectAdjacentPrefetchPositions] will prefetch one view for us,
|
* As [LinearLayoutManager.collectAdjacentPrefetchPositions] will prefetch one view for us,
|
||||||
|
@ -37,7 +38,8 @@ class PreloadLinearLayoutManager(context: Context, orientation: Int, reverseLayo
|
||||||
val currentPosition: Int = getPosition(child ?: return) + layoutDirection
|
val currentPosition: Int = getPosition(child ?: return) + layoutDirection
|
||||||
|
|
||||||
if (layoutDirection == 1) {
|
if (layoutDirection == 1) {
|
||||||
val scrollingOffset = (mOrientationHelper.getDecoratedEnd(child) - mOrientationHelper.endAfterPadding)
|
val scrollingOffset =
|
||||||
|
(mOrientationHelper.getDecoratedEnd(child) - mOrientationHelper.endAfterPadding)
|
||||||
((currentPosition + 1) until (currentPosition + preloadItemCount + 1)).forEach {
|
((currentPosition + 1) until (currentPosition + preloadItemCount + 1)).forEach {
|
||||||
if (it >= 0 && it < state.itemCount) {
|
if (it >= 0 && it < state.itemCount) {
|
||||||
layoutPrefetchRegistry.addPosition(it, max(0, scrollingOffset))
|
layoutPrefetchRegistry.addPosition(it, max(0, scrollingOffset))
|
||||||
|
|
|
@ -14,7 +14,11 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
private var _binding: BottomSheetCurrentReaderSettingsBinding? = null
|
private var _binding: BottomSheetCurrentReaderSettingsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetCurrentReaderSettingsBinding.inflate(inflater, container, false)
|
_binding = BottomSheetCurrentReaderSettingsBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -24,11 +28,14 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
val activity = requireActivity() as MangaReaderActivity
|
val activity = requireActivity() as MangaReaderActivity
|
||||||
val settings = activity.settings.default
|
val settings = activity.settings.default
|
||||||
|
|
||||||
binding.readerDirectionText.text = resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]
|
binding.readerDirectionText.text =
|
||||||
|
resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]
|
||||||
binding.readerDirection.rotation = 90f * (settings.direction.ordinal)
|
binding.readerDirection.rotation = 90f * (settings.direction.ordinal)
|
||||||
binding.readerDirection.setOnClickListener {
|
binding.readerDirection.setOnClickListener {
|
||||||
settings.direction = Directions[settings.direction.ordinal + 1] ?: Directions.TOP_TO_BOTTOM
|
settings.direction =
|
||||||
binding.readerDirectionText.text = resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]
|
Directions[settings.direction.ordinal + 1] ?: Directions.TOP_TO_BOTTOM
|
||||||
|
binding.readerDirectionText.text =
|
||||||
|
resources.getStringArray(R.array.manga_directions)[settings.direction.ordinal]
|
||||||
binding.readerDirection.rotation = 90f * (settings.direction.ordinal)
|
binding.readerDirection.rotation = 90f * (settings.direction.ordinal)
|
||||||
activity.applySettings()
|
activity.applySettings()
|
||||||
}
|
}
|
||||||
|
@ -56,7 +63,8 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
activity.applySettings()
|
activity.applySettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.readerLayoutText.text = resources.getStringArray(R.array.manga_layouts)[settings.layout.ordinal]
|
binding.readerLayoutText.text =
|
||||||
|
resources.getStringArray(R.array.manga_layouts)[settings.layout.ordinal]
|
||||||
var selected = list[settings.layout.ordinal]
|
var selected = list[settings.layout.ordinal]
|
||||||
selected.alpha = 1f
|
selected.alpha = 1f
|
||||||
|
|
||||||
|
@ -65,8 +73,10 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
selected.alpha = 0.33f
|
selected.alpha = 0.33f
|
||||||
selected = imageButton
|
selected = imageButton
|
||||||
selected.alpha = 1f
|
selected.alpha = 1f
|
||||||
settings.layout = CurrentReaderSettings.Layouts[index]?:CurrentReaderSettings.Layouts.CONTINUOUS
|
settings.layout =
|
||||||
binding.readerLayoutText.text = resources.getStringArray(R.array.manga_layouts)[settings.layout.ordinal]
|
CurrentReaderSettings.Layouts[index] ?: CurrentReaderSettings.Layouts.CONTINUOUS
|
||||||
|
binding.readerLayoutText.text =
|
||||||
|
resources.getStringArray(R.array.manga_layouts)[settings.layout.ordinal]
|
||||||
activity.applySettings()
|
activity.applySettings()
|
||||||
paddingAvailable(settings.layout.ordinal != 0)
|
paddingAvailable(settings.layout.ordinal != 0)
|
||||||
}
|
}
|
||||||
|
@ -87,7 +97,8 @@ class ReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
selectedDual.alpha = 0.33f
|
selectedDual.alpha = 0.33f
|
||||||
selectedDual = imageButton
|
selectedDual = imageButton
|
||||||
selectedDual.alpha = 1f
|
selectedDual.alpha = 1f
|
||||||
settings.dualPageMode = CurrentReaderSettings.DualPageModes[index] ?: CurrentReaderSettings.DualPageModes.Automatic
|
settings.dualPageMode = CurrentReaderSettings.DualPageModes[index]
|
||||||
|
?: CurrentReaderSettings.DualPageModes.Automatic
|
||||||
binding.readerDualPageText.text = settings.dualPageMode.toString()
|
binding.readerDualPageText.text = settings.dualPageMode.toString()
|
||||||
activity.applySettings()
|
activity.applySettings()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
|
||||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
class RemoveBordersTransformation(private val white:Boolean, private val threshHold:Int) : BitmapTransformation() {
|
class RemoveBordersTransformation(private val white: Boolean, private val threshHold: Int) :
|
||||||
|
BitmapTransformation() {
|
||||||
|
|
||||||
override fun transform(
|
override fun transform(
|
||||||
pool: BitmapPool,
|
pool: BitmapPool,
|
||||||
|
|
|
@ -109,6 +109,7 @@ class Swipy @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
initialDown = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
initialDown = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
if (activePointerId == INVALID_POINTER) {
|
if (activePointerId == INVALID_POINTER) {
|
||||||
//("Got ACTION_MOVE event but don't have an active pointer id.")
|
//("Got ACTION_MOVE event but don't have an active pointer id.")
|
||||||
|
@ -121,6 +122,7 @@ class Swipy @JvmOverloads constructor(
|
||||||
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
|
||||||
startDragging(pos)
|
startDragging(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
|
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
|
||||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
isBeingDragged = false
|
isBeingDragged = false
|
||||||
|
@ -142,6 +144,7 @@ class Swipy @JvmOverloads constructor(
|
||||||
activePointerId = ev.getPointerId(0)
|
activePointerId = ev.getPointerId(0)
|
||||||
isBeingDragged = false
|
isBeingDragged = false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
pointerIndex = ev.findPointerIndex(activePointerId)
|
pointerIndex = ev.findPointerIndex(activePointerId)
|
||||||
if (pointerIndex < 0) {
|
if (pointerIndex < 0) {
|
||||||
|
@ -161,15 +164,15 @@ class Swipy @JvmOverloads constructor(
|
||||||
if (overscroll > 0) {
|
if (overscroll > 0) {
|
||||||
parent.requestDisallowInterceptTouchEvent(true)
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
|
val totalDragDistance =
|
||||||
|
Resources.getSystem().displayMetrics.heightPixels / dragDivider
|
||||||
if (verticalPos == VerticalPosition.Top)
|
if (verticalPos == VerticalPosition.Top)
|
||||||
topBeingSwiped.invoke(overscroll / totalDragDistance)
|
topBeingSwiped.invoke(overscroll / totalDragDistance)
|
||||||
else
|
else
|
||||||
bottomBeingSwiped.invoke(overscroll / totalDragDistance)
|
bottomBeingSwiped.invoke(overscroll / totalDragDistance)
|
||||||
}
|
} else {
|
||||||
|
val totalDragDistance =
|
||||||
else {
|
Resources.getSystem().displayMetrics.widthPixels / dragDivider
|
||||||
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
|
|
||||||
if (horizontalPos == HorizontalPosition.Left)
|
if (horizontalPos == HorizontalPosition.Left)
|
||||||
leftBeingSwiped.invoke(overscroll / totalDragDistance)
|
leftBeingSwiped.invoke(overscroll / totalDragDistance)
|
||||||
else
|
else
|
||||||
|
@ -180,6 +183,7 @@ class Swipy @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||||
pointerIndex = ev.actionIndex
|
pointerIndex = ev.actionIndex
|
||||||
if (pointerIndex < 0) {
|
if (pointerIndex < 0) {
|
||||||
|
@ -188,6 +192,7 @@ class Swipy @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
activePointerId = ev.getPointerId(pointerIndex)
|
activePointerId = ev.getPointerId(pointerIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
|
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
|
||||||
MotionEvent.ACTION_UP -> {
|
MotionEvent.ACTION_UP -> {
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
|
@ -216,6 +221,7 @@ class Swipy @JvmOverloads constructor(
|
||||||
activePointerId = INVALID_POINTER
|
activePointerId = INVALID_POINTER
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_CANCEL -> return false
|
MotionEvent.ACTION_CANCEL -> return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -242,8 +248,7 @@ class Swipy @JvmOverloads constructor(
|
||||||
onTopSwiped.invoke()
|
onTopSwiped.invoke()
|
||||||
else
|
else
|
||||||
onBottomSwiped.invoke()
|
onBottomSwiped.invoke()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
|
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
|
||||||
if (overscrollDistance > totalDragDistance)
|
if (overscrollDistance > totalDragDistance)
|
||||||
if (horizontalPos == HorizontalPosition.Left)
|
if (horizontalPos == HorizontalPosition.Left)
|
||||||
|
|
|
@ -13,8 +13,6 @@ import ani.dantotsu.loadImage
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
import ani.dantotsu.others.getSerialized
|
import ani.dantotsu.others.getSerialized
|
||||||
import ani.dantotsu.parsers.ShowResponse
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -29,6 +27,15 @@ class BookDialog : BottomSheetDialogFragment() {
|
||||||
private lateinit var novel: ShowResponse
|
private lateinit var novel: ShowResponse
|
||||||
private var source: Int = 0
|
private var source: Int = 0
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onDownloadTriggered(link: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var callback: Callback? = null
|
||||||
|
fun setCallback(callback: Callback) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
arguments?.let {
|
arguments?.let {
|
||||||
novelName = it.getString("novelName")!!
|
novelName = it.getString("novelName")!!
|
||||||
|
@ -38,7 +45,11 @@ class BookDialog : BottomSheetDialogFragment() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetBookBinding.inflate(inflater, container, false)
|
_binding = BottomSheetBookBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -51,7 +62,7 @@ class BookDialog : BottomSheetDialogFragment() {
|
||||||
binding.itemBookTitle.text = it.name
|
binding.itemBookTitle.text = it.name
|
||||||
binding.itemBookDesc.text = it.description
|
binding.itemBookDesc.text = it.description
|
||||||
binding.itemBookImage.loadImage(it.img)
|
binding.itemBookImage.loadImage(it.img)
|
||||||
binding.bookRecyclerView.adapter = UrlAdapter(it.links, it, novelName)
|
binding.bookRecyclerView.adapter = UrlAdapter(it.links, it, novelName, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
|
|
@ -22,12 +22,14 @@ class NovelReadAdapter(
|
||||||
var progress: View? = null
|
var progress: View? = null
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NovelReadAdapter.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NovelReadAdapter.ViewHolder {
|
||||||
val binding = ItemNovelHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding =
|
||||||
|
ItemNovelHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
progress = binding.progress.root
|
progress = binding.progress.root
|
||||||
return ViewHolder(binding)
|
return ViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val imm = fragment.requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
private val imm = fragment.requireContext()
|
||||||
|
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
|
@ -35,7 +37,8 @@ class NovelReadAdapter(
|
||||||
|
|
||||||
fun search(): Boolean {
|
fun search(): Boolean {
|
||||||
val query = binding.searchBarText.text.toString()
|
val query = binding.searchBarText.text.toString()
|
||||||
val source = media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it }
|
val source =
|
||||||
|
media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it }
|
||||||
fragment.source = source
|
fragment.source = source
|
||||||
|
|
||||||
binding.searchBarText.clearFocus()
|
binding.searchBarText.clearFocus()
|
||||||
|
@ -44,11 +47,18 @@ class NovelReadAdapter(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
val source = media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it }
|
val source =
|
||||||
|
media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it }
|
||||||
if (novelReadSources.names.isNotEmpty() && source in 0 until novelReadSources.names.size) {
|
if (novelReadSources.names.isNotEmpty() && source in 0 until novelReadSources.names.size) {
|
||||||
binding.animeSource.setText(novelReadSources.names[source], false)
|
binding.animeSource.setText(novelReadSources.names[source], false)
|
||||||
}
|
}
|
||||||
binding.animeSource.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, novelReadSources.names))
|
binding.animeSource.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
fragment.requireContext(),
|
||||||
|
R.layout.item_dropdown,
|
||||||
|
novelReadSources.names
|
||||||
|
)
|
||||||
|
)
|
||||||
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
binding.animeSource.setOnItemClickListener { _, _, i, _ ->
|
||||||
fragment.onSourceChange(i)
|
fragment.onSourceChange(i)
|
||||||
search()
|
search()
|
||||||
|
@ -64,7 +74,8 @@ class NovelReadAdapter(
|
||||||
binding.searchBar.setEndIconOnClickListener { search() }
|
binding.searchBar.setEndIconOnClickListener { search() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = 0
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ItemNovelHeaderBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class ViewHolder(val binding: ItemNovelHeaderBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
|
@ -1,12 +1,20 @@
|
||||||
package ani.dantotsu.media.novel
|
package ani.dantotsu.media.novel
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
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 androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
@ -14,16 +22,29 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||||
|
import ani.dantotsu.download.Download
|
||||||
|
import ani.dantotsu.download.DownloadsManager
|
||||||
|
import ani.dantotsu.download.novel.NovelDownloaderService
|
||||||
|
import ani.dantotsu.download.novel.NovelServiceDataSingleton
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsViewModel
|
import ani.dantotsu.media.MediaDetailsViewModel
|
||||||
|
import ani.dantotsu.media.novel.novelreader.NovelReaderActivity
|
||||||
import ani.dantotsu.navBarHeight
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.saveData
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class NovelReadFragment : Fragment() {
|
class NovelReadFragment : Fragment(),
|
||||||
|
DownloadTriggerCallback,
|
||||||
|
DownloadedCheckCallback {
|
||||||
|
|
||||||
private var _binding: FragmentAnimeWatchBinding? = null
|
private var _binding: FragmentAnimeWatchBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
@ -40,11 +61,141 @@ class NovelReadFragment : Fragment() {
|
||||||
private var continueEp: Boolean = false
|
private var continueEp: Boolean = false
|
||||||
var loaded = false
|
var loaded = false
|
||||||
|
|
||||||
val uiSettings = loadData("ui_settings", toast = false) ?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
val uiSettings = loadData("ui_settings", toast = false)
|
||||||
|
?: UserInterfaceSettings().apply { saveData("ui_settings", this) }
|
||||||
|
|
||||||
|
override fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage) {
|
||||||
|
Log.e("downloadTrigger", novelDownloadPackage.link)
|
||||||
|
val downloadTask = NovelDownloaderService.DownloadTask(
|
||||||
|
title = media.nameMAL ?: media.nameRomaji,
|
||||||
|
chapter = novelDownloadPackage.novelName,
|
||||||
|
downloadLink = novelDownloadPackage.link,
|
||||||
|
originalLink = novelDownloadPackage.originalLink,
|
||||||
|
sourceMedia = media,
|
||||||
|
coverUrl = novelDownloadPackage.coverUrl,
|
||||||
|
retries = 2,
|
||||||
|
)
|
||||||
|
NovelServiceDataSingleton.downloadQueue.offer(downloadTask)
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
|
||||||
|
if (!NovelServiceDataSingleton.isServiceRunning) {
|
||||||
|
val intent = Intent(context, NovelDownloaderService::class.java)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
ContextCompat.startForegroundService(requireContext(), intent)
|
||||||
|
}
|
||||||
|
NovelServiceDataSingleton.isServiceRunning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun downloadedCheckWithStart(novel: ShowResponse): Boolean {
|
||||||
|
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||||
|
if (downloadsManager.queryDownload(
|
||||||
|
Download(
|
||||||
|
media.nameMAL ?: media.nameRomaji,
|
||||||
|
novel.name,
|
||||||
|
Download.Type.NOVEL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
val file = File(
|
||||||
|
context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
"${DownloadsManager.novelLocation}/${media.nameMAL ?: media.nameRomaji}/${novel.name}/0.epub"
|
||||||
|
)
|
||||||
|
if (!file.exists()) return false
|
||||||
|
val fileUri = FileProvider.getUriForFile(
|
||||||
|
requireContext(),
|
||||||
|
"${requireContext().packageName}.provider",
|
||||||
|
file
|
||||||
|
)
|
||||||
|
val intent = Intent(context, NovelReaderActivity::class.java).apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
setDataAndType(fileUri, "application/epub+zip")
|
||||||
|
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun downloadedCheck(novel: ShowResponse): Boolean {
|
||||||
|
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||||
|
return downloadsManager.queryDownload(
|
||||||
|
Download(
|
||||||
|
media.nameMAL ?: media.nameRomaji,
|
||||||
|
novel.name,
|
||||||
|
Download.Type.NOVEL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteDownload(novel: ShowResponse) {
|
||||||
|
val downloadsManager = Injekt.get<DownloadsManager>()
|
||||||
|
downloadsManager.removeDownload(
|
||||||
|
Download(
|
||||||
|
media.nameMAL ?: media.nameRomaji,
|
||||||
|
novel.name,
|
||||||
|
Download.Type.NOVEL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val downloadStatusReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (!this@NovelReadFragment::novelResponseAdapter.isInitialized) return
|
||||||
|
when (intent.action) {
|
||||||
|
ACTION_DOWNLOAD_STARTED -> {
|
||||||
|
val link = intent.getStringExtra(EXTRA_NOVEL_LINK)
|
||||||
|
link?.let {
|
||||||
|
novelResponseAdapter.startDownload(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_DOWNLOAD_FINISHED -> {
|
||||||
|
val link = intent.getStringExtra(EXTRA_NOVEL_LINK)
|
||||||
|
link?.let {
|
||||||
|
novelResponseAdapter.stopDownload(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_DOWNLOAD_FAILED -> {
|
||||||
|
val link = intent.getStringExtra(EXTRA_NOVEL_LINK)
|
||||||
|
link?.let {
|
||||||
|
novelResponseAdapter.purgeDownload(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_DOWNLOAD_PROGRESS -> {
|
||||||
|
val link = intent.getStringExtra(EXTRA_NOVEL_LINK)
|
||||||
|
val progress = intent.getIntExtra("progress", 0)
|
||||||
|
link?.let {
|
||||||
|
novelResponseAdapter.updateDownloadProgress(it, progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var response: List<ShowResponse>? = null
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
val intentFilter = IntentFilter().apply {
|
||||||
|
addAction(ACTION_DOWNLOAD_STARTED)
|
||||||
|
addAction(ACTION_DOWNLOAD_FINISHED)
|
||||||
|
addAction(ACTION_DOWNLOAD_FAILED)
|
||||||
|
addAction(ACTION_DOWNLOAD_PROGRESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextCompat.registerReceiver(
|
||||||
|
requireContext(),
|
||||||
|
downloadStatusReceiver,
|
||||||
|
intentFilter,
|
||||||
|
ContextCompat.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
|
|
||||||
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight)
|
||||||
|
|
||||||
binding.animeSourceRecycler.layoutManager = LinearLayoutManager(requireContext())
|
binding.animeSourceRecycler.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
@ -63,8 +214,13 @@ class NovelReadFragment : Fragment() {
|
||||||
val sel = media.selected
|
val sel = media.selected
|
||||||
searchQuery = sel?.server ?: media.name ?: media.nameRomaji
|
searchQuery = sel?.server ?: media.name ?: media.nameRomaji
|
||||||
headerAdapter = NovelReadAdapter(media, this, model.novelSources)
|
headerAdapter = NovelReadAdapter(media, this, model.novelSources)
|
||||||
novelResponseAdapter = NovelResponseAdapter(this)
|
novelResponseAdapter = NovelResponseAdapter(
|
||||||
binding.animeSourceRecycler.adapter = ConcatAdapter(headerAdapter, novelResponseAdapter)
|
this,
|
||||||
|
this,
|
||||||
|
this
|
||||||
|
) // probably a better way to do this but it works
|
||||||
|
binding.animeSourceRecycler.adapter =
|
||||||
|
ConcatAdapter(headerAdapter, novelResponseAdapter)
|
||||||
loaded = true
|
loaded = true
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
search(searchQuery, sel?.sourceIndex ?: 0, auto = sel?.server == null)
|
search(searchQuery, sel?.sourceIndex ?: 0, auto = sel?.server == null)
|
||||||
|
@ -74,6 +230,7 @@ class NovelReadFragment : Fragment() {
|
||||||
}
|
}
|
||||||
model.novelResponses.observe(viewLifecycleOwner) {
|
model.novelResponses.observe(viewLifecycleOwner) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
|
response = it
|
||||||
searching = false
|
searching = false
|
||||||
novelResponseAdapter.submitList(it)
|
novelResponseAdapter.submitList(it)
|
||||||
headerAdapter.progress?.visibility = View.GONE
|
headerAdapter.progress?.visibility = View.GONE
|
||||||
|
@ -90,7 +247,8 @@ class NovelReadFragment : Fragment() {
|
||||||
headerAdapter.progress?.visibility = View.VISIBLE
|
headerAdapter.progress?.visibility = View.VISIBLE
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
if (auto || query == "") model.autoSearchNovels(media)
|
if (auto || query == "") model.autoSearchNovels(media)
|
||||||
else model.searchNovels(query, source)
|
//else model.searchNovels(query, source)
|
||||||
|
else model.autoSearchNovels(media) //testing
|
||||||
}
|
}
|
||||||
searching = true
|
searching = true
|
||||||
if (save) {
|
if (save) {
|
||||||
|
@ -121,6 +279,7 @@ class NovelReadFragment : Fragment() {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
model.mangaReadSources?.flushText()
|
model.mangaReadSources?.flushText()
|
||||||
|
requireContext().unregisterReceiver(downloadStatusReceiver)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,4 +294,22 @@ class NovelReadFragment : Fragment() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState()
|
state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ACTION_DOWNLOAD_STARTED = "ani.dantotsu.ACTION_DOWNLOAD_STARTED"
|
||||||
|
const val ACTION_DOWNLOAD_FINISHED = "ani.dantotsu.ACTION_DOWNLOAD_FINISHED"
|
||||||
|
const val ACTION_DOWNLOAD_FAILED = "ani.dantotsu.ACTION_DOWNLOAD_FAILED"
|
||||||
|
const val ACTION_DOWNLOAD_PROGRESS = "ani.dantotsu.ACTION_DOWNLOAD_PROGRESS"
|
||||||
|
const val EXTRA_NOVEL_LINK = "extra_novel_link"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownloadTriggerCallback {
|
||||||
|
fun downloadTrigger(novelDownloadPackage: NovelDownloadPackage)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DownloadedCheckCallback {
|
||||||
|
fun downloadedCheck(novel: ShowResponse): Boolean
|
||||||
|
fun downloadedCheckWithStart(novel: ShowResponse): Boolean
|
||||||
|
fun deleteDownload(novel: ShowResponse)
|
||||||
}
|
}
|
|
@ -1,22 +1,32 @@
|
||||||
package ani.dantotsu.media.novel
|
package ani.dantotsu.media.novel
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.databinding.ItemNovelResponseBinding
|
import ani.dantotsu.databinding.ItemNovelResponseBinding
|
||||||
import ani.dantotsu.parsers.ShowResponse
|
import ani.dantotsu.parsers.ShowResponse
|
||||||
import ani.dantotsu.setAnimation
|
import ani.dantotsu.setAnimation
|
||||||
|
import ani.dantotsu.snackString
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
|
|
||||||
class NovelResponseAdapter(val fragment: NovelReadFragment) : RecyclerView.Adapter<NovelResponseAdapter.ViewHolder>() {
|
class NovelResponseAdapter(
|
||||||
|
val fragment: NovelReadFragment,
|
||||||
|
val downloadTriggerCallback: DownloadTriggerCallback,
|
||||||
|
val downloadedCheckCallback: DownloadedCheckCallback
|
||||||
|
) : RecyclerView.Adapter<NovelResponseAdapter.ViewHolder>() {
|
||||||
val list: MutableList<ShowResponse> = mutableListOf()
|
val list: MutableList<ShowResponse> = mutableListOf()
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ItemNovelResponseBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class ViewHolder(val binding: ItemNovelResponseBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val bind = ItemNovelResponseBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val bind =
|
||||||
|
ItemNovelResponseBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return ViewHolder(bind)
|
return ViewHolder(bind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,18 +38,141 @@ class NovelResponseAdapter(val fragment: NovelReadFragment) : RecyclerView.Adapt
|
||||||
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
setAnimation(fragment.requireContext(), holder.binding.root, fragment.uiSettings)
|
||||||
|
|
||||||
val cover = GlideUrl(novel.coverUrl.url) { novel.coverUrl.headers }
|
val cover = GlideUrl(novel.coverUrl.url) { novel.coverUrl.headers }
|
||||||
Glide.with(binding.itemEpisodeImage).load(cover).override(400,0).into(binding.itemEpisodeImage)
|
Glide.with(binding.itemEpisodeImage).load(cover).override(400, 0)
|
||||||
|
.into(binding.itemEpisodeImage)
|
||||||
|
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
fragment.requireContext().theme?.resolveAttribute(com.google.android.material.R.attr.colorOnBackground, typedValue, true)
|
||||||
|
val color = typedValue.data
|
||||||
|
|
||||||
binding.itemEpisodeTitle.text = novel.name
|
binding.itemEpisodeTitle.text = novel.name
|
||||||
binding.itemEpisodeFiller.text = novel.extra?.get("0") ?: ""
|
binding.itemEpisodeFiller.text =
|
||||||
|
if (downloadedCheckCallback.downloadedCheck(novel)) {
|
||||||
|
"Downloaded"
|
||||||
|
} else {
|
||||||
|
novel.extra?.get("0") ?: ""
|
||||||
|
}
|
||||||
|
if (binding.itemEpisodeFiller.text.contains("Downloading")) {
|
||||||
|
binding.itemEpisodeFiller.setTextColor(
|
||||||
|
fragment.requireContext().getColor(android.R.color.holo_blue_light)
|
||||||
|
)
|
||||||
|
} else if (binding.itemEpisodeFiller.text.contains("Downloaded")) {
|
||||||
|
binding.itemEpisodeFiller.setTextColor(
|
||||||
|
fragment.requireContext().getColor(android.R.color.holo_green_light)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
binding.itemEpisodeFiller.setTextColor(color)
|
||||||
|
}
|
||||||
binding.itemEpisodeDesc2.text = novel.extra?.get("1") ?: ""
|
binding.itemEpisodeDesc2.text = novel.extra?.get("1") ?: ""
|
||||||
val desc = novel.extra?.get("2")
|
val desc = novel.extra?.get("2")
|
||||||
binding.itemEpisodeDesc.visibility = if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE
|
binding.itemEpisodeDesc.visibility =
|
||||||
|
if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE
|
||||||
binding.itemEpisodeDesc.text = desc ?: ""
|
binding.itemEpisodeDesc.text = desc ?: ""
|
||||||
|
|
||||||
binding.root.setOnClickListener {
|
binding.root.setOnClickListener {
|
||||||
BookDialog.newInstance(fragment.novelName, novel, fragment.source)
|
//make sure the file is not downloading
|
||||||
.show(fragment.parentFragmentManager, "dialog")
|
if (activeDownloads.contains(novel.link)) {
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
if (downloadedCheckCallback.downloadedCheckWithStart(novel)) {
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val bookDialog = BookDialog.newInstance(fragment.novelName, novel, fragment.source)
|
||||||
|
|
||||||
|
bookDialog.setCallback(object : BookDialog.Callback {
|
||||||
|
override fun onDownloadTriggered(link: String) {
|
||||||
|
downloadTriggerCallback.downloadTrigger(
|
||||||
|
NovelDownloadPackage(
|
||||||
|
link,
|
||||||
|
novel.coverUrl.url,
|
||||||
|
novel.name,
|
||||||
|
novel.link
|
||||||
|
)
|
||||||
|
)
|
||||||
|
bookDialog.dismiss()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
bookDialog.show(fragment.parentFragmentManager, "dialog")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.root.setOnLongClickListener {
|
||||||
|
val builder = androidx.appcompat.app.AlertDialog.Builder(fragment.requireContext(), R.style.DialogTheme)
|
||||||
|
builder.setTitle("Delete ${novel.name}?")
|
||||||
|
builder.setMessage("Are you sure you want to delete ${novel.name}?")
|
||||||
|
builder.setPositiveButton("Yes") { _, _ ->
|
||||||
|
downloadedCheckCallback.deleteDownload(novel)
|
||||||
|
deleteDownload(novel.link)
|
||||||
|
snackString("Deleted ${novel.name}")
|
||||||
|
if (binding.itemEpisodeFiller.text.toString().contains("Download", ignoreCase = true)) {
|
||||||
|
binding.itemEpisodeFiller.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.setNegativeButton("No") { _, _ ->
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
builder.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val activeDownloads = mutableSetOf<String>()
|
||||||
|
private val downloadedChapters = mutableSetOf<String>()
|
||||||
|
|
||||||
|
fun startDownload(link: String) {
|
||||||
|
activeDownloads.add(link)
|
||||||
|
val position = list.indexOfFirst { it.link == link }
|
||||||
|
if (position != -1) {
|
||||||
|
list[position].extra?.remove("0")
|
||||||
|
list[position].extra?.set("0", "Downloading: 0%")
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopDownload(link: String) {
|
||||||
|
activeDownloads.remove(link)
|
||||||
|
downloadedChapters.add(link)
|
||||||
|
val position = list.indexOfFirst { it.link == link }
|
||||||
|
if (position != -1) {
|
||||||
|
list[position].extra?.remove("0")
|
||||||
|
list[position].extra?.set("0", "Downloaded")
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteDownload(link: String) { //TODO:
|
||||||
|
downloadedChapters.remove(link)
|
||||||
|
val position = list.indexOfFirst { it.link == link }
|
||||||
|
if (position != -1) {
|
||||||
|
list[position].extra?.remove("0")
|
||||||
|
list[position].extra?.set("0", "")
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun purgeDownload(link: String) {
|
||||||
|
activeDownloads.remove(link)
|
||||||
|
downloadedChapters.remove(link)
|
||||||
|
val position = list.indexOfFirst { it.link == link }
|
||||||
|
if (position != -1) {
|
||||||
|
list[position].extra?.remove("0")
|
||||||
|
list[position].extra?.set("0", "Failed")
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDownloadProgress(link: String, progress: Int) {
|
||||||
|
if (!activeDownloads.contains(link)) {
|
||||||
|
activeDownloads.add(link)
|
||||||
|
}
|
||||||
|
val position = list.indexOfFirst { it.link == link }
|
||||||
|
if (position != -1) {
|
||||||
|
list[position].extra?.remove("0")
|
||||||
|
list[position].extra?.set("0", "Downloading: $progress%")
|
||||||
|
Log.d("NovelResponseAdapter", "updateDownloadProgress: $progress, position: $position")
|
||||||
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,3 +188,10 @@ class NovelResponseAdapter(val fragment: NovelReadFragment) : RecyclerView.Adapt
|
||||||
notifyItemRangeRemoved(0, size)
|
notifyItemRangeRemoved(0, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class NovelDownloadPackage(
|
||||||
|
val link: String,
|
||||||
|
val coverUrl: String,
|
||||||
|
val novelName: String,
|
||||||
|
val originalLink: String
|
||||||
|
)
|
|
@ -9,16 +9,26 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.FileUrl
|
import ani.dantotsu.FileUrl
|
||||||
import ani.dantotsu.copyToClipboard
|
import ani.dantotsu.copyToClipboard
|
||||||
import ani.dantotsu.databinding.ItemUrlBinding
|
import ani.dantotsu.databinding.ItemUrlBinding
|
||||||
import ani.dantotsu.others.Download.download
|
|
||||||
import ani.dantotsu.parsers.Book
|
import ani.dantotsu.parsers.Book
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.tryWith
|
import ani.dantotsu.tryWith
|
||||||
|
|
||||||
class UrlAdapter(private val urls: List<FileUrl>, val book: Book, val novel: String) :
|
class UrlAdapter(
|
||||||
|
private val urls: List<FileUrl>,
|
||||||
|
val book: Book,
|
||||||
|
val novel: String,
|
||||||
|
val callback: BookDialog.Callback?
|
||||||
|
) :
|
||||||
RecyclerView.Adapter<UrlAdapter.UrlViewHolder>() {
|
RecyclerView.Adapter<UrlAdapter.UrlViewHolder>() {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UrlViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UrlViewHolder {
|
||||||
return UrlViewHolder(ItemUrlBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
return UrlViewHolder(
|
||||||
|
ItemUrlBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
|
@ -26,6 +36,7 @@ class UrlAdapter(private val urls: List<FileUrl>, val book: Book, val novel: Str
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
val url = urls[position]
|
val url = urls[position]
|
||||||
binding.urlQuality.text = url.url
|
binding.urlQuality.text = url.url
|
||||||
|
binding.urlQuality.maxLines = 4
|
||||||
binding.urlDownload.visibility = View.VISIBLE
|
binding.urlDownload.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,12 +47,14 @@ class UrlAdapter(private val urls: List<FileUrl>, val book: Book, val novel: Str
|
||||||
itemView.setSafeOnClickListener {
|
itemView.setSafeOnClickListener {
|
||||||
tryWith(true) {
|
tryWith(true) {
|
||||||
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
binding.urlDownload.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
download(
|
callback?.onDownloadTriggered(book.links[bindingAdapterPosition].url)
|
||||||
|
/*download(
|
||||||
itemView.context,
|
itemView.context,
|
||||||
book,
|
book,
|
||||||
bindingAdapterPosition,
|
bindingAdapterPosition,
|
||||||
novel
|
novel
|
||||||
)
|
)*/
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itemView.setOnLongClickListener {
|
itemView.setOnLongClickListener {
|
||||||
|
|
|
@ -2,8 +2,10 @@ package ani.dantotsu.media.novel.novelreader
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
@ -14,12 +16,14 @@ import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.view.animation.OvershootInterpolator
|
import android.view.animation.OvershootInterpolator
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.webkit.WebViewCompat
|
||||||
import ani.dantotsu.GesturesListener
|
import ani.dantotsu.GesturesListener
|
||||||
import ani.dantotsu.NoPaddingArrayAdapter
|
import ani.dantotsu.NoPaddingArrayAdapter
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
@ -27,6 +31,7 @@ import ani.dantotsu.databinding.ActivityNovelReaderBinding
|
||||||
import ani.dantotsu.hideSystemBars
|
import ani.dantotsu.hideSystemBars
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
import ani.dantotsu.others.ImageViewDialog
|
import ani.dantotsu.others.ImageViewDialog
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.saveData
|
import ani.dantotsu.saveData
|
||||||
import ani.dantotsu.setSafeOnClickListener
|
import ani.dantotsu.setSafeOnClickListener
|
||||||
import ani.dantotsu.settings.CurrentNovelReaderSettings
|
import ani.dantotsu.settings.CurrentNovelReaderSettings
|
||||||
|
@ -35,7 +40,6 @@ import ani.dantotsu.settings.NovelReaderSettings
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import ani.dantotsu.tryWith
|
import ani.dantotsu.tryWith
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import com.vipulog.ebookreader.Book
|
import com.vipulog.ebookreader.Book
|
||||||
|
@ -137,6 +141,20 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
//check for supported webview
|
||||||
|
val webViewVersion = WebViewCompat.getCurrentWebViewPackage(this)?.versionName
|
||||||
|
val firstVersion = webViewVersion?.split(".")?.firstOrNull()?.toIntOrNull()
|
||||||
|
if (webViewVersion == null || firstVersion == null || firstVersion < 87) {
|
||||||
|
Toast.makeText(this, "Please update WebView from PlayStore", Toast.LENGTH_LONG).show()
|
||||||
|
//open playstore
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.data = Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.webview")
|
||||||
|
startActivity(intent)
|
||||||
|
//stop reader
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
LangSet.setLocale(this)
|
LangSet.setLocale(this)
|
||||||
ThemeManager(this).applyTheme()
|
ThemeManager(this).applyTheme()
|
||||||
binding = ActivityNovelReaderBinding.inflate(layoutInflater)
|
binding = ActivityNovelReaderBinding.inflate(layoutInflater)
|
||||||
|
@ -161,7 +179,8 @@ ThemeManager(this).applyTheme()
|
||||||
|
|
||||||
binding.novelReaderBack.setOnClickListener { finish() }
|
binding.novelReaderBack.setOnClickListener { finish() }
|
||||||
binding.novelReaderSettings.setSafeOnClickListener {
|
binding.novelReaderSettings.setSafeOnClickListener {
|
||||||
NovelReaderSettingsDialogFragment.newInstance().show(supportFragmentManager, NovelReaderSettingsDialogFragment.TAG)
|
NovelReaderSettingsDialogFragment.newInstance()
|
||||||
|
.show(supportFragmentManager, NovelReaderSettingsDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
val gestureDetector = GestureDetectorCompat(this, object : GesturesListener() {
|
val gestureDetector = GestureDetectorCompat(this, object : GesturesListener() {
|
||||||
|
@ -233,9 +252,16 @@ ThemeManager(this).applyTheme()
|
||||||
binding.novelReaderSource.text = book.author?.joinToString(", ")
|
binding.novelReaderSource.text = book.author?.joinToString(", ")
|
||||||
|
|
||||||
val tocLabels = book.toc.map { it.label ?: "" }
|
val tocLabels = book.toc.map { it.label ?: "" }
|
||||||
binding.novelReaderChapterSelect.adapter = NoPaddingArrayAdapter(this, R.layout.item_dropdown, tocLabels)
|
binding.novelReaderChapterSelect.adapter =
|
||||||
binding.novelReaderChapterSelect.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
NoPaddingArrayAdapter(this, R.layout.item_dropdown, tocLabels)
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
binding.novelReaderChapterSelect.onItemSelectedListener =
|
||||||
|
object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(
|
||||||
|
parent: AdapterView<*>?,
|
||||||
|
view: View?,
|
||||||
|
position: Int,
|
||||||
|
id: Long
|
||||||
|
) {
|
||||||
binding.bookReader.goto(book.toc[position].href)
|
binding.bookReader.goto(book.toc[position].href)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +352,8 @@ ThemeManager(this).applyTheme()
|
||||||
saveData("${sanitizedBookId}_current_settings", settings.default)
|
saveData("${sanitizedBookId}_current_settings", settings.default)
|
||||||
hideBars()
|
hideBars()
|
||||||
|
|
||||||
currentTheme = themes.first { it.name.equals(settings.default.currentThemeName, ignoreCase = true) }
|
currentTheme =
|
||||||
|
themes.first { it.name.equals(settings.default.currentThemeName, ignoreCase = true) }
|
||||||
|
|
||||||
when (settings.default.layout) {
|
when (settings.default.layout) {
|
||||||
CurrentNovelReaderSettings.Layouts.PAGED -> {
|
CurrentNovelReaderSettings.Layouts.PAGED -> {
|
||||||
|
@ -342,7 +369,8 @@ ThemeManager(this).applyTheme()
|
||||||
when (settings.default.dualPageMode) {
|
when (settings.default.dualPageMode) {
|
||||||
CurrentReaderSettings.DualPageModes.No -> currentTheme?.maxColumnCount = 1
|
CurrentReaderSettings.DualPageModes.No -> currentTheme?.maxColumnCount = 1
|
||||||
CurrentReaderSettings.DualPageModes.Automatic -> currentTheme?.maxColumnCount = 2
|
CurrentReaderSettings.DualPageModes.Automatic -> currentTheme?.maxColumnCount = 2
|
||||||
CurrentReaderSettings.DualPageModes.Force -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
CurrentReaderSettings.DualPageModes.Force -> requestedOrientation =
|
||||||
|
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTheme?.lineHeight = settings.default.lineHeight
|
currentTheme?.lineHeight = settings.default.lineHeight
|
||||||
|
@ -393,7 +421,8 @@ ThemeManager(this).applyTheme()
|
||||||
isContVisible = false
|
isContVisible = false
|
||||||
if (!isAnimating) {
|
if (!isAnimating) {
|
||||||
isAnimating = true
|
isAnimating = true
|
||||||
ObjectAnimator.ofFloat(binding.novelReaderCont, "alpha", 1f, 0f).setDuration(controllerDuration).start()
|
ObjectAnimator.ofFloat(binding.novelReaderCont, "alpha", 1f, 0f)
|
||||||
|
.setDuration(controllerDuration).start()
|
||||||
ObjectAnimator.ofFloat(binding.novelReaderBottomCont, "translationY", 0f, 128f)
|
ObjectAnimator.ofFloat(binding.novelReaderBottomCont, "translationY", 0f, 128f)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
ObjectAnimator.ofFloat(binding.novelReaderTopLayout, "translationY", 0f, -128f)
|
ObjectAnimator.ofFloat(binding.novelReaderTopLayout, "translationY", 0f, -128f)
|
||||||
|
@ -403,7 +432,8 @@ ThemeManager(this).applyTheme()
|
||||||
} else {
|
} else {
|
||||||
isContVisible = true
|
isContVisible = true
|
||||||
binding.novelReaderCont.visibility = View.VISIBLE
|
binding.novelReaderCont.visibility = View.VISIBLE
|
||||||
ObjectAnimator.ofFloat(binding.novelReaderCont, "alpha", 0f, 1f).setDuration(controllerDuration).start()
|
ObjectAnimator.ofFloat(binding.novelReaderCont, "alpha", 0f, 1f)
|
||||||
|
.setDuration(controllerDuration).start()
|
||||||
ObjectAnimator.ofFloat(binding.novelReaderTopLayout, "translationY", -128f, 0f)
|
ObjectAnimator.ofFloat(binding.novelReaderTopLayout, "translationY", -128f, 0f)
|
||||||
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
.apply { interpolator = overshoot;duration = controllerDuration;start() }
|
||||||
ObjectAnimator.ofFloat(binding.novelReaderBottomCont, "translationY", 128f, 0f)
|
ObjectAnimator.ofFloat(binding.novelReaderBottomCont, "translationY", 128f, 0f)
|
||||||
|
@ -418,7 +448,10 @@ ThemeManager(this).applyTheme()
|
||||||
val displayCutout = window.decorView.rootWindowInsets.displayCutout
|
val displayCutout = window.decorView.rootWindowInsets.displayCutout
|
||||||
if (displayCutout != null) {
|
if (displayCutout != null) {
|
||||||
if (displayCutout.boundingRects.size > 0) {
|
if (displayCutout.boundingRects.size > 0) {
|
||||||
notchHeight = min(displayCutout.boundingRects[0].width(), displayCutout.boundingRects[0].height())
|
notchHeight = min(
|
||||||
|
displayCutout.boundingRects[0].width(),
|
||||||
|
displayCutout.boundingRects[0].height()
|
||||||
|
)
|
||||||
applyNotchMargin()
|
applyNotchMargin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,11 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetCurrentNovelReaderSettingsBinding.inflate(inflater, container, false)
|
_binding = BottomSheetCurrentNovelReaderSettingsBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -29,10 +33,16 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
val settings = activity.settings.default
|
val settings = activity.settings.default
|
||||||
|
|
||||||
val themeLabels = activity.themes.map { it.name }
|
val themeLabels = activity.themes.map { it.name }
|
||||||
binding.themeSelect.adapter = NoPaddingArrayAdapter(activity, R.layout.item_dropdown, themeLabels)
|
binding.themeSelect.adapter =
|
||||||
|
NoPaddingArrayAdapter(activity, R.layout.item_dropdown, themeLabels)
|
||||||
binding.themeSelect.setSelection(themeLabels.indexOfFirst { it == settings.currentThemeName })
|
binding.themeSelect.setSelection(themeLabels.indexOfFirst { it == settings.currentThemeName })
|
||||||
binding.themeSelect.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.themeSelect.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(
|
||||||
|
parent: AdapterView<*>?,
|
||||||
|
view: View?,
|
||||||
|
position: Int,
|
||||||
|
id: Long
|
||||||
|
) {
|
||||||
settings.currentThemeName = themeLabels[position]
|
settings.currentThemeName = themeLabels[position]
|
||||||
activity.applySettings()
|
activity.applySettings()
|
||||||
}
|
}
|
||||||
|
@ -54,7 +64,8 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
selected.alpha = 0.33f
|
selected.alpha = 0.33f
|
||||||
selected = imageButton
|
selected = imageButton
|
||||||
selected.alpha = 1f
|
selected.alpha = 1f
|
||||||
settings.layout = CurrentNovelReaderSettings.Layouts[index]?:CurrentNovelReaderSettings.Layouts.PAGED
|
settings.layout = CurrentNovelReaderSettings.Layouts[index]
|
||||||
|
?: CurrentNovelReaderSettings.Layouts.PAGED
|
||||||
binding.layoutText.text = settings.layout.string
|
binding.layoutText.text = settings.layout.string
|
||||||
activity.applySettings()
|
activity.applySettings()
|
||||||
}
|
}
|
||||||
|
@ -75,7 +86,8 @@ class NovelReaderSettingsDialogFragment : BottomSheetDialogFragment() {
|
||||||
selectedDual.alpha = 0.33f
|
selectedDual.alpha = 0.33f
|
||||||
selectedDual = imageButton
|
selectedDual = imageButton
|
||||||
selectedDual.alpha = 1f
|
selectedDual.alpha = 1f
|
||||||
settings.dualPageMode = CurrentReaderSettings.DualPageModes[index] ?: CurrentReaderSettings.DualPageModes.Automatic
|
settings.dualPageMode = CurrentReaderSettings.DualPageModes[index]
|
||||||
|
?: CurrentReaderSettings.DualPageModes.Automatic
|
||||||
binding.dualPageText.text = settings.dualPageMode.toString()
|
binding.dualPageText.text = settings.dualPageMode.toString()
|
||||||
activity.applySettings()
|
activity.applySettings()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ import ani.dantotsu.R
|
||||||
import ani.dantotsu.Refresh
|
import ani.dantotsu.Refresh
|
||||||
import ani.dantotsu.databinding.ActivityListBinding
|
import ani.dantotsu.databinding.ActivityListBinding
|
||||||
import ani.dantotsu.loadData
|
import ani.dantotsu.loadData
|
||||||
|
import ani.dantotsu.others.LangSet
|
||||||
import ani.dantotsu.settings.UserInterfaceSettings
|
import ani.dantotsu.settings.UserInterfaceSettings
|
||||||
import ani.dantotsu.themes.ThemeManager
|
import ani.dantotsu.themes.ThemeManager
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -41,7 +41,11 @@ ThemeManager(this).applyTheme()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true)
|
||||||
val primaryColor = typedValue.data
|
val primaryColor = typedValue.data
|
||||||
val typedValue2 = TypedValue()
|
val typedValue2 = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorOnBackground, typedValue2, true)
|
theme.resolveAttribute(
|
||||||
|
com.google.android.material.R.attr.colorOnBackground,
|
||||||
|
typedValue2,
|
||||||
|
true
|
||||||
|
)
|
||||||
val titleTextColor = typedValue2.data
|
val titleTextColor = typedValue2.data
|
||||||
val typedValue3 = TypedValue()
|
val typedValue3 = TypedValue()
|
||||||
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue3, true)
|
theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue3, true)
|
||||||
|
@ -54,7 +58,7 @@ ThemeManager(this).applyTheme()
|
||||||
window.navigationBarColor = primaryColor
|
window.navigationBarColor = primaryColor
|
||||||
binding.listTabLayout.setBackgroundColor(primaryColor)
|
binding.listTabLayout.setBackgroundColor(primaryColor)
|
||||||
binding.listAppBar.setBackgroundColor(primaryColor)
|
binding.listAppBar.setBackgroundColor(primaryColor)
|
||||||
binding.listTitle.setTextColor(titleTextColor)
|
binding.listTitle.setTextColor(primaryTextColor)
|
||||||
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
|
binding.listTabLayout.setTabTextColors(secondaryTextColor, primaryTextColor)
|
||||||
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
binding.listTabLayout.setSelectedTabIndicatorColor(primaryTextColor)
|
||||||
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
val uiSettings = loadData<UserInterfaceSettings>("ui_settings") ?: UserInterfaceSettings()
|
||||||
|
@ -66,30 +70,47 @@ ThemeManager(this).applyTheme()
|
||||||
} else {
|
} else {
|
||||||
binding.root.fitsSystemWindows = false
|
binding.root.fitsSystemWindows = false
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
window.setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||||
|
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||||
|
)
|
||||||
}
|
}
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
val anime = intent.getBooleanExtra("anime", true)
|
val anime = intent.getBooleanExtra("anime", true)
|
||||||
binding.listTitle.text = intent.getStringExtra("username") + "'s " + (if (anime) "Anime" else "Manga") + " List"
|
binding.listTitle.text =
|
||||||
|
intent.getStringExtra("username") + "'s " + (if (anime) "Anime" else "Manga") + " List"
|
||||||
|
|
||||||
binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||||
this@ListActivity.selectedTabIdx = tab?.position ?: 0
|
this@ListActivity.selectedTabIdx = tab?.position ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||||
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
val model: ListViewModel by viewModels()
|
val model: ListViewModel by viewModels()
|
||||||
model.getLists().observe(this) {
|
model.getLists().observe(this) {
|
||||||
val defaultKeys = listOf("Reading", "Watching", "Completed", "Paused", "Dropped", "Planning", "Favourites", "Rewatching", "Rereading", "All")
|
val defaultKeys = listOf(
|
||||||
|
"Reading",
|
||||||
|
"Watching",
|
||||||
|
"Completed",
|
||||||
|
"Paused",
|
||||||
|
"Dropped",
|
||||||
|
"Planning",
|
||||||
|
"Favourites",
|
||||||
|
"Rewatching",
|
||||||
|
"Rereading",
|
||||||
|
"All"
|
||||||
|
)
|
||||||
val userKeys: Array<String> = resources.getStringArray(R.array.keys)
|
val userKeys: Array<String> = resources.getStringArray(R.array.keys)
|
||||||
|
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
binding.listProgressBar.visibility = View.GONE
|
binding.listProgressBar.visibility = View.GONE
|
||||||
binding.listViewPager.adapter = ListViewPagerAdapter(it.size, false, this)
|
binding.listViewPager.adapter = ListViewPagerAdapter(it.size, false, this)
|
||||||
val keys = it.keys.toList().map { key -> userKeys.getOrNull(defaultKeys.indexOf(key))?: key }
|
val keys = it.keys.toList()
|
||||||
|
.map { key -> userKeys.getOrNull(defaultKeys.indexOf(key)) ?: key }
|
||||||
val values = it.values.toList()
|
val values = it.values.toList()
|
||||||
val savedTab = this.selectedTabIdx
|
val savedTab = this.selectedTabIdx
|
||||||
TabLayoutMediator(binding.listTabLayout, binding.listViewPager) { tab, position ->
|
TabLayoutMediator(binding.listTabLayout, binding.listViewPager) { tab, position ->
|
||||||
|
@ -103,7 +124,12 @@ ThemeManager(this).applyTheme()
|
||||||
live.observe(this) {
|
live.observe(this) {
|
||||||
if (it) {
|
if (it) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) { model.loadLists(anime, intent.getIntExtra("userId", 0)) }
|
withContext(Dispatchers.IO) {
|
||||||
|
model.loadLists(
|
||||||
|
anime,
|
||||||
|
intent.getIntExtra("userId", 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
live.postValue(false)
|
live.postValue(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +149,13 @@ ThemeManager(this).applyTheme()
|
||||||
binding.listProgressBar.visibility = View.VISIBLE
|
binding.listProgressBar.visibility = View.VISIBLE
|
||||||
binding.listViewPager.adapter = null
|
binding.listViewPager.adapter = null
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) { model.loadLists(anime, intent.getIntExtra("userId", 0), sort) }
|
withContext(Dispatchers.IO) {
|
||||||
|
model.loadLists(
|
||||||
|
anime,
|
||||||
|
intent.getIntExtra("userId", 0),
|
||||||
|
sort
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@ import ani.dantotsu.databinding.FragmentListBinding
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaAdaptor
|
import ani.dantotsu.media.MediaAdaptor
|
||||||
import ani.dantotsu.media.OtherDetailsViewModel
|
import ani.dantotsu.media.OtherDetailsViewModel
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import ani.dantotsu.others.LangSet
|
|
||||||
|
|
||||||
class ListFragment : Fragment() {
|
class ListFragment : Fragment() {
|
||||||
private var _binding: FragmentListBinding? = null
|
private var _binding: FragmentListBinding? = null
|
||||||
|
@ -29,7 +27,11 @@ class ListFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = FragmentListBinding.inflate(inflater, container, false)
|
_binding = FragmentListBinding.inflate(inflater, container, false)
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -42,7 +44,10 @@ class ListFragment : Fragment() {
|
||||||
if (grid != null && list != null) {
|
if (grid != null && list != null) {
|
||||||
val adapter = MediaAdaptor(if (grid!!) 0 else 1, list!!, requireActivity(), true)
|
val adapter = MediaAdaptor(if (grid!!) 0 else 1, list!!, requireActivity(), true)
|
||||||
binding.listRecyclerView.layoutManager =
|
binding.listRecyclerView.layoutManager =
|
||||||
GridLayoutManager(requireContext(), if (grid!!) (screenWidth / 124f).toInt() else 1)
|
GridLayoutManager(
|
||||||
|
requireContext(),
|
||||||
|
if (grid!!) (screenWidth / 124f).toInt() else 1
|
||||||
|
)
|
||||||
binding.listRecyclerView.adapter = adapter
|
binding.listRecyclerView.adapter = adapter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,13 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
|
||||||
class ListViewPagerAdapter(private val size: Int, private val calendar: Boolean, fragment: FragmentActivity) :
|
class ListViewPagerAdapter(
|
||||||
|
private val size: Int,
|
||||||
|
private val calendar: Boolean,
|
||||||
|
fragment: FragmentActivity
|
||||||
|
) :
|
||||||
FragmentStateAdapter(fragment) {
|
FragmentStateAdapter(fragment) {
|
||||||
override fun getItemCount(): Int = size
|
override fun getItemCount(): Int = size
|
||||||
override fun createFragment(position: Int): Fragment = ListFragment.newInstance(position, calendar)
|
override fun createFragment(position: Int): Fragment =
|
||||||
|
ListFragment.newInstance(position, calendar)
|
||||||
}
|
}
|
|
@ -13,7 +13,11 @@ import ani.dantotsu.startMainActivity
|
||||||
import ani.dantotsu.statusBarHeight
|
import ani.dantotsu.statusBarHeight
|
||||||
|
|
||||||
class OfflineFragment : Fragment() {
|
class OfflineFragment : Fragment() {
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
val binding = FragmentOfflineBinding.inflate(inflater, container, false)
|
val binding = FragmentOfflineBinding.inflate(inflater, container, false)
|
||||||
binding.refreshContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
binding.refreshContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
topMargin = statusBarHeight
|
topMargin = statusBarHeight
|
||||||
|
|
|
@ -8,12 +8,21 @@ import java.net.URLEncoder
|
||||||
object AniSkip {
|
object AniSkip {
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
suspend fun getResult(malId: Int, episodeNumber: Int, episodeLength: Long, useProxyForTimeStamps: Boolean): List<Stamp>? {
|
suspend fun getResult(
|
||||||
|
malId: Int,
|
||||||
|
episodeNumber: Int,
|
||||||
|
episodeLength: Long,
|
||||||
|
useProxyForTimeStamps: Boolean
|
||||||
|
): List<Stamp>? {
|
||||||
val url =
|
val url =
|
||||||
"https://api.aniskip.com/v2/skip-times/$malId/$episodeNumber?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=$episodeLength"
|
"https://api.aniskip.com/v2/skip-times/$malId/$episodeNumber?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=$episodeLength"
|
||||||
return tryWithSuspend {
|
return tryWithSuspend {
|
||||||
val a = if (useProxyForTimeStamps)
|
val a = if (useProxyForTimeStamps)
|
||||||
client.get("https://corsproxy.io/?${URLEncoder.encode(url, "utf-8").replace("+", "%20")}")
|
client.get(
|
||||||
|
"https://corsproxy.io/?${
|
||||||
|
URLEncoder.encode(url, "utf-8").replace("+", "%20")
|
||||||
|
}"
|
||||||
|
)
|
||||||
else
|
else
|
||||||
client.get(url)
|
client.get(url)
|
||||||
val res = a.parsed<AniSkipResponse>()
|
val res = a.parsed<AniSkipResponse>()
|
||||||
|
|
|
@ -24,6 +24,7 @@ import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
import rx.android.BuildConfig
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -53,15 +54,23 @@ object AppUpdater {
|
||||||
val dontShow = loadData("dont_ask_for_update_$version") ?: false
|
val dontShow = loadData("dont_ask_for_update_$version") ?: false
|
||||||
if (compareVersion(version) && !dontShow && !activity.isDestroyed) activity.runOnUiThread {
|
if (compareVersion(version) && !dontShow && !activity.isDestroyed) activity.runOnUiThread {
|
||||||
CustomBottomDialog.newInstance().apply {
|
CustomBottomDialog.newInstance().apply {
|
||||||
setTitleText("${if (BuildConfig.DEBUG) "Beta " else ""}Update " + currContext()!!.getString(R.string.available))
|
setTitleText(
|
||||||
|
"${if (BuildConfig.DEBUG) "Beta " else ""}Update " + currContext()!!.getString(
|
||||||
|
R.string.available
|
||||||
|
)
|
||||||
|
)
|
||||||
addView(
|
addView(
|
||||||
TextView(activity).apply {
|
TextView(activity).apply {
|
||||||
val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create()).build()
|
val markWon = Markwon.builder(activity)
|
||||||
|
.usePlugin(SoftBreakAddsNewLinePlugin.create()).build()
|
||||||
markWon.setMarkdown(this, md)
|
markWon.setMarkdown(this, md)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
setCheck(currContext()!!.getString(R.string.dont_show_again, version), false) { isChecked ->
|
setCheck(
|
||||||
|
currContext()!!.getString(R.string.dont_show_again, version),
|
||||||
|
false
|
||||||
|
) { isChecked ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
saveData("dont_ask_for_update_$version", true)
|
saveData("dont_ask_for_update_$version", true)
|
||||||
}
|
}
|
||||||
|
@ -98,8 +107,7 @@ object AppUpdater {
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
return BuildConfig.VERSION_NAME != version
|
return BuildConfig.VERSION_NAME != version
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
fun toDouble(list: List<String>): Double {
|
fun toDouble(list: List<String>): Double {
|
||||||
return list.mapIndexed { i: Int, s: String ->
|
return list.mapIndexed { i: Int, s: String ->
|
||||||
when (i) {
|
when (i) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ open class CustomBottomDialog : BottomSheetDialogFragment() {
|
||||||
fun addView(view: View) {
|
fun addView(view: View) {
|
||||||
viewList.add(view)
|
viewList.add(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
var title: String? = null
|
var title: String? = null
|
||||||
fun setTitleText(string: String) {
|
fun setTitleText(string: String) {
|
||||||
title = string
|
title = string
|
||||||
|
@ -46,7 +47,11 @@ open class CustomBottomDialog : BottomSheetDialogFragment() {
|
||||||
positiveCallback = callback
|
positiveCallback = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
_binding = BottomSheetCustomBinding.inflate(inflater, container, false)
|
_binding = BottomSheetCustomBinding.inflate(inflater, container, false)
|
||||||
val window = dialog?.window
|
val window = dialog?.window
|
||||||
window?.statusBarColor = Color.TRANSPARENT
|
window?.statusBarColor = Color.TRANSPARENT
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue