commit
d43d643bbd
163 changed files with 5058 additions and 5910 deletions
|
@ -75,11 +75,11 @@ android {
|
|||
|
||||
dependencies {
|
||||
|
||||
// FireBase
|
||||
// FireBase
|
||||
googleImplementation platform('com.google.firebase:firebase-bom:32.7.4')
|
||||
googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.5.1'
|
||||
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.2'
|
||||
// Core
|
||||
// Core
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.browser:browser:1.8.0'
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
|
@ -96,7 +96,7 @@ dependencies {
|
|||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.webkit:webkit:1.10.0'
|
||||
|
||||
// Glide
|
||||
// Glide
|
||||
ext.glide_version = '4.16.0'
|
||||
api "com.github.bumptech.glide:glide:$glide_version"
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
|
@ -104,7 +104,7 @@ dependencies {
|
|||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||
|
||||
// Exoplayer
|
||||
// Exoplayer
|
||||
ext.exo_version = '1.3.0'
|
||||
implementation "androidx.media3:media3-exoplayer:$exo_version"
|
||||
implementation "androidx.media3:media3-ui:$exo_version"
|
||||
|
@ -112,14 +112,13 @@ dependencies {
|
|||
implementation "androidx.media3:media3-exoplayer-dash:$exo_version"
|
||||
implementation "androidx.media3:media3-datasource-okhttp:$exo_version"
|
||||
implementation "androidx.media3:media3-session:$exo_version"
|
||||
//media3 casting
|
||||
// Media3 Casting
|
||||
implementation "androidx.media3:media3-cast:$exo_version"
|
||||
implementation "androidx.mediarouter:mediarouter:1.6.0"
|
||||
implementation "androidx.mediarouter:mediarouter:1.7.0"
|
||||
|
||||
// UI
|
||||
// UI
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
//implementation 'nl.joery.animatedbottombar:library:1.1.0'
|
||||
implementation 'com.github.rebelonion:AnimatedBottomBar:v1.1.0'
|
||||
implementation 'com.github.RepoDevil:AnimatedBottomBar:7fcb9af'
|
||||
implementation 'com.flaviofaria:kenburnsview:1.0.7'
|
||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
||||
implementation 'com.alexvasilkov:gesture-views:2.8.3'
|
||||
|
@ -128,7 +127,7 @@ dependencies {
|
|||
implementation 'com.github.eltos:simpledialogfragments:v3.7'
|
||||
implementation 'com.github.AAChartModel:AAChartCore-Kotlin:93972bc'
|
||||
|
||||
// Markwon
|
||||
// Markwon
|
||||
ext.markwon_version = '4.6.2'
|
||||
implementation "io.noties.markwon:core:$markwon_version"
|
||||
implementation "io.noties.markwon:editor:$markwon_version"
|
||||
|
@ -138,15 +137,15 @@ dependencies {
|
|||
implementation "io.noties.markwon:html:$markwon_version"
|
||||
implementation "io.noties.markwon:image-glide:$markwon_version"
|
||||
|
||||
// Groupie
|
||||
// Groupie
|
||||
ext.groupie_version = '2.10.1'
|
||||
implementation "com.github.lisawray.groupie:groupie:$groupie_version"
|
||||
implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version"
|
||||
|
||||
// string matching
|
||||
// String Matching
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||
|
||||
// Aniyomi
|
||||
// Aniyomi
|
||||
implementation 'io.reactivex:rxjava:1.3.8'
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
implementation 'ru.beryukhov:flowreactivenetwork:1.0.4'
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" /> <!-- For background jobs -->
|
||||
|
@ -53,11 +53,13 @@
|
|||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:roundIcon="${icon_placeholder_round}"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Dantotsu"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="AllowBackup">
|
||||
tools:ignore="AllowBackup"
|
||||
tools:targetApi="tiramisu">
|
||||
<receiver
|
||||
android:name=".widgets.CurrentlyAiringWidget"
|
||||
android:exported="false">
|
||||
|
@ -117,6 +119,7 @@
|
|||
<activity
|
||||
android:name=".profile.activity.FeedActivity"
|
||||
android:label="Inbox Activity"
|
||||
android:configChanges="orientation|screenSize|screenLayout"
|
||||
android:parentActivityName=".MainActivity" >
|
||||
</activity>
|
||||
<activity
|
||||
|
@ -299,11 +302,7 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity"
|
||||
android:exported="false"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
<activity
|
||||
android:name="eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallActivity"
|
||||
android:name="eu.kanade.tachiyomi.extension.util.ExtensionInstallActivity"
|
||||
android:exported="false"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
|
@ -354,11 +353,7 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
<service
|
||||
android:name="eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallService"
|
||||
android:name="eu.kanade.tachiyomi.extension.util.ExtensionInstallService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
<service
|
||||
|
|
|
@ -86,7 +86,7 @@ class App : MultiDexApplication() {
|
|||
Thread.setDefaultUncaughtExceptionHandler(FinalExceptionHandler())
|
||||
Logger.log("App: Logging started")
|
||||
|
||||
initializeNetwork(baseContext)
|
||||
initializeNetwork()
|
||||
|
||||
setupNotificationChannels()
|
||||
if (!LogcatLogger.isInstalled) {
|
||||
|
@ -154,6 +154,9 @@ class App : MultiDexApplication() {
|
|||
|
||||
companion object {
|
||||
private var instance: App? = null
|
||||
/** Reference to the application context.
|
||||
*
|
||||
* USE WITH EXTREME CAUTION!**/
|
||||
var context: Context? = null
|
||||
fun currentContext(): Context? {
|
||||
return instance?.mFTActivityLifecycleCallbacks?.currentActivity ?: context
|
||||
|
|
|
@ -10,11 +10,13 @@ import android.app.PendingIntent
|
|||
import android.content.ActivityNotFoundException
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.content.res.Resources.getSystem
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
|
@ -182,6 +184,10 @@ fun currActivity(): Activity? {
|
|||
var loadMedia: Int? = null
|
||||
var loadIsMAL = false
|
||||
|
||||
val Int.toPx get() = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics
|
||||
).toInt()
|
||||
|
||||
fun initActivity(a: Activity) {
|
||||
val window = a.window
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
@ -201,6 +207,7 @@ fun initActivity(a: Activity) {
|
|||
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
|
||||
?.apply {
|
||||
navBarHeight = this.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) navBarHeight += 48.toPx
|
||||
}
|
||||
}
|
||||
WindowInsetsControllerCompat(
|
||||
|
@ -222,6 +229,7 @@ fun initActivity(a: Activity) {
|
|||
statusBarHeight = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top
|
||||
navBarHeight =
|
||||
windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) navBarHeight += 48.toPx
|
||||
}
|
||||
}
|
||||
if (a !is MainActivity) a.setNavigationTheme()
|
||||
|
@ -394,7 +402,6 @@ class InputFilterMinMax(
|
|||
return ""
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun isInRange(a: Double, b: Double, c: Double): Boolean {
|
||||
val statusStrings = currContext()!!.resources.getStringArray(R.array.status_manga)[2]
|
||||
|
||||
|
@ -712,6 +719,23 @@ fun openLinkInBrowser(link: String?) {
|
|||
}
|
||||
}
|
||||
|
||||
fun openLinkInYouTube(link: String?) {
|
||||
link?.let {
|
||||
try {
|
||||
val videoIntent = Intent(Intent.ACTION_VIEW).apply {
|
||||
addCategory(Intent.CATEGORY_BROWSABLE)
|
||||
data = Uri.parse(link)
|
||||
setPackage("com.google.android.youtube")
|
||||
}
|
||||
currContext()!!.startActivity(videoIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
openLinkInBrowser(link)
|
||||
} catch (e: Exception) {
|
||||
Logger.log(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Activity) {
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
|
@ -897,9 +921,9 @@ fun copyToClipboard(string: String, toast: Boolean = true) {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun countDown(media: Media, view: ViewGroup) {
|
||||
if (media.anime?.nextAiringEpisode != null && media.anime.nextAiringEpisodeTime != null && (media.anime.nextAiringEpisodeTime!! - System.currentTimeMillis() / 1000) <= 86400 * 28.toLong()) {
|
||||
if (media.anime?.nextAiringEpisode != null && media.anime.nextAiringEpisodeTime != null
|
||||
&& (media.anime.nextAiringEpisodeTime!! - System.currentTimeMillis() / 1000) <= 86400 * 28.toLong()) {
|
||||
val v = ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
|
||||
view.addView(v.root, 0)
|
||||
v.mediaCountdownText.text =
|
||||
|
@ -1000,6 +1024,10 @@ class EmptyAdapter(private val count: Int) : RecyclerView.Adapter<RecyclerView.V
|
|||
inner class EmptyViewHolder(view: View) : RecyclerView.ViewHolder(view)
|
||||
}
|
||||
|
||||
fun getAppString(res: Int): String {
|
||||
return currContext()!!.getString(res) ?: ""
|
||||
}
|
||||
|
||||
fun toast(string: String?) {
|
||||
if (string != null) {
|
||||
Logger.log(string)
|
||||
|
@ -1010,6 +1038,10 @@ fun toast(string: String?) {
|
|||
}
|
||||
}
|
||||
|
||||
fun toast(res: Int) {
|
||||
toast(getAppString(res))
|
||||
}
|
||||
|
||||
fun snackString(s: String?, activity: Activity? = null, clipboard: String? = null): Snackbar? {
|
||||
try { //I have no idea why this sometimes crashes for some people...
|
||||
if (s != null) {
|
||||
|
@ -1050,6 +1082,10 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul
|
|||
return null
|
||||
}
|
||||
|
||||
fun snackString(r: Int, activity: Activity? = null, clipboard: String? = null): Snackbar? {
|
||||
return snackString(getAppString(r), activity, clipboard)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.annotation.SuppressLint
|
|||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.net.Uri
|
||||
|
@ -14,7 +13,6 @@ import android.os.Bundle
|
|||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -161,16 +159,16 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
val _bottomBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
||||
val bottomNavBar = findViewById<AnimatedBottomBar>(R.id.navbar)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
|
||||
val backgroundDrawable = _bottomBar.background as GradientDrawable
|
||||
val backgroundDrawable = bottomNavBar.background as GradientDrawable
|
||||
val currentColor = backgroundDrawable.color?.defaultColor ?: 0
|
||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xF9000000.toInt()
|
||||
backgroundDrawable.setColor(semiTransparentColor)
|
||||
_bottomBar.background = backgroundDrawable
|
||||
bottomNavBar.background = backgroundDrawable
|
||||
}
|
||||
_bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
|
||||
bottomNavBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray)
|
||||
|
||||
val offset = try {
|
||||
val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
|
@ -339,7 +337,7 @@ class MainActivity : AppCompatActivity() {
|
|||
startActivity(Intent(this, NoInternet::class.java))
|
||||
} else {
|
||||
val model: AnilistHomeViewModel by viewModels()
|
||||
model.genres.observe(this) { it ->
|
||||
model.genres.observe(this) {
|
||||
if (it != null) {
|
||||
if (it) {
|
||||
val navbar = binding.includedNavbar.navbar
|
||||
|
@ -364,7 +362,7 @@ class MainActivity : AppCompatActivity() {
|
|||
mainViewPager.setCurrentItem(newIndex, false)
|
||||
}
|
||||
})
|
||||
if (mainViewPager.getCurrentItem() != selectedOption) {
|
||||
if (mainViewPager.currentItem != selectedOption) {
|
||||
navbar.selectTabAt(selectedOption)
|
||||
mainViewPager.post {
|
||||
mainViewPager.setCurrentItem(
|
||||
|
@ -467,18 +465,12 @@ class MainActivity : AppCompatActivity() {
|
|||
window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent)
|
||||
}
|
||||
|
||||
private val Int.toPx get() = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics
|
||||
).toInt()
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
val margin = if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 8 else 32
|
||||
val params : ViewGroup.MarginLayoutParams =
|
||||
binding.includedNavbar.navbar.layoutParams as ViewGroup.MarginLayoutParams
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
params.updateMargins(bottom = 8.toPx)
|
||||
else
|
||||
params.updateMargins(bottom = 32.toPx)
|
||||
params.updateMargins(bottom = margin.toPx)
|
||||
}
|
||||
|
||||
private fun passwordAlertDialog(callback: (CharArray?) -> Unit) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package ani.dantotsu
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import ani.dantotsu.others.webview.CloudFlare
|
||||
|
@ -35,7 +34,7 @@ lateinit var defaultHeaders: Map<String, String>
|
|||
lateinit var okHttpClient: OkHttpClient
|
||||
lateinit var client: Requests
|
||||
|
||||
fun initializeNetwork(context: Context) {
|
||||
fun initializeNetwork() {
|
||||
|
||||
val networkHelper = Injekt.get<NetworkHelper>()
|
||||
|
||||
|
|
|
@ -387,6 +387,7 @@ class AnilistQueries {
|
|||
returnArray.addAll(map.values)
|
||||
return returnArray
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = PrefManager.getNullableCustomVal(
|
||||
"continueAnimeList",
|
||||
listOf<Int>(),
|
||||
|
@ -544,6 +545,7 @@ class AnilistQueries {
|
|||
returnMap["current$type"] = returnArray
|
||||
return
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = PrefManager.getNullableCustomVal(
|
||||
"continueAnimeList",
|
||||
listOf<Int>(),
|
||||
|
@ -573,6 +575,7 @@ class AnilistQueries {
|
|||
subMap[m.id] = m
|
||||
}
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = PrefManager.getNullableCustomVal(
|
||||
"continueAnimeList",
|
||||
listOf<Int>(),
|
||||
|
@ -734,7 +737,7 @@ class AnilistQueries {
|
|||
}
|
||||
|
||||
sorted["All"] = all
|
||||
val listSort: String = if (anime) PrefManager.getVal(PrefName.AnimeListSortOrder)
|
||||
val listSort: String? = if (anime) PrefManager.getVal(PrefName.AnimeListSortOrder)
|
||||
else PrefManager.getVal(PrefName.MangaListSortOrder)
|
||||
val sort = listSort ?: sortOrder ?: options?.rowOrder
|
||||
for (i in sorted.keys) {
|
||||
|
|
|
@ -112,8 +112,8 @@ class AnilistHomeViewModel : ViewModel() {
|
|||
|
||||
suspend fun loadMain(context: FragmentActivity) {
|
||||
Anilist.getSavedToken()
|
||||
MAL.getSavedToken(context)
|
||||
Discord.getSavedToken(context)
|
||||
MAL.getSavedToken()
|
||||
Discord.getSavedToken()
|
||||
if (!BuildConfig.FLAVOR.contains("fdroid")) {
|
||||
if (PrefManager.getVal(PrefName.CheckUpdate)) AppUpdater.check(context)
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ class AnilistAnimeViewModel : ViewModel() {
|
|||
fun getPopular(): LiveData<SearchResults?> = animePopular
|
||||
suspend fun loadPopular(
|
||||
type: String,
|
||||
search_val: String? = null,
|
||||
searchVal: String? = null,
|
||||
genres: ArrayList<String>? = null,
|
||||
sort: String = Anilist.sortBy[1],
|
||||
onList: Boolean = true,
|
||||
|
@ -167,7 +167,7 @@ class AnilistAnimeViewModel : ViewModel() {
|
|||
animePopular.postValue(
|
||||
Anilist.query.search(
|
||||
type,
|
||||
search = search_val,
|
||||
search = searchVal,
|
||||
onList = if (onList) null else false,
|
||||
sort = sort,
|
||||
genres = genres
|
||||
|
@ -231,7 +231,7 @@ class AnilistMangaViewModel : ViewModel() {
|
|||
fun getPopular(): LiveData<SearchResults?> = mangaPopular
|
||||
suspend fun loadPopular(
|
||||
type: String,
|
||||
search_val: String? = null,
|
||||
searchVal: String? = null,
|
||||
genres: ArrayList<String>? = null,
|
||||
sort: String = Anilist.sortBy[1],
|
||||
onList: Boolean = true,
|
||||
|
@ -239,7 +239,7 @@ class AnilistMangaViewModel : ViewModel() {
|
|||
mangaPopular.postValue(
|
||||
Anilist.query.search(
|
||||
type,
|
||||
search = search_val,
|
||||
search = searchVal,
|
||||
onList = if (onList) null else false,
|
||||
sort = sort,
|
||||
genres = genres
|
||||
|
|
|
@ -20,14 +20,14 @@ object Discord {
|
|||
var avatar: String? = null
|
||||
|
||||
|
||||
fun getSavedToken(context: Context): Boolean {
|
||||
fun getSavedToken(): Boolean {
|
||||
token = PrefManager.getVal(
|
||||
PrefName.DiscordToken, null as String?
|
||||
)
|
||||
return token != null
|
||||
}
|
||||
|
||||
fun saveToken(context: Context, token: String) {
|
||||
fun saveToken(token: String) {
|
||||
PrefManager.setVal(PrefName.DiscordToken, token)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,12 @@ 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 androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
@ -37,7 +33,6 @@ 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
|
||||
|
@ -162,8 +157,8 @@ class DiscordService : Service() {
|
|||
|
||||
inner class DiscordWebSocketListener : WebSocketListener() {
|
||||
|
||||
var retryAttempts = 0
|
||||
val maxRetryAttempts = 10
|
||||
private var retryAttempts = 0
|
||||
private val maxRetryAttempts = 10
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
super.onOpen(webSocket, response)
|
||||
this@DiscordService.webSocket = webSocket
|
||||
|
@ -232,7 +227,7 @@ class DiscordService : Service() {
|
|||
resume()
|
||||
resume = false
|
||||
} else {
|
||||
identify(webSocket, baseContext)
|
||||
identify(webSocket)
|
||||
log("WebSocket: Identified")
|
||||
}
|
||||
}
|
||||
|
@ -245,13 +240,13 @@ class DiscordService : Service() {
|
|||
}
|
||||
}
|
||||
|
||||
fun identify(webSocket: WebSocket, context: Context) {
|
||||
private fun identify(webSocket: WebSocket) {
|
||||
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("token", getToken())
|
||||
d.addProperty("intents", 0)
|
||||
d.add("properties", properties)
|
||||
val payload = JsonObject()
|
||||
|
@ -311,7 +306,7 @@ class DiscordService : Service() {
|
|||
}
|
||||
}
|
||||
|
||||
fun getToken(context: Context): String {
|
||||
fun getToken(): String {
|
||||
val token = PrefManager.getVal(PrefName.DiscordToken, null as String?)
|
||||
return if (token == null) {
|
||||
log("WebSocket: Token not found")
|
||||
|
@ -375,10 +370,10 @@ class DiscordService : Service() {
|
|||
log("WebSocket: Simple Test Presence Saved")
|
||||
}
|
||||
|
||||
fun setPresence(String: String) {
|
||||
fun setPresence(string: String) {
|
||||
log("WebSocket: Sending Presence payload")
|
||||
log(String)
|
||||
webSocket.send(String)
|
||||
log(string)
|
||||
webSocket.send(string)
|
||||
}
|
||||
|
||||
fun log(string: String) {
|
||||
|
@ -388,7 +383,7 @@ class DiscordService : Service() {
|
|||
fun resume() {
|
||||
log("Sending Resume payload")
|
||||
val d = JsonObject()
|
||||
d.addProperty("token", getToken(baseContext))
|
||||
d.addProperty("token", getToken())
|
||||
d.addProperty("session_id", sessionId)
|
||||
d.addProperty("seq", sequence)
|
||||
val json = JsonObject()
|
||||
|
@ -404,8 +399,7 @@ class DiscordService : Service() {
|
|||
Thread.sleep(heartbeat.toLong())
|
||||
heartbeatSend(webSocket, sequence)
|
||||
log("WebSocket: Heartbeat Sent")
|
||||
} catch (e: InterruptedException) {
|
||||
}
|
||||
} catch (ignored: InterruptedException) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ class Login : AppCompatActivity() {
|
|||
}
|
||||
Toast.makeText(this, "Logged in successfully", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
saveToken(this, token)
|
||||
saveToken(token)
|
||||
startMainActivity(this@Login)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
|||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.client
|
||||
import ani.dantotsu.currContext
|
||||
|
@ -64,7 +63,7 @@ object MAL {
|
|||
}
|
||||
|
||||
|
||||
suspend fun getSavedToken(context: FragmentActivity): Boolean {
|
||||
suspend fun getSavedToken(): Boolean {
|
||||
return tryWithSuspend(false) {
|
||||
var res: ResponseToken =
|
||||
PrefManager.getNullableVal<ResponseToken>(PrefName.MALToken, null)
|
||||
|
@ -77,7 +76,7 @@ object MAL {
|
|||
} ?: false
|
||||
}
|
||||
|
||||
fun removeSavedToken(context: Context) {
|
||||
fun removeSavedToken() {
|
||||
token = null
|
||||
username = null
|
||||
userid = null
|
||||
|
|
|
@ -3,6 +3,7 @@ package ani.dantotsu.download
|
|||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import com.google.gson.Gson
|
||||
|
@ -15,11 +16,11 @@ class DownloadsManager(private val context: Context) {
|
|||
private val downloadsList = loadDownloads().toMutableList()
|
||||
|
||||
val mangaDownloadedTypes: List<DownloadedType>
|
||||
get() = downloadsList.filter { it.type == DownloadedType.Type.MANGA }
|
||||
get() = downloadsList.filter { it.type == MediaType.MANGA }
|
||||
val animeDownloadedTypes: List<DownloadedType>
|
||||
get() = downloadsList.filter { it.type == DownloadedType.Type.ANIME }
|
||||
get() = downloadsList.filter { it.type == MediaType.ANIME }
|
||||
val novelDownloadedTypes: List<DownloadedType>
|
||||
get() = downloadsList.filter { it.type == DownloadedType.Type.NOVEL }
|
||||
get() = downloadsList.filter { it.type == MediaType.NOVEL }
|
||||
|
||||
private fun saveDownloads() {
|
||||
val jsonString = gson.toJson(downloadsList)
|
||||
|
@ -47,14 +48,8 @@ class DownloadsManager(private val context: Context) {
|
|||
saveDownloads()
|
||||
}
|
||||
|
||||
fun removeMedia(title: String, type: DownloadedType.Type) {
|
||||
val subDirectory = if (type == DownloadedType.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
}
|
||||
fun removeMedia(title: String, type: MediaType) {
|
||||
val subDirectory = type.asText()
|
||||
val directory = File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$subDirectory/$title"
|
||||
|
@ -71,53 +66,45 @@ class DownloadsManager(private val context: Context) {
|
|||
cleanDownloads()
|
||||
}
|
||||
when (type) {
|
||||
DownloadedType.Type.MANGA -> {
|
||||
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.MANGA }
|
||||
MediaType.MANGA -> {
|
||||
downloadsList.removeAll { it.title == title && it.type == MediaType.MANGA }
|
||||
}
|
||||
|
||||
DownloadedType.Type.ANIME -> {
|
||||
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.ANIME }
|
||||
MediaType.ANIME -> {
|
||||
downloadsList.removeAll { it.title == title && it.type == MediaType.ANIME }
|
||||
}
|
||||
|
||||
DownloadedType.Type.NOVEL -> {
|
||||
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.NOVEL }
|
||||
MediaType.NOVEL -> {
|
||||
downloadsList.removeAll { it.title == title && it.type == MediaType.NOVEL }
|
||||
}
|
||||
}
|
||||
saveDownloads()
|
||||
}
|
||||
|
||||
private fun cleanDownloads() {
|
||||
cleanDownload(DownloadedType.Type.MANGA)
|
||||
cleanDownload(DownloadedType.Type.ANIME)
|
||||
cleanDownload(DownloadedType.Type.NOVEL)
|
||||
cleanDownload(MediaType.MANGA)
|
||||
cleanDownload(MediaType.ANIME)
|
||||
cleanDownload(MediaType.NOVEL)
|
||||
}
|
||||
|
||||
private fun cleanDownload(type: DownloadedType.Type) {
|
||||
private fun cleanDownload(type: MediaType) {
|
||||
// remove all folders that are not in the downloads list
|
||||
val subDirectory = if (type == DownloadedType.Type.MANGA) {
|
||||
"Manga"
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
"Anime"
|
||||
} else {
|
||||
"Novel"
|
||||
}
|
||||
val subDirectory = type.asText()
|
||||
val directory = File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$subDirectory"
|
||||
)
|
||||
val downloadsSubLists = if (type == DownloadedType.Type.MANGA) {
|
||||
mangaDownloadedTypes
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
animeDownloadedTypes
|
||||
} else {
|
||||
novelDownloadedTypes
|
||||
val downloadsSubLists = when (type) {
|
||||
MediaType.MANGA -> mangaDownloadedTypes
|
||||
MediaType.ANIME -> animeDownloadedTypes
|
||||
else -> novelDownloadedTypes
|
||||
}
|
||||
if (directory.exists()) {
|
||||
val files = directory.listFiles()
|
||||
if (files != null) {
|
||||
for (file in files) {
|
||||
if (!downloadsSubLists.any { it.title == file.name }) {
|
||||
val deleted = file.deleteRecursively()
|
||||
file.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +140,7 @@ class DownloadsManager(private val context: Context) {
|
|||
return downloadsList.contains(downloadedType)
|
||||
}
|
||||
|
||||
fun queryDownload(title: String, chapter: String, type: DownloadedType.Type? = null): Boolean {
|
||||
fun queryDownload(title: String, chapter: String, type: MediaType? = null): Boolean {
|
||||
return if (type == null) {
|
||||
downloadsList.any { it.title == title && it.chapter == chapter }
|
||||
} else {
|
||||
|
@ -162,21 +149,25 @@ class DownloadsManager(private val context: Context) {
|
|||
}
|
||||
|
||||
private fun removeDirectory(downloadedType: DownloadedType) {
|
||||
val directory = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
val directory = when (downloadedType.type) {
|
||||
MediaType.MANGA -> {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
}
|
||||
MediaType.ANIME -> {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the directory exists and delete it recursively
|
||||
|
@ -193,21 +184,25 @@ class DownloadsManager(private val context: Context) {
|
|||
}
|
||||
|
||||
fun exportDownloads(downloadedType: DownloadedType) { //copies to the downloads folder available to the user
|
||||
val directory = if (downloadedType.type == DownloadedType.Type.MANGA) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
} else if (downloadedType.type == DownloadedType.Type.ANIME) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
val directory = when (downloadedType.type) {
|
||||
MediaType.MANGA -> {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
}
|
||||
MediaType.ANIME -> {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
|
||||
)
|
||||
}
|
||||
}
|
||||
val destination = File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
|
@ -225,13 +220,17 @@ class DownloadsManager(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun purgeDownloads(type: DownloadedType.Type) {
|
||||
val directory = if (type == DownloadedType.Type.MANGA) {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga")
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime")
|
||||
} else {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel")
|
||||
fun purgeDownloads(type: MediaType) {
|
||||
val directory = when (type) {
|
||||
MediaType.MANGA -> {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga")
|
||||
}
|
||||
MediaType.ANIME -> {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime")
|
||||
}
|
||||
else -> {
|
||||
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel")
|
||||
}
|
||||
}
|
||||
if (directory.exists()) {
|
||||
val deleted = directory.deleteRecursively()
|
||||
|
@ -255,56 +254,53 @@ class DownloadsManager(private val context: Context) {
|
|||
|
||||
fun getDirectory(
|
||||
context: Context,
|
||||
type: DownloadedType.Type,
|
||||
type: MediaType,
|
||||
title: String,
|
||||
chapter: String? = null
|
||||
): File {
|
||||
return if (type == DownloadedType.Type.MANGA) {
|
||||
if (chapter != null) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$mangaLocation/$title/$chapter"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$mangaLocation/$title"
|
||||
)
|
||||
return when (type) {
|
||||
MediaType.MANGA -> {
|
||||
if (chapter != null) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$mangaLocation/$title/$chapter"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$mangaLocation/$title"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (type == DownloadedType.Type.ANIME) {
|
||||
if (chapter != null) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$animeLocation/$title/$chapter"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$animeLocation/$title"
|
||||
)
|
||||
MediaType.ANIME -> {
|
||||
if (chapter != null) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$animeLocation/$title/$chapter"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$animeLocation/$title"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (chapter != null) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$novelLocation/$title/$chapter"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$novelLocation/$title"
|
||||
)
|
||||
else -> {
|
||||
if (chapter != null) {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$novelLocation/$title/$chapter"
|
||||
)
|
||||
} else {
|
||||
File(
|
||||
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"$novelLocation/$title"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class DownloadedType(val title: String, val chapter: String, val type: Type) : Serializable {
|
||||
enum class Type {
|
||||
MANGA,
|
||||
ANIME,
|
||||
NOVEL
|
||||
}
|
||||
}
|
||||
data class DownloadedType(val title: String, val chapter: String, val type: MediaType) : Serializable
|
||||
|
|
|
@ -27,14 +27,15 @@ import ani.dantotsu.download.DownloadedType
|
|||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.video.ExoplayerDownloadService
|
||||
import ani.dantotsu.download.video.Helper
|
||||
import ani.dantotsu.util.Logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.SubtitleDownloader
|
||||
import ani.dantotsu.media.anime.AnimeWatchFragment
|
||||
import ani.dantotsu.parsers.Subtitle
|
||||
import ani.dantotsu.parsers.Video
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.InstanceCreator
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
|
@ -242,7 +243,7 @@ class AnimeDownloaderService : Service() {
|
|||
DownloadedType(
|
||||
task.title,
|
||||
task.episode,
|
||||
DownloadedType.Type.ANIME,
|
||||
MediaType.ANIME,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -273,7 +274,7 @@ class AnimeDownloaderService : Service() {
|
|||
DownloadedType(
|
||||
task.title,
|
||||
task.episode,
|
||||
DownloadedType.Type.ANIME,
|
||||
MediaType.ANIME,
|
||||
)
|
||||
)
|
||||
Injekt.get<CrashlyticsInterface>().logException(
|
||||
|
@ -302,7 +303,7 @@ class AnimeDownloaderService : Service() {
|
|||
DownloadedType(
|
||||
task.title,
|
||||
task.episode,
|
||||
DownloadedType.Type.ANIME,
|
||||
MediaType.ANIME,
|
||||
)
|
||||
)
|
||||
currentTasks.removeAll { it.getTaskName() == task.getTaskName() }
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ani.dantotsu.download.anime
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -38,7 +37,6 @@ class OfflineAnimeAdapter(
|
|||
return position.toLong()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
|
||||
val view: View = convertView ?: when (style) {
|
||||
|
@ -61,14 +59,14 @@ class OfflineAnimeAdapter(
|
|||
if (style == 0) {
|
||||
val bannerView = view.findViewById<ImageView>(R.id.itemCompactBanner) // for large view
|
||||
val episodes = view.findViewById<TextView>(R.id.itemTotal)
|
||||
episodes.text = " Episodes"
|
||||
episodes.text = context.getString(R.string.episodes)
|
||||
bannerView.setImageURI(item.banner ?: item.image)
|
||||
totalepisodes.text = item.totalEpisodeList
|
||||
} else if (style == 1) {
|
||||
val watchedEpisodes =
|
||||
view.findViewById<TextView>(R.id.itemCompactUserProgress) // for compact view
|
||||
watchedEpisodes.text = item.watchedEpisode
|
||||
totalepisodes.text = " | " + item.totalEpisode
|
||||
totalepisodes.text = context.getString(R.string.total_divider, item.totalEpisode)
|
||||
}
|
||||
|
||||
// Bind item data to the views
|
||||
|
|
|
@ -22,6 +22,7 @@ import androidx.annotation.OptIn
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.marginBottom
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
|
@ -33,15 +34,16 @@ import ani.dantotsu.currContext
|
|||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.initActivity
|
||||
import ani.dantotsu.util.Logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.setSafeOnClickListener
|
||||
import ani.dantotsu.settings.SettingsDialogFragment
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
@ -187,8 +189,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||
gridView.setOnItemLongClickListener { _, _, position, _ ->
|
||||
// Get the OfflineAnimeModel that was clicked
|
||||
val item = adapter.getItem(position) as OfflineAnimeModel
|
||||
val type: DownloadedType.Type =
|
||||
DownloadedType.Type.ANIME
|
||||
val type: MediaType = MediaType.ANIME
|
||||
|
||||
// Alert dialog to confirm deletion
|
||||
val builder =
|
||||
|
@ -250,7 +251,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||
val visibility = first != null && first.top < 0
|
||||
scrollTop.translationY =
|
||||
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
||||
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
|
||||
scrollTop.isVisible = visibility
|
||||
}
|
||||
})
|
||||
initActivity(requireActivity())
|
||||
|
@ -292,11 +293,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||
}
|
||||
|
||||
private fun getMedia(downloadedType: DownloadedType): Media? {
|
||||
val type = when (downloadedType.type) {
|
||||
DownloadedType.Type.MANGA -> "Manga"
|
||||
DownloadedType.Type.ANIME -> "Anime"
|
||||
else -> "Novel"
|
||||
}
|
||||
val type = downloadedType.type.asText()
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$type/${downloadedType.title}"
|
||||
|
@ -326,11 +323,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
|
|||
}
|
||||
|
||||
private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
|
||||
val type = when (downloadedType.type) {
|
||||
DownloadedType.Type.MANGA -> "Manga"
|
||||
DownloadedType.Type.ANIME -> "Anime"
|
||||
else -> "Novel"
|
||||
}
|
||||
val type = downloadedType.type.asText()
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$type/${downloadedType.title}"
|
||||
|
|
|
@ -21,8 +21,8 @@ import ani.dantotsu.R
|
|||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.util.Logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.manga.ImageData
|
||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED
|
||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FINISHED
|
||||
|
@ -30,6 +30,7 @@ import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROG
|
|||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STARTED
|
||||
import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.InstanceCreator
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS
|
||||
|
@ -211,8 +212,7 @@ class MangaDownloaderService : Service() {
|
|||
while (bitmap == null && retryCount < task.retries) {
|
||||
bitmap = image.fetchAndProcessImage(
|
||||
image.page,
|
||||
image.source,
|
||||
this@MangaDownloaderService
|
||||
image.source
|
||||
)
|
||||
retryCount++
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ class MangaDownloaderService : Service() {
|
|||
DownloadedType(
|
||||
task.title,
|
||||
task.chapter,
|
||||
DownloadedType.Type.MANGA
|
||||
MediaType.MANGA
|
||||
)
|
||||
)
|
||||
broadcastDownloadFinished(task.chapter)
|
||||
|
|
|
@ -37,7 +37,6 @@ class OfflineMangaAdapter(
|
|||
return position.toLong()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
|
||||
val view: View = convertView ?: when (style) {
|
||||
|
@ -60,14 +59,14 @@ class OfflineMangaAdapter(
|
|||
if (style == 0) {
|
||||
val bannerView = view.findViewById<ImageView>(R.id.itemCompactBanner) // for large view
|
||||
val chapters = view.findViewById<TextView>(R.id.itemTotal)
|
||||
chapters.text = " Chapters"
|
||||
chapters.text = context.getString(R.string.chapters)
|
||||
bannerView.setImageURI(item.banner ?: item.image)
|
||||
totalChapter.text = item.totalChapter
|
||||
} else if (style == 1) {
|
||||
val readChapter =
|
||||
view.findViewById<TextView>(R.id.itemCompactUserProgress) // for compact view
|
||||
readChapter.text = item.readChapter
|
||||
totalChapter.text = " | " + item.totalChapter
|
||||
totalChapter.text = context.getString(R.string.total_divider, item.totalChapter)
|
||||
}
|
||||
|
||||
// Bind item data to the views
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.widget.TextView
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.marginBottom
|
||||
import androidx.fragment.app.Fragment
|
||||
import ani.dantotsu.R
|
||||
|
@ -30,15 +31,16 @@ import ani.dantotsu.currContext
|
|||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.initActivity
|
||||
import ani.dantotsu.util.Logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.setSafeOnClickListener
|
||||
import ani.dantotsu.settings.SettingsDialogFragment
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
@ -178,11 +180,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
gridView.setOnItemLongClickListener { _, _, position, _ ->
|
||||
// Get the OfflineMangaModel that was clicked
|
||||
val item = adapter.getItem(position) as OfflineMangaModel
|
||||
val type: DownloadedType.Type =
|
||||
val type: MediaType =
|
||||
if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) {
|
||||
DownloadedType.Type.MANGA
|
||||
MediaType.MANGA
|
||||
} else {
|
||||
DownloadedType.Type.NOVEL
|
||||
MediaType.NOVEL
|
||||
}
|
||||
// Alert dialog to confirm deletion
|
||||
val builder =
|
||||
|
@ -234,7 +236,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
) {
|
||||
val first = view.getChildAt(0)
|
||||
val visibility = first != null && first.top < 0
|
||||
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE
|
||||
scrollTop.isVisible = visibility
|
||||
scrollTop.translationY =
|
||||
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
|
||||
}
|
||||
|
@ -288,11 +290,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
}
|
||||
|
||||
private fun getMedia(downloadedType: DownloadedType): Media? {
|
||||
val type = when (downloadedType.type) {
|
||||
DownloadedType.Type.MANGA -> "Manga"
|
||||
DownloadedType.Type.ANIME -> "Anime"
|
||||
else -> "Novel"
|
||||
}
|
||||
val type = downloadedType.type.asText()
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$type/${downloadedType.title}"
|
||||
|
@ -316,11 +314,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
|
|||
}
|
||||
|
||||
private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
|
||||
val type = when (downloadedType.type) {
|
||||
DownloadedType.Type.MANGA -> "Manga"
|
||||
DownloadedType.Type.ANIME -> "Anime"
|
||||
else -> "Novel"
|
||||
}
|
||||
val type = downloadedType.type.asText()
|
||||
val directory = File(
|
||||
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
|
||||
"Dantotsu/$type/${downloadedType.title}"
|
||||
|
|
|
@ -20,10 +20,11 @@ import ani.dantotsu.R
|
|||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.util.Logger
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.novel.NovelReadFragment
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.InstanceCreator
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
|
@ -335,7 +336,7 @@ class NovelDownloaderService : Service() {
|
|||
DownloadedType(
|
||||
task.title,
|
||||
task.chapter,
|
||||
DownloadedType.Type.NOVEL
|
||||
MediaType.NOVEL
|
||||
)
|
||||
)
|
||||
broadcastDownloadFinished(task.originalLink)
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.content.Intent
|
|||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -37,6 +36,7 @@ import ani.dantotsu.download.anime.AnimeDownloaderService
|
|||
import ani.dantotsu.download.anime.AnimeServiceDataSingleton
|
||||
import ani.dantotsu.logError
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.okHttpClient
|
||||
import ani.dantotsu.parsers.Subtitle
|
||||
import ani.dantotsu.parsers.SubtitleType
|
||||
|
@ -49,13 +49,14 @@ import uy.kohesive.injekt.Injekt
|
|||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
object Helper {
|
||||
|
||||
|
||||
private var simpleCache: SimpleCache? = null
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) {
|
||||
val dataSourceFactory = DataSource.Factory {
|
||||
val dataSource: HttpDataSource =
|
||||
|
@ -157,16 +158,14 @@ object Helper {
|
|||
download: Download,
|
||||
finalException: Exception?
|
||||
) {
|
||||
if (download.state == Download.STATE_COMPLETED) {
|
||||
Logger.log("Download Completed")
|
||||
} else if (download.state == Download.STATE_FAILED) {
|
||||
Logger.log("Download Failed")
|
||||
} else if (download.state == Download.STATE_STOPPED) {
|
||||
Logger.log("Download Stopped")
|
||||
} else if (download.state == Download.STATE_QUEUED) {
|
||||
Logger.log("Download Queued")
|
||||
} else if (download.state == Download.STATE_DOWNLOADING) {
|
||||
Logger.log("Download Downloading")
|
||||
when (download.state) {
|
||||
Download.STATE_COMPLETED -> Logger.log("Download Completed")
|
||||
Download.STATE_FAILED -> Logger.log("Download Failed")
|
||||
Download.STATE_STOPPED -> Logger.log("Download Stopped")
|
||||
Download.STATE_QUEUED -> Logger.log("Download Queued")
|
||||
Download.STATE_DOWNLOADING -> Logger.log("Download Downloading")
|
||||
Download.STATE_REMOVING -> Logger.log("Download Removing")
|
||||
Download.STATE_RESTARTING -> Logger.log("Download Restarting")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +219,7 @@ object Helper {
|
|||
|
||||
val downloadsManger = Injekt.get<DownloadsManager>()
|
||||
val downloadCheck = downloadsManger
|
||||
.queryDownload(title, episode, DownloadedType.Type.ANIME)
|
||||
.queryDownload(title, episode, MediaType.ANIME)
|
||||
|
||||
if (downloadCheck) {
|
||||
AlertDialog.Builder(context, R.style.MyPopup)
|
||||
|
@ -243,7 +242,7 @@ object Helper {
|
|||
DownloadedType(
|
||||
title,
|
||||
episode,
|
||||
DownloadedType.Type.ANIME
|
||||
MediaType.ANIME
|
||||
)
|
||||
)
|
||||
AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask)
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.view.ViewGroup
|
|||
import android.view.animation.LayoutAnimationController
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
@ -21,6 +22,7 @@ import ani.dantotsu.R
|
|||
import ani.dantotsu.connections.anilist.Anilist
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.databinding.ItemAnimePageBinding
|
||||
import ani.dantotsu.databinding.LayoutTrendingBinding
|
||||
import ani.dantotsu.loadImage
|
||||
import ani.dantotsu.media.CalendarActivity
|
||||
import ani.dantotsu.media.GenreActivity
|
||||
|
@ -41,6 +43,7 @@ import com.google.android.material.textfield.TextInputLayout
|
|||
class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHolder>() {
|
||||
val ready = MutableLiveData(false)
|
||||
lateinit var binding: ItemAnimePageBinding
|
||||
private lateinit var trendingBinding: LayoutTrendingBinding
|
||||
private var trendHandler: Handler? = null
|
||||
private lateinit var trendRun: Runnable
|
||||
var trendingViewPager: ViewPager2? = null
|
||||
|
@ -53,14 +56,15 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||
|
||||
override fun onBindViewHolder(holder: AnimePageViewHolder, position: Int) {
|
||||
binding = holder.binding
|
||||
trendingViewPager = binding.animeTrendingViewPager
|
||||
trendingBinding = LayoutTrendingBinding.bind(binding.root)
|
||||
trendingViewPager = trendingBinding.trendingViewPager
|
||||
|
||||
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.animeSearchBar)
|
||||
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.searchBar)
|
||||
val currentColor = textInputLayout.boxBackgroundColor
|
||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
||||
textInputLayout.boxBackgroundColor = semiTransparentColor
|
||||
val materialCardView =
|
||||
holder.itemView.findViewById<MaterialCardView>(R.id.animeUserAvatarContainer)
|
||||
holder.itemView.findViewById<MaterialCardView>(R.id.userAvatarContainer)
|
||||
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
||||
val typedValue = TypedValue()
|
||||
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||
|
@ -69,33 +73,29 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
|
||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000)
|
||||
|
||||
binding.animeTitleContainer.updatePadding(top = statusBarHeight)
|
||||
trendingBinding.titleContainer.updatePadding(top = statusBarHeight)
|
||||
|
||||
if (PrefManager.getVal(PrefName.SmallView)) binding.animeTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
if (PrefManager.getVal(PrefName.SmallView)) trendingBinding.trendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = (-108f).px
|
||||
}
|
||||
|
||||
updateAvatar()
|
||||
|
||||
binding.animeSearchBar.hint = "ANIME"
|
||||
binding.animeSearchBarText.setOnClickListener {
|
||||
trendingBinding.searchBar.hint = "ANIME"
|
||||
trendingBinding.searchBarText.setOnClickListener {
|
||||
ContextCompat.startActivity(
|
||||
it.context,
|
||||
Intent(it.context, SearchActivity::class.java).putExtra("type", "ANIME"),
|
||||
Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
binding.animeSearchBar.setEndIconOnClickListener {
|
||||
binding.animeSearchBarText.performClick()
|
||||
}
|
||||
|
||||
binding.animeUserAvatar.setSafeOnClickListener {
|
||||
trendingBinding.userAvatar.setSafeOnClickListener {
|
||||
val dialogFragment =
|
||||
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.ANIME)
|
||||
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||
}
|
||||
binding.animeUserAvatar.setOnLongClickListener { view ->
|
||||
trendingBinding.userAvatar.setOnLongClickListener { view ->
|
||||
ContextCompat.startActivity(
|
||||
view.context,
|
||||
Intent(view.context, ProfileActivity::class.java)
|
||||
|
@ -104,8 +104,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||
false
|
||||
}
|
||||
|
||||
binding.animeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||
binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
trendingBinding.searchBar.setEndIconOnClickListener {
|
||||
trendingBinding.searchBar.performClick()
|
||||
}
|
||||
|
||||
trendingBinding.notificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
|
||||
listOf(
|
||||
binding.animePreviousSeason,
|
||||
|
@ -134,8 +138,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||
)
|
||||
}
|
||||
|
||||
binding.animeIncludeList.visibility =
|
||||
if (Anilist.userid != null) View.VISIBLE else View.GONE
|
||||
binding.animeIncludeList.isVisible = Anilist.userid != null
|
||||
|
||||
binding.animeIncludeList.isChecked = PrefManager.getVal(PrefName.PopularAnimeList)
|
||||
|
||||
|
@ -159,18 +162,17 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||
}
|
||||
|
||||
fun updateTrending(adaptor: MediaAdaptor) {
|
||||
binding.animeTrendingProgressBar.visibility = View.GONE
|
||||
binding.animeTrendingViewPager.adapter = adaptor
|
||||
binding.animeTrendingViewPager.offscreenPageLimit = 3
|
||||
binding.animeTrendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
|
||||
binding.animeTrendingViewPager.setPageTransformer(MediaPageTransformer())
|
||||
trendingBinding.trendingProgressBar.visibility = View.GONE
|
||||
trendingBinding.trendingViewPager.adapter = adaptor
|
||||
trendingBinding.trendingViewPager.offscreenPageLimit = 3
|
||||
trendingBinding.trendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
|
||||
trendingBinding.trendingViewPager.setPageTransformer(MediaPageTransformer())
|
||||
|
||||
trendHandler = Handler(Looper.getMainLooper())
|
||||
trendRun = Runnable {
|
||||
binding.animeTrendingViewPager.currentItem =
|
||||
binding.animeTrendingViewPager.currentItem + 1
|
||||
trendingBinding.trendingViewPager.currentItem += 1
|
||||
}
|
||||
binding.animeTrendingViewPager.registerOnPageChangeCallback(
|
||||
trendingBinding.trendingViewPager.registerOnPageChangeCallback(
|
||||
object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
|
@ -180,9 +182,9 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||
}
|
||||
)
|
||||
|
||||
binding.animeTrendingViewPager.layoutAnimation =
|
||||
trendingBinding.trendingViewPager.layoutAnimation =
|
||||
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||
binding.animeTitleContainer.startAnimation(setSlideUp())
|
||||
trendingBinding.titleContainer.startAnimation(setSlideUp())
|
||||
binding.animeListContainer.layoutAnimation =
|
||||
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||
binding.animeSeasonsCont.layoutAnimation =
|
||||
|
@ -210,16 +212,16 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
|
|||
|
||||
fun updateAvatar() {
|
||||
if (Anilist.avatar != null && ready.value == true) {
|
||||
binding.animeUserAvatar.loadImage(Anilist.avatar)
|
||||
binding.animeUserAvatar.imageTintList = null
|
||||
trendingBinding.userAvatar.loadImage(Anilist.avatar)
|
||||
trendingBinding.userAvatar.imageTintList = null
|
||||
}
|
||||
}
|
||||
|
||||
fun updateNotificationCount() {
|
||||
if (this::binding.isInitialized) {
|
||||
binding.animeNotificationCount.visibility =
|
||||
trendingBinding.notificationCount.visibility =
|
||||
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||
binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.animation.LayoutAnimationController
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
|
@ -81,7 +82,7 @@ class HomeFragment : Fragment() {
|
|||
if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.homeUserBg.pause()
|
||||
blurImage(binding.homeUserBg, Anilist.bg)
|
||||
binding.homeUserDataProgressBar.visibility = View.GONE
|
||||
binding.homeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||
binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
|
||||
binding.homeAnimeList.setOnClickListener {
|
||||
|
@ -375,7 +376,7 @@ class HomeFragment : Fragment() {
|
|||
override fun onResume() {
|
||||
if (!model.loaded) Refresh.activity[1]!!.postValue(true)
|
||||
if (_binding != null) {
|
||||
binding.homeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||
binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||
binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
}
|
||||
super.onResume()
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.view.ViewGroup
|
|||
import android.view.animation.LayoutAnimationController
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
@ -21,6 +22,7 @@ import ani.dantotsu.R
|
|||
import ani.dantotsu.connections.anilist.Anilist
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.databinding.ItemMangaPageBinding
|
||||
import ani.dantotsu.databinding.LayoutTrendingBinding
|
||||
import ani.dantotsu.loadImage
|
||||
import ani.dantotsu.media.GenreActivity
|
||||
import ani.dantotsu.media.MediaAdaptor
|
||||
|
@ -40,6 +42,7 @@ import com.google.android.material.textfield.TextInputLayout
|
|||
class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHolder>() {
|
||||
val ready = MutableLiveData(false)
|
||||
lateinit var binding: ItemMangaPageBinding
|
||||
private lateinit var trendingBinding: LayoutTrendingBinding
|
||||
private var trendHandler: Handler? = null
|
||||
private lateinit var trendRun: Runnable
|
||||
var trendingViewPager: ViewPager2? = null
|
||||
|
@ -52,33 +55,34 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||
|
||||
override fun onBindViewHolder(holder: MangaPageViewHolder, position: Int) {
|
||||
binding = holder.binding
|
||||
trendingViewPager = binding.mangaTrendingViewPager
|
||||
trendingBinding = LayoutTrendingBinding.bind(binding.root)
|
||||
trendingViewPager = trendingBinding.trendingViewPager
|
||||
|
||||
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.mangaSearchBar)
|
||||
val textInputLayout = holder.itemView.findViewById<TextInputLayout>(R.id.searchBar)
|
||||
val currentColor = textInputLayout.boxBackgroundColor
|
||||
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
|
||||
textInputLayout.boxBackgroundColor = semiTransparentColor
|
||||
val materialCardView =
|
||||
holder.itemView.findViewById<MaterialCardView>(R.id.mangaUserAvatarContainer)
|
||||
holder.itemView.findViewById<MaterialCardView>(R.id.userAvatarContainer)
|
||||
materialCardView.setCardBackgroundColor(semiTransparentColor)
|
||||
val typedValue = TypedValue()
|
||||
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
|
||||
val color = typedValue.data
|
||||
|
||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt()
|
||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt())
|
||||
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
|
||||
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000)
|
||||
|
||||
binding.mangaTitleContainer.updatePadding(top = statusBarHeight)
|
||||
trendingBinding.titleContainer.updatePadding(top = statusBarHeight)
|
||||
|
||||
if (PrefManager.getVal(PrefName.SmallView)) binding.mangaTrendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
if (PrefManager.getVal(PrefName.SmallView)) trendingBinding.trendingContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = (-108f).px
|
||||
}
|
||||
|
||||
updateAvatar()
|
||||
binding.mangaNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||
binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
binding.mangaSearchBar.hint = "MANGA"
|
||||
binding.mangaSearchBarText.setOnClickListener {
|
||||
trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
trendingBinding.searchBar.hint = "MANGA"
|
||||
trendingBinding.searchBarText.setOnClickListener {
|
||||
ContextCompat.startActivity(
|
||||
it.context,
|
||||
Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"),
|
||||
|
@ -86,12 +90,12 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||
)
|
||||
}
|
||||
|
||||
binding.mangaUserAvatar.setSafeOnClickListener {
|
||||
trendingBinding.userAvatar.setSafeOnClickListener {
|
||||
val dialogFragment =
|
||||
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.MANGA)
|
||||
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
|
||||
}
|
||||
binding.mangaUserAvatar.setOnLongClickListener { view ->
|
||||
trendingBinding.userAvatar.setOnLongClickListener { view ->
|
||||
ContextCompat.startActivity(
|
||||
view.context,
|
||||
Intent(view.context, ProfileActivity::class.java)
|
||||
|
@ -100,8 +104,8 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||
false
|
||||
}
|
||||
|
||||
binding.mangaSearchBar.setEndIconOnClickListener {
|
||||
binding.mangaSearchBarText.performClick()
|
||||
trendingBinding.searchBar.setEndIconOnClickListener {
|
||||
trendingBinding.searchBarText.performClick()
|
||||
}
|
||||
|
||||
binding.mangaGenreImage.loadImage("https://s4.anilist.co/file/anilistcdn/media/manga/banner/105778-wk5qQ7zAaTGl.jpg")
|
||||
|
@ -125,8 +129,7 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||
)
|
||||
}
|
||||
|
||||
binding.mangaIncludeList.visibility =
|
||||
if (Anilist.userid != null) View.VISIBLE else View.GONE
|
||||
binding.mangaIncludeList.isVisible = Anilist.userid != null
|
||||
|
||||
binding.mangaIncludeList.isChecked = PrefManager.getVal(PrefName.PopularMangaList)
|
||||
|
||||
|
@ -148,16 +151,16 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||
}
|
||||
|
||||
fun updateTrending(adaptor: MediaAdaptor) {
|
||||
binding.mangaTrendingProgressBar.visibility = View.GONE
|
||||
binding.mangaTrendingViewPager.adapter = adaptor
|
||||
binding.mangaTrendingViewPager.offscreenPageLimit = 3
|
||||
binding.mangaTrendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
|
||||
binding.mangaTrendingViewPager.setPageTransformer(MediaPageTransformer())
|
||||
trendingBinding.trendingProgressBar.visibility = View.GONE
|
||||
trendingBinding.trendingViewPager.adapter = adaptor
|
||||
trendingBinding.trendingViewPager.offscreenPageLimit = 3
|
||||
trendingBinding.trendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
|
||||
trendingBinding.trendingViewPager.setPageTransformer(MediaPageTransformer())
|
||||
trendHandler = Handler(Looper.getMainLooper())
|
||||
trendRun = Runnable {
|
||||
binding.mangaTrendingViewPager.currentItem += 1
|
||||
trendingBinding.trendingViewPager.currentItem += 1
|
||||
}
|
||||
binding.mangaTrendingViewPager.registerOnPageChangeCallback(
|
||||
trendingBinding.trendingViewPager.registerOnPageChangeCallback(
|
||||
object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
|
@ -167,9 +170,9 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||
}
|
||||
)
|
||||
|
||||
binding.mangaTrendingViewPager.layoutAnimation =
|
||||
trendingBinding.trendingViewPager.layoutAnimation =
|
||||
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||
binding.mangaTitleContainer.startAnimation(setSlideUp())
|
||||
trendingBinding.titleContainer.startAnimation(setSlideUp())
|
||||
binding.mangaListContainer.layoutAnimation =
|
||||
LayoutAnimationController(setSlideIn(), 0.25f)
|
||||
}
|
||||
|
@ -195,16 +198,16 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
|
|||
|
||||
fun updateAvatar() {
|
||||
if (Anilist.avatar != null && ready.value == true) {
|
||||
binding.mangaUserAvatar.loadImage(Anilist.avatar)
|
||||
binding.mangaUserAvatar.imageTintList = null
|
||||
trendingBinding.userAvatar.loadImage(Anilist.avatar)
|
||||
trendingBinding.userAvatar.imageTintList = null
|
||||
}
|
||||
}
|
||||
|
||||
fun updateNotificationCount() {
|
||||
if (this::binding.isInitialized) {
|
||||
binding.mangaNotificationCount.visibility =
|
||||
trendingBinding.notificationCount.visibility =
|
||||
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||
binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ class AuthorAdapter(
|
|||
return AuthorViewHolder(binding)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder:AuthorViewHolder, position: Int) {
|
||||
val binding = holder.binding
|
||||
setAnimation(binding.root.context, holder.binding.root)
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.util.TypedValue
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -16,8 +15,8 @@ import androidx.lifecycle.lifecycleScope
|
|||
import ani.dantotsu.R
|
||||
import ani.dantotsu.Refresh
|
||||
import ani.dantotsu.databinding.ActivityListBinding
|
||||
import ani.dantotsu.hideSystemBarsExtendView
|
||||
import ani.dantotsu.media.user.ListViewPagerAdapter
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.statusBarHeight
|
||||
|
@ -34,7 +33,6 @@ class CalendarActivity : AppCompatActivity() {
|
|||
private var selectedTabIdx = 1
|
||||
private val model: OtherDetailsViewModel by viewModels()
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -74,10 +72,7 @@ class CalendarActivity : AppCompatActivity() {
|
|||
} else {
|
||||
binding.root.fitsSystemWindows = false
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
)
|
||||
hideSystemBarsExtendView()
|
||||
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = statusBarHeight
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ class CharacterAdapter(
|
|||
return CharacterViewHolder(binding)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
|
||||
val binding = holder.binding
|
||||
setAnimation(binding.root.context, holder.binding.root)
|
||||
val character = characterList[position]
|
||||
binding.itemCompactRelation.text = character.role + " "
|
||||
val whitespace = "${character.role} "
|
||||
binding.itemCompactRelation.text = whitespace
|
||||
binding.itemCompactImage.loadImage(character.image)
|
||||
binding.itemCompactTitle.text = character.name
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.activity.viewModels
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.math.MathUtils.clamp
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
@ -152,7 +153,7 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
|
|||
}
|
||||
|
||||
override fun onResume() {
|
||||
binding.characterProgress.visibility = if (!loaded) View.VISIBLE else View.GONE
|
||||
binding.characterProgress.isGone = loaded
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
|
|
|
@ -20,15 +20,16 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
|
|||
return GenreViewHolder(binding)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: GenreViewHolder, position: Int) {
|
||||
val binding = holder.binding
|
||||
val desc =
|
||||
(if (character.age != "null") currActivity()!!.getString(R.string.age) + " " + character.age else "") +
|
||||
(if (character.dateOfBirth.toString() != "") currActivity()!!.getString(R.string.birthday) + " " + character.dateOfBirth.toString() else "") +
|
||||
(if (character.gender != "null") currActivity()!!.getString(R.string.gender) + " " + when (character.gender) {
|
||||
"Male" -> currActivity()!!.getString(R.string.male)
|
||||
"Female" -> currActivity()!!.getString(R.string.female)
|
||||
(if (character.age != "null") "${currActivity()!!.getString(R.string.age)} ${character.age}" else "") +
|
||||
(if (character.dateOfBirth.toString() != "")
|
||||
"${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") +
|
||||
(if (character.gender != "null")
|
||||
currActivity()!!.getString(R.string.gender) + " " + when (character.gender) {
|
||||
currActivity()!!.getString(R.string.male) -> currActivity()!!.getString(R.string.male)
|
||||
currActivity()!!.getString(R.string.female) -> currActivity()!!.getString(R.string.female)
|
||||
else -> character.gender
|
||||
} else "") + "\n" + character.description
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import androidx.core.app.ActivityOptionsCompat
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.util.Pair
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -85,7 +86,7 @@ class MediaAdaptor(
|
|||
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (type) {
|
||||
0 -> {
|
||||
|
@ -94,8 +95,8 @@ class MediaAdaptor(
|
|||
val media = mediaList?.getOrNull(position)
|
||||
if (media != null) {
|
||||
b.itemCompactImage.loadImage(media.cover)
|
||||
b.itemCompactOngoing.visibility =
|
||||
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||
b.itemCompactOngoing.isVisible =
|
||||
media.status == currActivity()!!.getString(R.string.status_releasing)
|
||||
b.itemCompactTitle.text = media.userPreferredName
|
||||
b.itemCompactScore.text =
|
||||
((if (media.userScore == 0) (media.meanScore
|
||||
|
@ -140,8 +141,8 @@ class MediaAdaptor(
|
|||
if (media != null) {
|
||||
b.itemCompactImage.loadImage(media.cover)
|
||||
blurImage(b.itemCompactBanner, media.banner ?: media.cover)
|
||||
b.itemCompactOngoing.visibility =
|
||||
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||
b.itemCompactOngoing.isVisible =
|
||||
media.status == currActivity()!!.getString(R.string.status_releasing)
|
||||
b.itemCompactTitle.text = media.userPreferredName
|
||||
b.itemCompactScore.text =
|
||||
((if (media.userScore == 0) (media.meanScore
|
||||
|
@ -188,8 +189,8 @@ class MediaAdaptor(
|
|||
)
|
||||
)
|
||||
blurImage(b.itemCompactBanner, media.banner ?: media.cover)
|
||||
b.itemCompactOngoing.visibility =
|
||||
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||
b.itemCompactOngoing.isVisible =
|
||||
media.status == currActivity()!!.getString(R.string.status_releasing)
|
||||
b.itemCompactTitle.text = media.userPreferredName
|
||||
b.itemCompactScore.text =
|
||||
((if (media.userScore == 0) (media.meanScore
|
||||
|
@ -237,8 +238,8 @@ class MediaAdaptor(
|
|||
)
|
||||
)
|
||||
blurImage(b.itemCompactBanner, media.banner ?: media.cover)
|
||||
b.itemCompactOngoing.visibility =
|
||||
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE
|
||||
b.itemCompactOngoing.isVisible =
|
||||
media.status == currActivity()!!.getString(R.string.status_releasing)
|
||||
b.itemCompactTitle.text = media.userPreferredName
|
||||
b.itemCompactScore.text =
|
||||
((if (media.userScore == 0) (media.meanScore
|
||||
|
|
|
@ -2,9 +2,9 @@ package ani.dantotsu.media
|
|||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.util.TypedValue
|
||||
|
@ -12,9 +12,7 @@ import android.view.GestureDetector
|
|||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
@ -22,8 +20,10 @@ import androidx.appcompat.content.res.AppCompatResources
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.bold
|
||||
import androidx.core.text.color
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.marginBottom
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updateMargins
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -62,6 +62,7 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
|
@ -70,12 +71,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
lateinit var binding: ActivityMediaBinding
|
||||
private val scope = lifecycleScope
|
||||
private val model: MediaDetailsViewModel by viewModels()
|
||||
lateinit var tabLayout: TripleNavAdapter
|
||||
var selected = 0
|
||||
lateinit var navBar: AnimatedBottomBar
|
||||
var anime = true
|
||||
private var adult = false
|
||||
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia()
|
||||
|
@ -83,8 +84,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
if (id != -1) {
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
media =
|
||||
Anilist.query.getMedia(id, false) ?: emptyMedia()
|
||||
media = Anilist.query.getMedia(id, false) ?: emptyMedia()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,26 +100,34 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
binding = ActivityMediaBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
||||
navBar = binding.mediaBottomBar
|
||||
|
||||
val isVertical = resources.configuration.orientation
|
||||
//Ui init
|
||||
// Ui init
|
||||
|
||||
initActivity(this)
|
||||
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
||||
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarHeight }
|
||||
val oldMargin = binding.mediaViewPager.marginBottom
|
||||
AndroidBug5497Workaround.assistActivity(this) {
|
||||
if (it) {
|
||||
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = 0
|
||||
}
|
||||
binding.mediaTabContainer.visibility = View.GONE
|
||||
navBar.visibility = View.GONE
|
||||
} else {
|
||||
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = oldMargin
|
||||
}
|
||||
binding.mediaTabContainer.visibility = View.VISIBLE
|
||||
navBar.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
val navBarRightMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0
|
||||
val navBarBottomMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
|
||||
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
rightMargin = navBarRightMargin
|
||||
bottomMargin = navBarBottomMargin
|
||||
}
|
||||
binding.mediaBanner.updateLayoutParams { height += statusBarHeight }
|
||||
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
|
||||
binding.mediaClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||
|
@ -147,7 +155,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
val banner =
|
||||
if (bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen
|
||||
val viewPager = binding.mediaViewPager
|
||||
//tabLayout = binding.mediaTab as AnimatedBottomBar
|
||||
viewPager.isUserInputEnabled = false
|
||||
viewPager.setPageTransformer(ZoomOutPageTransformer())
|
||||
|
||||
|
@ -157,9 +164,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
|
||||
binding.mediaCoverImage.loadImage(media.cover)
|
||||
binding.mediaCoverImage.setOnLongClickListener {
|
||||
val coverTitle = "${media.userPreferredName}[Cover]"
|
||||
ImageViewDialog.newInstance(
|
||||
this,
|
||||
media.userPreferredName + "[Cover]",
|
||||
coverTitle,
|
||||
media.cover
|
||||
)
|
||||
}
|
||||
|
@ -176,9 +184,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
}
|
||||
|
||||
override fun onLongClick(event: MotionEvent) {
|
||||
val bannerTitle = "${media.userPreferredName}[Banner]"
|
||||
ImageViewDialog.newInstance(
|
||||
this@MediaDetailsActivity,
|
||||
media.userPreferredName + "[Banner]",
|
||||
bannerTitle,
|
||||
media.banner ?: media.cover
|
||||
)
|
||||
banner.performClick()
|
||||
|
@ -186,7 +195,8 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
})
|
||||
banner.setOnTouchListener { _, motionEvent -> gestureDetector.onTouchEvent(motionEvent);true }
|
||||
if (PrefManager.getVal(PrefName.Incognito)) {
|
||||
binding.mediaTitle.text = " ${media.userPreferredName}"
|
||||
val mediaTitle = " ${media.userPreferredName}"
|
||||
binding.mediaTitle.text = mediaTitle
|
||||
binding.incognito.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.mediaTitle.text = media.userPreferredName
|
||||
|
@ -246,13 +256,13 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
@SuppressLint("ResourceType")
|
||||
fun total() {
|
||||
val text = SpannableStringBuilder().apply {
|
||||
val typedValue = TypedValue()
|
||||
val mediaTypedValue = TypedValue()
|
||||
this@MediaDetailsActivity.theme.resolveAttribute(
|
||||
com.google.android.material.R.attr.colorOnBackground,
|
||||
typedValue,
|
||||
mediaTypedValue,
|
||||
true
|
||||
)
|
||||
val white = typedValue.data
|
||||
val white = mediaTypedValue.data
|
||||
if (media.userStatus != null) {
|
||||
append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num))
|
||||
val typedValue = TypedValue()
|
||||
|
@ -342,14 +352,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
progress()
|
||||
}
|
||||
}
|
||||
tabLayout = TripleNavAdapter(
|
||||
binding.mediaTab1,
|
||||
binding.mediaTab2,
|
||||
binding.mediaTab3,
|
||||
media.anime != null,
|
||||
media.format ?: "",
|
||||
isVertical == 1
|
||||
)
|
||||
adult = media.isAdult
|
||||
if (media.anime != null) {
|
||||
viewPager.adapter =
|
||||
|
@ -365,22 +367,36 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
anime = false
|
||||
}
|
||||
|
||||
|
||||
selected = media.selected!!.window
|
||||
binding.mediaTitle.translationX = -screenWidth
|
||||
|
||||
tabLayout.selectionListener = { selected, newId ->
|
||||
binding.commentInputLayout.visibility = if (selected == 2) View.VISIBLE else View.GONE
|
||||
this.selected = selected
|
||||
selectFromID(newId)
|
||||
viewPager.setCurrentItem(selected, false)
|
||||
val sel = model.loadSelected(media, isDownload)
|
||||
sel.window = selected
|
||||
model.saveSelected(media.id, sel)
|
||||
val infoTab = navBar.createTab(R.drawable.ic_round_info_24, R.string.info, R.id.info)
|
||||
val watchTab = if (anime) {
|
||||
navBar.createTab(R.drawable.ic_round_movie_filter_24, R.string.watch, R.id.watch)
|
||||
} else if (media.format == "NOVEL") {
|
||||
navBar.createTab(R.drawable.ic_round_book_24, R.string.read, R.id.read)
|
||||
} else {
|
||||
navBar.createTab(R.drawable.ic_round_import_contacts_24, R.string.read, R.id.read)
|
||||
}
|
||||
tabLayout.selectTab(selected)
|
||||
selectFromID(tabLayout.selected)
|
||||
viewPager.setCurrentItem(selected, false)
|
||||
val commentTab = navBar.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment)
|
||||
navBar.addTab(infoTab)
|
||||
navBar.addTab(watchTab)
|
||||
navBar.addTab(commentTab)
|
||||
navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||
override fun onTabSelected(
|
||||
lastIndex: Int,
|
||||
lastTab: AnimatedBottomBar.Tab?,
|
||||
newIndex: Int,
|
||||
newTab: AnimatedBottomBar.Tab
|
||||
) {
|
||||
selected = newIndex
|
||||
binding.commentInputLayout.isVisible = selected == 2
|
||||
viewPager.setCurrentItem(selected, true)
|
||||
val sel = model.loadSelected(media, isDownload)
|
||||
sel.window = selected
|
||||
model.saveSelected(media.id, sel)
|
||||
}
|
||||
})
|
||||
|
||||
if (model.continueMedia == null && media.cameFromContinue) {
|
||||
model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia)
|
||||
|
@ -390,6 +406,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
if (frag != null) {
|
||||
selected = 2
|
||||
}
|
||||
navBar.selectTabAt(selected)
|
||||
binding.commentInputLayout.isVisible = selected == 2
|
||||
viewPager.setCurrentItem(selected, false)
|
||||
|
||||
val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(true) }
|
||||
live.observe(this) {
|
||||
|
@ -402,40 +421,19 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
}
|
||||
}
|
||||
|
||||
private fun selectFromID(id: Int) {
|
||||
when (id) {
|
||||
R.id.info -> {
|
||||
selected = 0
|
||||
}
|
||||
|
||||
R.id.watch, R.id.read -> {
|
||||
selected = 1
|
||||
}
|
||||
|
||||
R.id.comment -> {
|
||||
selected = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun idFromSelect(): Int {
|
||||
if (anime) when (selected) {
|
||||
0 -> return R.id.info
|
||||
1 -> return R.id.watch
|
||||
2 -> return R.id.comment
|
||||
}
|
||||
else when (selected) {
|
||||
0 -> return R.id.info
|
||||
1 -> return R.id.read
|
||||
2 -> return R.id.comment
|
||||
}
|
||||
return R.id.info
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
val rightMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0
|
||||
val bottomMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
|
||||
val params : ViewGroup.MarginLayoutParams =
|
||||
navBar.layoutParams as ViewGroup.MarginLayoutParams
|
||||
params.updateMargins(right = rightMargin, bottom = bottomMargin)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (this::tabLayout.isInitialized) {
|
||||
tabLayout.selectTab(selected)
|
||||
}
|
||||
navBar.selectTabAt(selected)
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
|
@ -443,7 +441,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
|
|||
ANIME, MANGA, NOVEL
|
||||
}
|
||||
|
||||
//ViewPager
|
||||
// ViewPager
|
||||
private class ViewPagerAdapter(
|
||||
fragmentManager: FragmentManager,
|
||||
lifecycle: Lifecycle,
|
||||
|
|
|
@ -52,12 +52,16 @@ class MediaDetailsViewModel : ViewModel() {
|
|||
it
|
||||
}
|
||||
if (isDownload) {
|
||||
data.sourceIndex = if (media.anime != null) {
|
||||
AnimeSources.list.size - 1
|
||||
} else if (media.format == "MANGA" || media.format == "ONE_SHOT") {
|
||||
MangaSources.list.size - 1
|
||||
} else {
|
||||
NovelSources.list.size - 1
|
||||
data.sourceIndex = when {
|
||||
media.anime != null -> {
|
||||
AnimeSources.list.size - 1
|
||||
}
|
||||
media.format == "MANGA" || media.format == "ONE_SHOT" -> {
|
||||
MangaSources.list.size - 1
|
||||
}
|
||||
else -> {
|
||||
NovelSources.list.size - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
|
@ -152,10 +156,10 @@ class MediaDetailsViewModel : ViewModel() {
|
|||
watchSources?.get(i)?.apply {
|
||||
if (!post && !allowsPreloading) return@apply
|
||||
ep.sEpisode?.let {
|
||||
loadByVideoServers(link, ep.extra, it) {
|
||||
if (it.videos.isNotEmpty()) {
|
||||
list.add(it)
|
||||
ep.extractorCallback?.invoke(it)
|
||||
loadByVideoServers(link, ep.extra, it) { extractor ->
|
||||
if (extractor.videos.isNotEmpty()) {
|
||||
list.add(extractor)
|
||||
ep.extractorCallback?.invoke(extractor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ import android.widget.TextView
|
|||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
@ -37,7 +39,7 @@ import java.io.Serializable
|
|||
import java.net.URLEncoder
|
||||
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
|
||||
class MediaInfoFragment : Fragment() {
|
||||
private var _binding: FragmentMediaInfoBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
@ -46,6 +48,8 @@ class MediaInfoFragment : Fragment() {
|
|||
private var type = "ANIME"
|
||||
private val genreModel: GenresViewModel by activityViewModels()
|
||||
|
||||
private val tripleTab = "\t\t\t"
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -63,8 +67,8 @@ class MediaInfoFragment : Fragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val model: MediaDetailsViewModel by activityViewModels()
|
||||
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
|
||||
binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE
|
||||
binding.mediaInfoContainer.visibility = if (loaded) View.VISIBLE else View.GONE
|
||||
binding.mediaInfoProgressBar.isGone = loaded
|
||||
binding.mediaInfoContainer.isVisible = loaded
|
||||
binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight }
|
||||
|
||||
model.scrolledToTop.observe(viewLifecycleOwner) {
|
||||
|
@ -78,14 +82,16 @@ class MediaInfoFragment : Fragment() {
|
|||
|
||||
binding.mediaInfoProgressBar.visibility = View.GONE
|
||||
binding.mediaInfoContainer.visibility = View.VISIBLE
|
||||
binding.mediaInfoName.text = "\t\t\t" + (media.name ?: media.nameRomaji)
|
||||
val infoName = tripleTab + (media.name ?: media.nameRomaji)
|
||||
binding.mediaInfoName.text = infoName
|
||||
binding.mediaInfoName.setOnLongClickListener {
|
||||
copyToClipboard(media.name ?: media.nameRomaji)
|
||||
true
|
||||
}
|
||||
if (media.name != null) binding.mediaInfoNameRomajiContainer.visibility =
|
||||
View.VISIBLE
|
||||
binding.mediaInfoNameRomaji.text = "\t\t\t" + media.nameRomaji
|
||||
val infoNameRomanji = tripleTab + media.nameRomaji
|
||||
binding.mediaInfoNameRomaji.text = infoNameRomanji
|
||||
binding.mediaInfoNameRomaji.setOnLongClickListener {
|
||||
copyToClipboard(media.nameRomaji)
|
||||
true
|
||||
|
@ -127,8 +133,9 @@ class MediaInfoFragment : Fragment() {
|
|||
}
|
||||
binding.mediaInfoDurationContainer.visibility = View.VISIBLE
|
||||
binding.mediaInfoSeasonContainer.visibility = View.VISIBLE
|
||||
binding.mediaInfoSeason.text =
|
||||
(media.anime.season ?: "??") + " " + (media.anime.seasonYear ?: "??")
|
||||
val seasonInfo = "${(media.anime.season ?: "??")} ${(media.anime.seasonYear ?: "??")}"
|
||||
binding.mediaInfoSeason.text = seasonInfo
|
||||
|
||||
if (media.anime.mainStudio != null) {
|
||||
binding.mediaInfoStudioContainer.visibility = View.VISIBLE
|
||||
binding.mediaInfoStudio.text = media.anime.mainStudio!!.name
|
||||
|
@ -162,9 +169,12 @@ class MediaInfoFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
binding.mediaInfoTotalTitle.setText(R.string.total_eps)
|
||||
binding.mediaInfoTotal.text =
|
||||
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " | " + (media.anime.totalEpisodes
|
||||
?: "~").toString()) else (media.anime.totalEpisodes ?: "~").toString()
|
||||
val infoTotal = if (media.anime.nextAiringEpisode != null)
|
||||
"${media.anime.nextAiringEpisode} | ${media.anime.totalEpisodes ?: "~"}"
|
||||
else
|
||||
(media.anime.totalEpisodes ?: "~").toString()
|
||||
binding.mediaInfoTotal.text = infoTotal
|
||||
|
||||
} else if (media.manga != null) {
|
||||
type = "MANGA"
|
||||
binding.mediaInfoTotalTitle.setText(R.string.total_chaps)
|
||||
|
@ -191,8 +201,9 @@ class MediaInfoFragment : Fragment() {
|
|||
(media.description ?: "null").replace("\\n", "<br>").replace("\\\"", "\""),
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
binding.mediaInfoDescription.text =
|
||||
"\t\t\t" + if (desc.toString() != "null") desc else getString(R.string.no_description_available)
|
||||
val infoDesc = tripleTab + if (desc.toString() != "null") desc else getString(R.string.no_description_available)
|
||||
binding.mediaInfoDescription.text = infoDesc
|
||||
|
||||
binding.mediaInfoDescription.setOnClickListener {
|
||||
if (binding.mediaInfoDescription.maxLines == 5) {
|
||||
ObjectAnimator.ofInt(binding.mediaInfoDescription, "maxLines", 100)
|
||||
|
@ -550,7 +561,7 @@ class MediaInfoFragment : Fragment() {
|
|||
}
|
||||
|
||||
override fun onResume() {
|
||||
binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE
|
||||
binding.mediaInfoProgressBar.isGone = loaded
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
||||
var media: Media?
|
||||
|
@ -168,9 +167,10 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
|
|||
val init =
|
||||
if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString()
|
||||
.toInt() else 0
|
||||
if (init < (total
|
||||
?: 5000)
|
||||
) binding.mediaListProgress.setText((init + 1).toString())
|
||||
if (init < (total ?: 5000)) {
|
||||
val progressText = "${init + 1}"
|
||||
binding.mediaListProgress.setText(progressText)
|
||||
}
|
||||
if (init + 1 == (total ?: 5000)) {
|
||||
binding.mediaListStatus.setText(statusStrings[2], false)
|
||||
onComplete()
|
||||
|
|
|
@ -54,7 +54,6 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
|
|||
}
|
||||
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
||||
val scope = viewLifecycleOwner.lifecycleScope
|
||||
|
@ -68,7 +67,7 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
|
|||
MAL.query.deleteList(media.anime != null, media.idMAL)
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
snackString("Failed to delete because of... ${e.message}")
|
||||
snackString(getString(R.string.delete_fail_reason, e.message))
|
||||
}
|
||||
return@withContext
|
||||
}
|
||||
|
@ -154,7 +153,10 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
|
|||
val init =
|
||||
if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString()
|
||||
.toInt() else 0
|
||||
if (init < (total ?: 5000)) binding.mediaListProgress.setText((init + 1).toString())
|
||||
if (init < (total ?: 5000)) {
|
||||
val progressText = "${init + 1}"
|
||||
binding.mediaListProgress.setText(progressText)
|
||||
}
|
||||
if (init + 1 == (total ?: 5000)) {
|
||||
binding.mediaListStatus.setText(statusStrings[2], false)
|
||||
}
|
||||
|
|
26
app/src/main/java/ani/dantotsu/media/MediaType.kt
Normal file
26
app/src/main/java/ani/dantotsu/media/MediaType.kt
Normal file
|
@ -0,0 +1,26 @@
|
|||
package ani.dantotsu.media
|
||||
|
||||
enum class MediaType {
|
||||
ANIME,
|
||||
MANGA,
|
||||
NOVEL;
|
||||
|
||||
fun asText(): String {
|
||||
return when (this) {
|
||||
ANIME -> "Anime"
|
||||
MANGA -> "Manga"
|
||||
NOVEL -> "Novel"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromText(string : String): MediaType {
|
||||
return when (string) {
|
||||
"Anime" -> ANIME
|
||||
"Manga" -> MANGA
|
||||
"Novel" -> NOVEL
|
||||
else -> { ANIME }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ class ProgressAdapter(private val horizontal: Boolean = true, searched: Boolean)
|
|||
return ProgressViewHolder(binding)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onBindViewHolder(holder: ProgressViewHolder, position: Int) {
|
||||
val progressBar = holder.binding.root
|
||||
bar = progressBar
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.os.Parcelable
|
|||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePaddingRelative
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
|
@ -137,7 +138,7 @@ class SearchActivity : AppCompatActivity() {
|
|||
model.searchResults.results.addAll(it.results)
|
||||
mediaAdaptor.notifyItemRangeInserted(prev, it.results.size)
|
||||
|
||||
progressAdapter.bar?.visibility = if (it.hasNextPage) View.VISIBLE else View.GONE
|
||||
progressAdapter.bar?.isVisible = it.hasNextPage
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package ani.dantotsu.media
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -22,7 +21,6 @@ abstract class SourceAdapter(
|
|||
return SourceViewHolder(binding)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: SourceViewHolder, position: Int) {
|
||||
val binding = holder.binding
|
||||
val character = sources[position]
|
||||
|
|
|
@ -65,7 +65,7 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() {
|
|||
i = media!!.selected!!.sourceIndex
|
||||
|
||||
val source = if (media!!.anime != null) {
|
||||
(if (!media!!.isAdult) AnimeSources else HAnimeSources)[i!!]
|
||||
(if (media!!.isAdult) HAnimeSources else AnimeSources)[i!!]
|
||||
} else {
|
||||
anime = false
|
||||
(if (media!!.isAdult) HMangaSources else MangaSources)[i!!]
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.view.ViewGroup
|
|||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
@ -114,7 +115,7 @@ class StudioActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onResume() {
|
||||
binding.studioProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE
|
||||
binding.studioProgressBar.isGone = loaded
|
||||
super.onResume()
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ class SubtitleDownloader {
|
|||
|
||||
companion object {
|
||||
//doesn't really download the subtitles -\_(o_o)_/-
|
||||
suspend fun loadSubtitleType(context: Context, url: String): SubtitleType =
|
||||
suspend fun loadSubtitleType(url: String): SubtitleType =
|
||||
withContext(Dispatchers.IO) {
|
||||
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
|
||||
val networkHelper = Injekt.get<NetworkHelper>()
|
||||
|
@ -60,7 +60,7 @@ class SubtitleDownloader {
|
|||
if (!directory.exists()) { //just in case
|
||||
directory.mkdirs()
|
||||
}
|
||||
val type = loadSubtitleType(context, url)
|
||||
val type = loadSubtitleType(url)
|
||||
val subtiteFile = File(directory, "subtitle.${type}")
|
||||
if (subtiteFile.exists()) {
|
||||
subtiteFile.delete()
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
package ani.dantotsu.media
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.navBarHeight
|
||||
import nl.joery.animatedbottombar.AnimatedBottomBar
|
||||
|
||||
class TripleNavAdapter(
|
||||
private val nav1: AnimatedBottomBar,
|
||||
private val nav2: AnimatedBottomBar,
|
||||
private val nav3: AnimatedBottomBar,
|
||||
anime: Boolean,
|
||||
format: String,
|
||||
private val isScreenVertical: Boolean = false
|
||||
) {
|
||||
var selected: Int = 0
|
||||
var selectionListener: ((Int, Int) -> Unit)? = null
|
||||
init {
|
||||
nav1.tabs.clear()
|
||||
nav2.tabs.clear()
|
||||
nav3.tabs.clear()
|
||||
val infoTab = nav1.createTab(R.drawable.ic_round_info_24, R.string.info, R.id.info)
|
||||
val watchTab = if (anime) {
|
||||
nav2.createTab(R.drawable.ic_round_movie_filter_24, R.string.watch, R.id.watch)
|
||||
} else if (format == "NOVEL") {
|
||||
nav2.createTab(R.drawable.ic_round_book_24, R.string.read, R.id.read)
|
||||
} else {
|
||||
nav2.createTab(R.drawable.ic_round_import_contacts_24, R.string.read, R.id.read)
|
||||
}
|
||||
val commentTab = nav3.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment)
|
||||
nav1.addTab(infoTab)
|
||||
nav1.visibility = ViewGroup.VISIBLE
|
||||
if (isScreenVertical) {
|
||||
nav2.visibility = ViewGroup.GONE
|
||||
nav3.visibility = ViewGroup.GONE
|
||||
nav1.addTab(watchTab)
|
||||
nav1.addTab(commentTab)
|
||||
nav1.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = navBarHeight
|
||||
}
|
||||
nav2.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = navBarHeight
|
||||
}
|
||||
nav3.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin = navBarHeight
|
||||
}
|
||||
} else {
|
||||
nav1.indicatorColor = Color.TRANSPARENT
|
||||
nav2.indicatorColor = Color.TRANSPARENT
|
||||
nav3.indicatorColor = Color.TRANSPARENT
|
||||
nav2.visibility = ViewGroup.VISIBLE
|
||||
nav3.visibility = ViewGroup.VISIBLE
|
||||
nav2.addTab(watchTab)
|
||||
nav3.addTab(commentTab)
|
||||
nav2.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||
override fun onTabSelected(
|
||||
lastIndex: Int,
|
||||
lastTab: AnimatedBottomBar.Tab?,
|
||||
newIndex: Int,
|
||||
newTab: AnimatedBottomBar.Tab
|
||||
) {
|
||||
selected = 1
|
||||
deselectOthers(selected)
|
||||
selectionListener?.invoke(selected, newTab.id)
|
||||
}
|
||||
})
|
||||
nav3.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||
override fun onTabSelected(
|
||||
lastIndex: Int,
|
||||
lastTab: AnimatedBottomBar.Tab?,
|
||||
newIndex: Int,
|
||||
newTab: AnimatedBottomBar.Tab
|
||||
) {
|
||||
selected = 2
|
||||
deselectOthers(selected)
|
||||
selectionListener?.invoke(selected, newTab.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
nav1.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
||||
override fun onTabSelected(
|
||||
lastIndex: Int,
|
||||
lastTab: AnimatedBottomBar.Tab?,
|
||||
newIndex: Int,
|
||||
newTab: AnimatedBottomBar.Tab
|
||||
) {
|
||||
if (!isScreenVertical) {
|
||||
selected = 0
|
||||
deselectOthers(selected)
|
||||
} else selected = newIndex
|
||||
selectionListener?.invoke(selected, newTab.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun deselectOthers(selected: Int) {
|
||||
if (selected == 0) {
|
||||
nav2.clearSelection()
|
||||
nav3.clearSelection()
|
||||
}
|
||||
if (selected == 1) {
|
||||
nav1.clearSelection()
|
||||
nav3.clearSelection()
|
||||
}
|
||||
if (selected == 2) {
|
||||
nav1.clearSelection()
|
||||
nav2.clearSelection()
|
||||
}
|
||||
}
|
||||
|
||||
fun selectTab(tab: Int) {
|
||||
selected = tab
|
||||
if (!isScreenVertical) {
|
||||
when (tab) {
|
||||
0 -> nav1.selectTabAt(0)
|
||||
1 -> nav2.selectTabAt(0)
|
||||
2 -> nav3.selectTabAt(0)
|
||||
}
|
||||
deselectOthers(selected)
|
||||
} else {
|
||||
nav1.selectTabAt(selected)
|
||||
}
|
||||
}
|
||||
|
||||
fun setVisibility(visibility: Int) {
|
||||
if (isScreenVertical) {
|
||||
nav1.visibility = visibility
|
||||
return
|
||||
}
|
||||
nav1.visibility = visibility
|
||||
nav2.visibility = visibility
|
||||
nav3.visibility = visibility
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import java.util.regex.Pattern
|
|||
class AnimeNameAdapter {
|
||||
companion object {
|
||||
const val episodeRegex =
|
||||
"(episode|episodio|ep|e)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*\\(?\\s*(sub|subbed|dub|dubbed)*\\s*\\)?\\s*"
|
||||
"(episode|episodio|ep|e)[\\s:.\\-]*(\\d+\\.?\\d*)[\\s:.\\-]*\\(?\\s*(sub|subbed|dub|dubbed)*\\s*\\)?\\s*"
|
||||
const val failedEpisodeNumberRegex =
|
||||
"(?<!part\\s)\\b(\\d+)\\b"
|
||||
const val seasonRegex = "(season|s)[\\s:.\\-]*(\\d+)[\\s:.\\-]*"
|
||||
|
@ -114,7 +114,7 @@ class AnimeNameAdapter {
|
|||
val regexPattern = Regex(episodeRegex, RegexOption.IGNORE_CASE)
|
||||
val removedNumber = text.replace(regexPattern, "")
|
||||
return if (removedNumber.equals(text, true)) { // if nothing was removed
|
||||
val failedEpisodeNumberPattern: Regex =
|
||||
val failedEpisodeNumberPattern =
|
||||
Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE)
|
||||
failedEpisodeNumberPattern.replace(removedNumber) { mr ->
|
||||
mr.value.replaceFirst(mr.groupValues[1], "")
|
||||
|
|
|
@ -13,6 +13,8 @@ import android.widget.LinearLayout
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import ani.dantotsu.*
|
||||
|
@ -54,7 +56,6 @@ class AnimeWatchAdapter(
|
|||
private var nestedDialog: AlertDialog? = null
|
||||
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val binding = holder.binding
|
||||
_binding = binding
|
||||
|
@ -97,15 +98,12 @@ class AnimeWatchAdapter(
|
|||
null
|
||||
)
|
||||
}
|
||||
val offline = if (!isOnline(binding.root.context) || PrefManager.getVal(
|
||||
PrefName.OfflineMode
|
||||
)
|
||||
) View.GONE else View.VISIBLE
|
||||
val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
|
||||
|
||||
binding.animeSourceNameContainer.visibility = offline
|
||||
binding.animeSourceSettings.visibility = offline
|
||||
binding.animeSourceSearch.visibility = offline
|
||||
binding.animeSourceTitle.visibility = offline
|
||||
binding.animeSourceNameContainer.isGone = offline
|
||||
binding.animeSourceSettings.isGone = offline
|
||||
binding.animeSourceSearch.isGone = offline
|
||||
binding.animeSourceTitle.isGone = offline
|
||||
|
||||
//Source Selection
|
||||
var source =
|
||||
|
@ -117,8 +115,7 @@ class AnimeWatchAdapter(
|
|||
this.selectDub = media.selected!!.preferDub
|
||||
binding.animeSourceTitle.text = showUserText
|
||||
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
|
||||
binding.animeSourceDubbedCont.visibility =
|
||||
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
|
||||
binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,8 +134,7 @@ class AnimeWatchAdapter(
|
|||
changing = true
|
||||
binding.animeSourceDubbed.isChecked = selectDub
|
||||
changing = false
|
||||
binding.animeSourceDubbedCont.visibility =
|
||||
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
|
||||
binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
|
||||
source = i
|
||||
setLanguageList(0, i)
|
||||
}
|
||||
|
@ -158,8 +154,7 @@ class AnimeWatchAdapter(
|
|||
changing = true
|
||||
binding.animeSourceDubbed.isChecked = selectDub
|
||||
changing = false
|
||||
binding.animeSourceDubbedCont.visibility =
|
||||
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
|
||||
binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
|
||||
setLanguageList(i, source)
|
||||
}
|
||||
subscribeButton(false)
|
||||
|
@ -223,9 +218,9 @@ class AnimeWatchAdapter(
|
|||
else -> dialogBinding.animeSourceList
|
||||
}
|
||||
when (style) {
|
||||
0 -> dialogBinding.layoutText.text = "List"
|
||||
1 -> dialogBinding.layoutText.text = "Grid"
|
||||
2 -> dialogBinding.layoutText.text = "Compact"
|
||||
0 -> dialogBinding.layoutText.setText(R.string.list)
|
||||
1 -> dialogBinding.layoutText.setText(R.string.grid)
|
||||
2 -> dialogBinding.layoutText.setText(R.string.compact)
|
||||
else -> dialogBinding.animeSourceList
|
||||
}
|
||||
selected.alpha = 1f
|
||||
|
@ -237,24 +232,24 @@ class AnimeWatchAdapter(
|
|||
dialogBinding.animeSourceList.setOnClickListener {
|
||||
selected(it as ImageButton)
|
||||
style = 0
|
||||
dialogBinding.layoutText.text = "List"
|
||||
dialogBinding.layoutText.setText(R.string.list)
|
||||
run = true
|
||||
}
|
||||
dialogBinding.animeSourceGrid.setOnClickListener {
|
||||
selected(it as ImageButton)
|
||||
style = 1
|
||||
dialogBinding.layoutText.text = "Grid"
|
||||
dialogBinding.layoutText.setText(R.string.grid)
|
||||
run = true
|
||||
}
|
||||
dialogBinding.animeSourceCompact.setOnClickListener {
|
||||
selected(it as ImageButton)
|
||||
style = 2
|
||||
dialogBinding.layoutText.text = "Compact"
|
||||
dialogBinding.layoutText.setText(R.string.compact)
|
||||
run = true
|
||||
}
|
||||
dialogBinding.animeWebviewContainer.setOnClickListener {
|
||||
if (!WebViewUtil.supportsWebView(fragment.requireContext())) {
|
||||
toast("WebView not installed")
|
||||
toast(R.string.webview_not_installed)
|
||||
}
|
||||
//start CookieCatcher activity
|
||||
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
|
||||
|
@ -307,7 +302,6 @@ class AnimeWatchAdapter(
|
|||
}
|
||||
|
||||
//Chips
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun updateChips(limit: Int, names: Array<String>, arr: Array<Int>, selected: Int = 0) {
|
||||
val binding = _binding
|
||||
if (binding != null) {
|
||||
|
@ -329,7 +323,8 @@ class AnimeWatchAdapter(
|
|||
0
|
||||
)
|
||||
}
|
||||
chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
|
||||
val chipText = "${names[limit * (position)]} - ${names[last - 1]}"
|
||||
chip.text = chipText
|
||||
chip.setTextColor(
|
||||
ContextCompat.getColorStateList(
|
||||
fragment.requireContext(),
|
||||
|
@ -363,7 +358,6 @@ class AnimeWatchAdapter(
|
|||
_binding?.animeSourceChipGroup?.removeAllViews()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun handleEpisodes() {
|
||||
val binding = _binding
|
||||
if (binding != null) {
|
||||
|
@ -371,9 +365,9 @@ class AnimeWatchAdapter(
|
|||
val episodes = media.anime.episodes!!.keys.toTypedArray()
|
||||
|
||||
val anilistEp = (media.userProgress ?: 0).plus(1)
|
||||
val appEp =
|
||||
PrefManager.getCustomVal<String?>("${media.id}_current_ep", "")?.toIntOrNull()
|
||||
?: 1
|
||||
val appEp = PrefManager.getCustomVal<String?>(
|
||||
"${media.id}_current_ep", ""
|
||||
)?.toIntOrNull() ?: 1
|
||||
|
||||
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
|
||||
if (episodes.contains(continueEp)) {
|
||||
|
@ -409,15 +403,19 @@ class AnimeWatchAdapter(
|
|||
ep.thumb ?: FileUrl[media.banner ?: media.cover], 0
|
||||
)
|
||||
if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE
|
||||
|
||||
binding.animeSourceContinueText.text =
|
||||
currActivity()!!.getString(R.string.continue_episode) + "${ep.number}${if (ep.filler) " - Filler" else ""}${"\n$cleanedTitle"}"
|
||||
currActivity()!!.getString(R.string.continue_episode, ep.number, if (ep.filler)
|
||||
currActivity()!!.getString(R.string.filler_tag)
|
||||
else
|
||||
"", cleanedTitle)
|
||||
binding.animeSourceContinue.setOnClickListener {
|
||||
fragment.onEpisodeClick(continueEp)
|
||||
}
|
||||
if (fragment.continueEp) {
|
||||
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < PrefManager.getVal<Float>(
|
||||
PrefName.WatchPercentage
|
||||
)
|
||||
if (
|
||||
(binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams)
|
||||
.weight < PrefManager.getVal<Float>(PrefName.WatchPercentage)
|
||||
) {
|
||||
binding.animeSourceContinue.performClick()
|
||||
fragment.continueEp = false
|
||||
|
@ -428,13 +426,10 @@ class AnimeWatchAdapter(
|
|||
}
|
||||
|
||||
binding.animeSourceProgressBar.visibility = View.GONE
|
||||
if (media.anime.episodes!!.isNotEmpty()) {
|
||||
binding.animeSourceNotFound.visibility = View.GONE
|
||||
binding.faqbutton.visibility = View.GONE}
|
||||
else {
|
||||
binding.animeSourceNotFound.visibility = View.VISIBLE
|
||||
binding.faqbutton.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
val sourceFound = media.anime.episodes!!.isNotEmpty()
|
||||
binding.animeSourceNotFound.isGone = sourceFound
|
||||
binding.faqbutton.isGone = sourceFound
|
||||
} else {
|
||||
binding.animeSourceContinue.visibility = View.GONE
|
||||
binding.animeSourceNotFound.visibility = View.GONE
|
||||
|
|
|
@ -17,6 +17,8 @@ import androidx.annotation.OptIn
|
|||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.math.MathUtils
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
@ -27,19 +29,24 @@ import androidx.recyclerview.widget.ConcatAdapter
|
|||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.FileUrl
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.anime.AnimeDownloaderService
|
||||
import ani.dantotsu.download.video.ExoplayerDownloadService
|
||||
import ani.dantotsu.dp
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.media.MediaDetailsViewModel
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.others.LanguageMapper
|
||||
import ani.dantotsu.parsers.AnimeParser
|
||||
import ani.dantotsu.parsers.AnimeSources
|
||||
import ani.dantotsu.parsers.HAnimeSources
|
||||
import ani.dantotsu.setNavigationTheme
|
||||
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
|
@ -340,16 +347,12 @@ class AnimeWatchFragment : Fragment() {
|
|||
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
||||
val activity = activity
|
||||
if (activity is MediaDetailsActivity && isAdded) {
|
||||
val visibility = if (show) View.VISIBLE else View.GONE
|
||||
activity.findViewById<AppBarLayout>(R.id.mediaAppBar).visibility = visibility
|
||||
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility
|
||||
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility
|
||||
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility
|
||||
|
||||
activity.tabLayout.setVisibility(visibility)
|
||||
|
||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||
if (show) View.GONE else View.VISIBLE
|
||||
activity.findViewById<AppBarLayout>(R.id.mediaAppBar).isVisible = show
|
||||
activity.findViewById<ViewPager2>(R.id.mediaViewPager).isVisible = show
|
||||
activity.findViewById<CardView>(R.id.mediaCover).isVisible = show
|
||||
activity.findViewById<CardView>(R.id.mediaClose).isVisible = show
|
||||
activity.navBar.isVisible = show
|
||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).isGone = show
|
||||
}
|
||||
}
|
||||
var itemSelected = false
|
||||
|
@ -435,7 +438,7 @@ class AnimeWatchFragment : Fragment() {
|
|||
DownloadedType(
|
||||
media.mainName(),
|
||||
i,
|
||||
DownloadedType.Type.ANIME
|
||||
MediaType.ANIME
|
||||
)
|
||||
)
|
||||
episodeAdapter.purgeDownload(i)
|
||||
|
@ -447,7 +450,7 @@ class AnimeWatchFragment : Fragment() {
|
|||
DownloadedType(
|
||||
media.mainName(),
|
||||
i,
|
||||
DownloadedType.Type.ANIME
|
||||
MediaType.ANIME
|
||||
)
|
||||
)
|
||||
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
|
||||
|
|
|
@ -8,18 +8,21 @@ import android.view.ViewGroup
|
|||
import android.view.animation.LinearInterpolator
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.offline.DownloadIndex
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.connections.updateProgress
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.databinding.ItemEpisodeCompactBinding
|
||||
import ani.dantotsu.databinding.ItemEpisodeGridBinding
|
||||
import ani.dantotsu.databinding.ItemEpisodeListBinding
|
||||
import ani.dantotsu.download.anime.AnimeDownloaderService
|
||||
import ani.dantotsu.download.video.Helper
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.setAnimation
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
|
@ -97,7 +100,6 @@ class EpisodeAdapter(
|
|||
return type
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val ep = arr[position]
|
||||
val title = if (!ep.title.isNullOrEmpty() && ep.title != "null") {
|
||||
|
@ -125,8 +127,7 @@ class EpisodeAdapter(
|
|||
binding.itemEpisodeFiller.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.isVisible = !ep.desc.isNullOrBlank()
|
||||
binding.itemEpisodeDesc.text = ep.desc ?: ""
|
||||
holder.bind(ep.number, ep.downloadProgress, ep.desc)
|
||||
|
||||
|
@ -203,8 +204,7 @@ class EpisodeAdapter(
|
|||
val binding = holder.binding
|
||||
setAnimation(fragment.requireContext(), holder.binding.root)
|
||||
binding.itemEpisodeNumber.text = ep.number
|
||||
binding.itemEpisodeFillerView.visibility =
|
||||
if (ep.filler) View.VISIBLE else View.GONE
|
||||
binding.itemEpisodeFillerView.isVisible = ep.filler
|
||||
if (media.userProgress != null) {
|
||||
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat())
|
||||
binding.itemEpisodeViewedCover.visibility = View.VISIBLE
|
||||
|
@ -429,7 +429,7 @@ class EpisodeAdapter(
|
|||
if (bytes < 0) return null
|
||||
val unit = 1000
|
||||
if (bytes < unit) return "$bytes B"
|
||||
val exp = (Math.log(bytes.toDouble()) / ln(unit.toDouble())).toInt()
|
||||
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
|
||||
val pre = ("KMGTPE")[exp - 1]
|
||||
return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,10 @@ import android.graphics.Color
|
|||
import android.graphics.drawable.Animatable
|
||||
import android.hardware.SensorManager
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioManager.*
|
||||
import android.media.AudioManager.AUDIOFOCUS_GAIN
|
||||
import android.media.AudioManager.AUDIOFOCUS_LOSS
|
||||
import android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
|
||||
import android.media.AudioManager.STREAM_MUSIC
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
@ -27,8 +30,18 @@ import android.provider.Settings.System
|
|||
import android.util.AttributeSet
|
||||
import android.util.Rational
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.view.KeyEvent.*
|
||||
import android.view.GestureDetector
|
||||
import android.view.KeyEvent
|
||||
import android.view.KeyEvent.ACTION_UP
|
||||
import android.view.KeyEvent.KEYCODE_B
|
||||
import android.view.KeyEvent.KEYCODE_DPAD_LEFT
|
||||
import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
|
||||
import android.view.KeyEvent.KEYCODE_N
|
||||
import android.view.KeyEvent.KEYCODE_SPACE
|
||||
import android.view.MotionEvent
|
||||
import android.view.OrientationEventListener
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ImageButton
|
||||
|
@ -46,27 +59,43 @@ import androidx.core.view.updateLayoutParams
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.cast.CastPlayer
|
||||
import androidx.media3.cast.SessionAvailabilityListener
|
||||
import androidx.media3.common.*
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE
|
||||
import androidx.media3.common.C.TRACK_TYPE_VIDEO
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.PlaybackParameters
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.TrackSelectionOverride
|
||||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.DefaultDataSourceFactory
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
import androidx.media3.datasource.HttpDataSource
|
||||
import androidx.media3.datasource.cache.CacheDataSource
|
||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||
import androidx.media3.exoplayer.DefaultLoadControl
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||
import androidx.media3.exoplayer.util.EventLogger
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.ui.*
|
||||
import androidx.media3.ui.CaptionStyleCompat.*
|
||||
import androidx.media3.exoplayer.DefaultLoadControl
|
||||
import androidx.media3.ui.AspectRatioFrameLayout
|
||||
import androidx.media3.ui.CaptionStyleCompat
|
||||
import androidx.media3.ui.CaptionStyleCompat.EDGE_TYPE_DEPRESSED
|
||||
import androidx.media3.ui.CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW
|
||||
import androidx.media3.ui.CaptionStyleCompat.EDGE_TYPE_NONE
|
||||
import androidx.media3.ui.CaptionStyleCompat.EDGE_TYPE_OUTLINE
|
||||
import androidx.media3.ui.DefaultTimeBar
|
||||
import androidx.media3.ui.PlayerView
|
||||
import androidx.media3.ui.SubtitleView
|
||||
import androidx.mediarouter.app.MediaRouteButton
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.GesturesListener
|
||||
import ani.dantotsu.NoPaddingArrayAdapter
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.brightnessConverter
|
||||
import ani.dantotsu.circularReveal
|
||||
import ani.dantotsu.connections.anilist.Anilist
|
||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||
import ani.dantotsu.connections.discord.Discord
|
||||
|
@ -75,19 +104,38 @@ import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton
|
|||
import ani.dantotsu.connections.discord.RPC
|
||||
import ani.dantotsu.connections.updateProgress
|
||||
import ani.dantotsu.databinding.ActivityExoplayerBinding
|
||||
import ani.dantotsu.defaultHeaders
|
||||
import ani.dantotsu.download.video.Helper
|
||||
import ani.dantotsu.dp
|
||||
import ani.dantotsu.getCurrentBrightnessValue
|
||||
import ani.dantotsu.hideSystemBars
|
||||
import ani.dantotsu.hideSystemBarsExtendView
|
||||
import ani.dantotsu.isOnline
|
||||
import ani.dantotsu.logError
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsViewModel
|
||||
import ani.dantotsu.media.SubtitleDownloader
|
||||
import ani.dantotsu.okHttpClient
|
||||
import ani.dantotsu.others.AniSkip
|
||||
import ani.dantotsu.others.AniSkip.getType
|
||||
import ani.dantotsu.others.ResettableTimer
|
||||
import ani.dantotsu.others.getSerialized
|
||||
import ani.dantotsu.parsers.*
|
||||
import ani.dantotsu.parsers.AnimeSources
|
||||
import ani.dantotsu.parsers.HAnimeSources
|
||||
import ani.dantotsu.parsers.Subtitle
|
||||
import ani.dantotsu.parsers.SubtitleType
|
||||
import ani.dantotsu.parsers.Video
|
||||
import ani.dantotsu.parsers.VideoExtractor
|
||||
import ani.dantotsu.parsers.VideoType
|
||||
import ani.dantotsu.px
|
||||
import ani.dantotsu.settings.PlayerSettingsActivity
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.startMainActivity
|
||||
import ani.dantotsu.themes.ThemeManager
|
||||
import ani.dantotsu.toast
|
||||
import ani.dantotsu.tryWithSuspend
|
||||
import ani.dantotsu.util.Logger
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
|
@ -103,14 +151,17 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
import java.util.Calendar
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@UnstableApi
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityListener {
|
||||
|
||||
private val resumeWindow = "resumeWindow"
|
||||
|
@ -344,15 +395,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
isCastApiAvailable = GoogleApiAvailability.getInstance()
|
||||
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||
try {
|
||||
castContext = CastContext.getSharedInstance(this)
|
||||
castContext = CastContext.getSharedInstance(this, Executors.newSingleThreadExecutor()).result
|
||||
castPlayer = CastPlayer(castContext!!)
|
||||
castPlayer!!.setSessionAvailabilityListener(this)
|
||||
} catch (e: Exception) {
|
||||
isCastApiAvailable = false
|
||||
}
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
hideSystemBars()
|
||||
hideSystemBarsExtendView()
|
||||
|
||||
onBackPressedDispatcher.addCallback(this) {
|
||||
finishAndRemoveTask()
|
||||
|
@ -397,21 +447,25 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
orientationListener =
|
||||
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
if (orientation in 45..135) {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
when (orientation) {
|
||||
in 45..135 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
} else if (orientation in 225..315) {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
in 225..315 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
} else if (orientation in 315..360 || orientation in 0..45) {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
in 315..360, in 0..45 -> {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
|
||||
exoRotate.visibility = View.VISIBLE
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
rotation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -703,11 +757,13 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
fun seek(forward: Boolean, event: MotionEvent? = null) {
|
||||
val seekTime = PrefManager.getVal<Int>(PrefName.SeekTime)
|
||||
val (card, text) = if (forward) {
|
||||
forwardText.text = "+${seekTime * ++seekTimesF}"
|
||||
val text = "+${seekTime * ++seekTimesF}"
|
||||
forwardText.text = text
|
||||
handler.post { exoPlayer.seekTo(exoPlayer.currentPosition + seekTime * 1000) }
|
||||
fastForwardCard to forwardText
|
||||
} else {
|
||||
rewindText.text = "-${seekTime * ++seekTimesR}"
|
||||
val text = "-${seekTime * ++seekTimesR}"
|
||||
rewindText.text = text
|
||||
handler.post { exoPlayer.seekTo(exoPlayer.currentPosition - seekTime * 1000) }
|
||||
fastRewindCard to rewindText
|
||||
}
|
||||
|
@ -941,7 +997,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
episodeArr = episodes.keys.toList()
|
||||
currentEpisodeIndex = episodeArr.indexOf(media.anime!!.selectedEpisode!!)
|
||||
|
||||
episodeTitleArr = arrayListOf<String>()
|
||||
episodeTitleArr = arrayListOf()
|
||||
episodes.forEach {
|
||||
val episode = it.value
|
||||
val cleanedTitle = AnimeNameAdapter.removeEpisodeNumberCompletely(episode.title ?: "")
|
||||
|
@ -1052,7 +1108,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""),
|
||||
RPC.Link(
|
||||
"Stream on Dantotsu",
|
||||
"https://github.com/rebelonion/Dantotsu/"
|
||||
getString(R.string.github)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -1264,6 +1320,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
media.anime!!.selectedEpisode!!
|
||||
)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = (PrefManager.getNullableCustomVal("continueAnimeList", listOf<Int>(), List::class.java) as List<Int>).toMutableList()
|
||||
if (list.contains(media.id)) list.remove(media.id)
|
||||
list.add(media.id)
|
||||
|
@ -1293,7 +1350,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
}
|
||||
|
||||
//Subtitles
|
||||
exoSubtitle.visibility = if (ext.subtitles.isNotEmpty()) View.VISIBLE else View.GONE
|
||||
exoSubtitle.isVisible = ext.subtitles.isNotEmpty()
|
||||
exoSubtitle.setOnClickListener {
|
||||
subClick()
|
||||
}
|
||||
|
@ -1301,9 +1358,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
if (subtitle != null) {
|
||||
//var localFile: String? = null
|
||||
if (subtitle?.type == SubtitleType.UNKNOWN) {
|
||||
val context = this
|
||||
runBlocking {
|
||||
val type = SubtitleDownloader.loadSubtitleType(context, subtitle!!.file.url)
|
||||
val type = SubtitleDownloader.loadSubtitleType(subtitle!!.file.url)
|
||||
val fileUri = Uri.parse(subtitle!!.file.url)
|
||||
sub = MediaItem.SubtitleConfiguration
|
||||
.Builder(fileUri)
|
||||
|
@ -1358,8 +1414,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
}
|
||||
dataSource
|
||||
}
|
||||
val dafuckDataSourceFactory =
|
||||
DefaultDataSourceFactory(this, Util.getUserAgent(this, R.string.app_name.toString()))
|
||||
val dafuckDataSourceFactory = DefaultDataSource.Factory(this)
|
||||
cacheFactory = CacheDataSource.Factory().apply {
|
||||
setCache(Helper.getSimpleCache(this@ExoplayerView))
|
||||
if (ext.server.offline) {
|
||||
|
@ -1659,7 +1714,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
|
||||
aspectRatio = Rational(width, height)
|
||||
|
||||
videoInfo.text = "Quality: ${height}p"
|
||||
videoInfo.text = getString(R.string.video_quality, height)
|
||||
|
||||
if (exoPlayer.duration < playbackPosition)
|
||||
exoPlayer.seekTo(0)
|
||||
|
@ -1735,28 +1790,26 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
|
|||
timer = null
|
||||
return
|
||||
}
|
||||
if (timer == null) {
|
||||
timer = object : CountDownTimer(5000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
if (new == null){
|
||||
skipTimeButton.visibility = View.GONE
|
||||
exoSkip.visibility = View.VISIBLE
|
||||
disappeared = false
|
||||
functionstarted = false
|
||||
cancelTimer()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
timer = object : CountDownTimer(5000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
if (new == null) {
|
||||
skipTimeButton.visibility = View.GONE
|
||||
exoSkip.visibility = View.VISIBLE
|
||||
disappeared = true
|
||||
disappeared = false
|
||||
functionstarted = false
|
||||
cancelTimer()
|
||||
}
|
||||
}
|
||||
timer?.start()
|
||||
|
||||
override fun onFinish() {
|
||||
skipTimeButton.visibility = View.GONE
|
||||
exoSkip.visibility = View.VISIBLE
|
||||
disappeared = true
|
||||
functionstarted = false
|
||||
cancelTimer()
|
||||
}
|
||||
}
|
||||
timer?.start()
|
||||
|
||||
}
|
||||
if (PrefManager.getVal(PrefName.ShowTimeStampButton)) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import android.util.TypedValue
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
@ -302,7 +303,6 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||
)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: UrlViewHolder, position: Int) {
|
||||
val binding = holder.binding
|
||||
val video = extractor.videos[position]
|
||||
|
@ -401,12 +401,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
|
|||
dismiss()
|
||||
}
|
||||
if (video.format == VideoType.CONTAINER) {
|
||||
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE
|
||||
binding.urlSize.text =
|
||||
// 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"))
|
||||
binding.urlSize.isVisible = video.size != null
|
||||
// if video size is null or 0, show "Unknown Size" else show the size in MB
|
||||
val sizeText = getString(R.string.mb_size, "${if (video.extraNote != null) " : " else ""}${
|
||||
if (video.size == 0.0) getString(R.string.size_unknown) else DecimalFormat("#.##").format(video.size ?: 0)
|
||||
}")
|
||||
binding.urlSize.text = sizeText
|
||||
}
|
||||
binding.urlNote.visibility = View.VISIBLE
|
||||
binding.urlNote.text = video.format.name
|
||||
|
|
|
@ -11,6 +11,7 @@ import ani.dantotsu.connections.comments.Comment
|
|||
import ani.dantotsu.connections.comments.CommentsAPI
|
||||
import ani.dantotsu.copyToClipboard
|
||||
import ani.dantotsu.databinding.ItemCommentsBinding
|
||||
import ani.dantotsu.getAppString
|
||||
import ani.dantotsu.loadImage
|
||||
import ani.dantotsu.others.ImageViewDialog
|
||||
import ani.dantotsu.profile.ProfileActivity
|
||||
|
@ -28,6 +29,7 @@ import kotlinx.coroutines.SupervisorJob
|
|||
import kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sqrt
|
||||
|
@ -52,7 +54,6 @@ class CommentItem(val comment: Comment,
|
|||
adapter.add(repliesSection)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun bind(viewBinding: ItemCommentsBinding, position: Int) {
|
||||
binding = viewBinding
|
||||
setAnimation(binding.root.context, binding.root)
|
||||
|
@ -76,8 +77,15 @@ class CommentItem(val comment: Comment,
|
|||
if ((comment.replyCount ?: 0) > 0) {
|
||||
viewBinding.commentTotalReplies.visibility = View.VISIBLE
|
||||
viewBinding.commentRepliesDivider.visibility = View.VISIBLE
|
||||
viewBinding.commentTotalReplies.text = if(repliesVisible) "Hide Replies" else
|
||||
"View ${comment.replyCount} repl${if (comment.replyCount == 1) "y" else "ies"}"
|
||||
viewBinding.commentTotalReplies.context.run {
|
||||
viewBinding.commentTotalReplies.text = if (repliesVisible)
|
||||
getString(R.string.hide_replies)
|
||||
else
|
||||
if (comment.replyCount == 1)
|
||||
getString(R.string.view_reply)
|
||||
else
|
||||
getString(R.string.view_replies_count, comment.replyCount)
|
||||
}
|
||||
} else {
|
||||
viewBinding.commentTotalReplies.visibility = View.GONE
|
||||
viewBinding.commentRepliesDivider.visibility = View.GONE
|
||||
|
@ -87,10 +95,15 @@ class CommentItem(val comment: Comment,
|
|||
if (repliesVisible) {
|
||||
repliesSection.clear()
|
||||
removeSubCommentIds()
|
||||
viewBinding.commentTotalReplies.text = "View ${comment.replyCount} repl${if (comment.replyCount == 1) "y" else "ies"}"
|
||||
viewBinding.commentTotalReplies.context.run {
|
||||
viewBinding.commentTotalReplies.text = if (comment.replyCount == 1)
|
||||
getString(R.string.view_reply)
|
||||
else
|
||||
getString(R.string.view_replies_count, comment.replyCount)
|
||||
}
|
||||
repliesVisible = false
|
||||
} else {
|
||||
viewBinding.commentTotalReplies.text = "Hide Replies"
|
||||
viewBinding.commentTotalReplies.setText(R.string.hide_replies)
|
||||
repliesSection.clear()
|
||||
commentsFragment.viewReplyCallback(this)
|
||||
repliesVisible = true
|
||||
|
@ -128,35 +141,37 @@ class CommentItem(val comment: Comment,
|
|||
viewBinding.modBadge.visibility = if (comment.isMod == true) View.VISIBLE else View.GONE
|
||||
viewBinding.adminBadge.visibility = if (comment.isAdmin == true) View.VISIBLE else View.GONE
|
||||
viewBinding.commentDelete.setOnClickListener {
|
||||
dialogBuilder("Delete Comment", "Are you sure you want to delete this comment?") {
|
||||
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
scope.launch {
|
||||
dialogBuilder(getAppString(R.string.delete_comment), getAppString(R.string.delete_comment_confirm)) {
|
||||
CoroutineScope(Dispatchers.Main + SupervisorJob()).launch {
|
||||
val success = CommentsAPI.deleteComment(comment.commentId)
|
||||
if (success) {
|
||||
snackString("Comment Deleted")
|
||||
snackString(R.string.comment_deleted)
|
||||
parentSection.remove(this@CommentItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
viewBinding.commentBanUser.setOnClickListener {
|
||||
dialogBuilder("Ban User", "Are you sure you want to ban this user?") {
|
||||
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
scope.launch {
|
||||
dialogBuilder(getAppString(R.string.ban_user), getAppString(R.string.ban_user_confirm)) {
|
||||
CoroutineScope(Dispatchers.Main + SupervisorJob()).launch {
|
||||
val success = CommentsAPI.banUser(comment.userId)
|
||||
if (success) {
|
||||
snackString("User Banned")
|
||||
snackString(R.string.user_banned)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
viewBinding.commentReport.setOnClickListener {
|
||||
dialogBuilder("Report Comment", "Only report comments that violate the rules. Are you sure you want to report this comment?") {
|
||||
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
scope.launch {
|
||||
val success = CommentsAPI.reportComment(comment.commentId, comment.username, commentsFragment.mediaName, comment.userId)
|
||||
dialogBuilder(getAppString(R.string.report_comment), getAppString(R.string.report_comment_confirm)) {
|
||||
CoroutineScope(Dispatchers.Main + SupervisorJob()).launch {
|
||||
val success = CommentsAPI.reportComment(
|
||||
comment.commentId,
|
||||
comment.username,
|
||||
commentsFragment.mediaName,
|
||||
comment.userId
|
||||
)
|
||||
if (success) {
|
||||
snackString("Comment Reported")
|
||||
snackString(R.string.comment_reported)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +225,8 @@ class CommentItem(val comment: Comment,
|
|||
}
|
||||
comment.profilePictureUrl?.let { viewBinding.commentUserAvatar.loadImage(it) }
|
||||
viewBinding.commentUserName.text = comment.username
|
||||
viewBinding.commentUserLevel.text = "[${levelColor.second}]"
|
||||
val userColor = "[${levelColor.second}]"
|
||||
viewBinding.commentUserLevel.text = userColor
|
||||
viewBinding.commentUserLevel.setTextColor(levelColor.first)
|
||||
viewBinding.commentUserTime.text = formatTimestamp(comment.timestamp)
|
||||
}
|
||||
|
@ -243,6 +259,7 @@ class CommentItem(val comment: Comment,
|
|||
|
||||
private fun removeSubCommentIds(){
|
||||
subCommentIds.forEach { id ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val parentComments = parentSection.groups as? List<CommentItem> ?: emptyList()
|
||||
val commentToRemove = parentComments.find { it.comment.commentId == id }
|
||||
commentToRemove?.let {
|
||||
|
@ -272,9 +289,10 @@ class CommentItem(val comment: Comment,
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private fun formatTimestamp(timestamp: String): String {
|
||||
return try {
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT)
|
||||
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||
val parsedDate = dateFormat.parse(timestamp)
|
||||
val currentDate = Date()
|
||||
|
@ -297,8 +315,9 @@ class CommentItem(val comment: Comment,
|
|||
}
|
||||
|
||||
companion object {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun timestampToMillis(timestamp: String): Long {
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT)
|
||||
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||
val parsedDate = dateFormat.parse(timestamp)
|
||||
return parsedDate?.time ?: 0
|
||||
|
|
|
@ -523,11 +523,10 @@ class CommentsFragment : Fragment() {
|
|||
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun replyTo(comment: CommentItem, username: String) {
|
||||
if (comment.isReplying) {
|
||||
activity.binding.commentReplyToContainer.visibility = View.VISIBLE
|
||||
activity.binding.commentReplyTo.text = "Replying to $username"
|
||||
activity.binding.commentReplyTo.text = getString(R.string.replying_to, username)
|
||||
activity.binding.commentReplyToCancel.setOnClickListener {
|
||||
comment.replying(false)
|
||||
replyCallback(comment)
|
||||
|
|
|
@ -2,7 +2,6 @@ package ani.dantotsu.media.manga
|
|||
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
|
@ -10,8 +9,8 @@ import android.os.Build
|
|||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.LruCache
|
||||
import ani.dantotsu.util.Logger
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -25,15 +24,13 @@ data class ImageData(
|
|||
) {
|
||||
suspend fun fetchAndProcessImage(
|
||||
page: Page,
|
||||
httpSource: HttpSource,
|
||||
context: Context
|
||||
httpSource: HttpSource
|
||||
): Bitmap? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
// Fetch the image
|
||||
val response = httpSource.getImage(page)
|
||||
Logger.log("Response: ${response.code}")
|
||||
Logger.log("Response: ${response.message}")
|
||||
Logger.log("Response: ${response.code} - ${response.message}")
|
||||
|
||||
// Convert the Response to an InputStream
|
||||
val inputStream = response.body.byteStream()
|
||||
|
|
|
@ -262,7 +262,6 @@ class MangaChapterAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is ChapterCompactViewHolder -> {
|
||||
|
|
|
@ -5,8 +5,8 @@ import java.util.regex.Pattern
|
|||
|
||||
class MangaNameAdapter {
|
||||
companion object {
|
||||
const val chapterRegex = "(chapter|chap|ch|c)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*"
|
||||
const val filedChapterNumberRegex = "(?<!part\\s)\\b(\\d+)\\b"
|
||||
private const val chapterRegex = "(chapter|chap|ch|c)[\\s:.\\-]*(\\d+\\.?\\d*)[\\s:.\\-]*"
|
||||
private const val filedChapterNumberRegex = "(?<!part\\s)\\b(\\d+)\\b"
|
||||
fun findChapterNumber(text: String): Float? {
|
||||
val pattern: Pattern = Pattern.compile(chapterRegex, Pattern.CASE_INSENSITIVE)
|
||||
val matcher: Matcher = pattern.matcher(text)
|
||||
|
|
|
@ -13,6 +13,8 @@ import android.widget.LinearLayout
|
|||
import android.widget.NumberPicker
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import ani.dantotsu.*
|
||||
|
@ -58,7 +60,6 @@ class MangaReadAdapter(
|
|||
|
||||
private var nestedDialog: AlertDialog? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val binding = holder.binding
|
||||
_binding = binding
|
||||
|
@ -77,14 +78,12 @@ class MangaReadAdapter(
|
|||
null
|
||||
)
|
||||
}
|
||||
val offline =
|
||||
if (!isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
|
||||
) View.GONE else View.VISIBLE
|
||||
val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
|
||||
|
||||
binding.animeSourceNameContainer.visibility = offline
|
||||
binding.animeSourceSettings.visibility = offline
|
||||
binding.animeSourceSearch.visibility = offline
|
||||
binding.animeSourceTitle.visibility = offline
|
||||
binding.animeSourceNameContainer.isGone = offline
|
||||
binding.animeSourceSettings.isGone = offline
|
||||
binding.animeSourceSearch.isGone = offline
|
||||
binding.animeSourceTitle.isGone = offline
|
||||
//Source Selection
|
||||
var source =
|
||||
media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it }
|
||||
|
@ -188,8 +187,8 @@ class MangaReadAdapter(
|
|||
else -> dialogBinding.animeSourceList
|
||||
}
|
||||
when (style) {
|
||||
0 -> dialogBinding.layoutText.text = "List"
|
||||
1 -> dialogBinding.layoutText.text = "Compact"
|
||||
0 -> dialogBinding.layoutText.setText(R.string.list)
|
||||
1 -> dialogBinding.layoutText.setText(R.string.compact)
|
||||
else -> dialogBinding.animeSourceList
|
||||
}
|
||||
selected.alpha = 1f
|
||||
|
@ -201,18 +200,18 @@ class MangaReadAdapter(
|
|||
dialogBinding.animeSourceList.setOnClickListener {
|
||||
selected(it as ImageButton)
|
||||
style = 0
|
||||
dialogBinding.layoutText.text = "List"
|
||||
dialogBinding.layoutText.setText(R.string.list)
|
||||
run = true
|
||||
}
|
||||
dialogBinding.animeSourceCompact.setOnClickListener {
|
||||
selected(it as ImageButton)
|
||||
style = 1
|
||||
dialogBinding.layoutText.text = "Compact"
|
||||
dialogBinding.layoutText.setText(R.string.compact)
|
||||
run = true
|
||||
}
|
||||
dialogBinding.animeWebviewContainer.setOnClickListener {
|
||||
if (!WebViewUtil.supportsWebView(fragment.requireContext())) {
|
||||
toast("WebView not installed")
|
||||
toast(R.string.webview_not_installed)
|
||||
}
|
||||
//start CookieCatcher activity
|
||||
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
|
||||
|
@ -249,8 +248,7 @@ class MangaReadAdapter(
|
|||
}
|
||||
|
||||
//Scanlator
|
||||
dialogBinding.animeScanlatorContainer.visibility =
|
||||
if (options.count() > 1) View.VISIBLE else View.GONE
|
||||
dialogBinding.animeScanlatorContainer.isVisible = options.count() > 1
|
||||
dialogBinding.scanlatorNo.text = "${options.count()}"
|
||||
dialogBinding.animeScanlatorTop.setOnClickListener {
|
||||
val dialogView2 = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
|
||||
|
@ -359,7 +357,6 @@ class MangaReadAdapter(
|
|||
}
|
||||
|
||||
//Chips
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun updateChips(limit: Int, names: Array<String>, arr: Array<Int>, selected: Int = 0) {
|
||||
val binding = _binding
|
||||
if (binding != null) {
|
||||
|
@ -395,7 +392,8 @@ class MangaReadAdapter(
|
|||
names[last - 1]
|
||||
}
|
||||
//chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
|
||||
chip.text = "$startChapterString - $endChapterString"
|
||||
val chipText = "$startChapterString - $endChapterString"
|
||||
chip.text = chipText
|
||||
chip.setTextColor(
|
||||
ContextCompat.getColorStateList(
|
||||
fragment.requireContext(),
|
||||
|
@ -429,7 +427,6 @@ class MangaReadAdapter(
|
|||
_binding?.animeSourceChipGroup?.removeAllViews()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun handleChapters() {
|
||||
|
||||
val binding = _binding
|
||||
|
@ -466,7 +463,7 @@ class MangaReadAdapter(
|
|||
val ep = media.manga.chapters!![continueEp]!!
|
||||
binding.itemEpisodeImage.loadImage(media.banner ?: media.cover)
|
||||
binding.animeSourceContinueText.text =
|
||||
currActivity()!!.getString(R.string.continue_chapter) + "${ep.number}${if (!ep.title.isNullOrEmpty()) "\n${ep.title}" else ""}"
|
||||
currActivity()!!.getString(R.string.continue_chapter, ep.number, if (!ep.title.isNullOrEmpty()) ep.title else "")
|
||||
binding.animeSourceContinue.setOnClickListener {
|
||||
fragment.onMangaChapterClick(continueEp)
|
||||
}
|
||||
|
@ -481,13 +478,9 @@ class MangaReadAdapter(
|
|||
binding.animeSourceContinue.visibility = View.GONE
|
||||
}
|
||||
binding.animeSourceProgressBar.visibility = View.GONE
|
||||
if (media.manga.chapters!!.isNotEmpty()) {
|
||||
binding.animeSourceNotFound.visibility = View.GONE
|
||||
binding.faqbutton.visibility = View.GONE
|
||||
} else {
|
||||
binding.animeSourceNotFound.visibility = View.VISIBLE
|
||||
binding.faqbutton.visibility = View.VISIBLE
|
||||
}
|
||||
val sourceFound = media.manga.chapters!!.isNotEmpty()
|
||||
binding.animeSourceNotFound.isGone = sourceFound
|
||||
binding.faqbutton.isGone = sourceFound
|
||||
} else {
|
||||
binding.animeSourceContinue.visibility = View.GONE
|
||||
binding.animeSourceNotFound.visibility = View.GONE
|
||||
|
@ -519,8 +512,7 @@ class MangaReadAdapter(
|
|||
parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) }
|
||||
)
|
||||
val items = adapter.count
|
||||
binding?.animeSourceLanguageContainer?.visibility =
|
||||
if (items > 1) View.VISIBLE else View.GONE
|
||||
binding?.animeSourceLanguageContainer?.isVisible = items > 1
|
||||
|
||||
binding?.animeSourceLanguage?.setAdapter(adapter)
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ import androidx.cardview.widget.CardView
|
|||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.math.MathUtils.clamp
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
@ -28,21 +30,25 @@ import androidx.recyclerview.widget.ConcatAdapter
|
|||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.databinding.FragmentAnimeWatchBinding
|
||||
import ani.dantotsu.download.DownloadedType
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.download.manga.MangaDownloaderService
|
||||
import ani.dantotsu.download.manga.MangaServiceDataSingleton
|
||||
import ani.dantotsu.dp
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsActivity
|
||||
import ani.dantotsu.media.MediaDetailsViewModel
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.others.LanguageMapper
|
||||
import ani.dantotsu.parsers.DynamicMangaParser
|
||||
import ani.dantotsu.parsers.HMangaSources
|
||||
import ani.dantotsu.parsers.MangaParser
|
||||
import ani.dantotsu.parsers.MangaSources
|
||||
import ani.dantotsu.setNavigationTheme
|
||||
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
|
@ -354,14 +360,12 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
||||
val activity = activity
|
||||
if (activity is MediaDetailsActivity && isAdded) {
|
||||
val visibility = if (show) View.VISIBLE else View.GONE
|
||||
activity.findViewById<AppBarLayout>(R.id.mediaAppBar).visibility = visibility
|
||||
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility
|
||||
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility
|
||||
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility
|
||||
activity.tabLayout.setVisibility(visibility)
|
||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||
if (show) View.GONE else View.VISIBLE
|
||||
activity.findViewById<AppBarLayout>(R.id.mediaAppBar).isVisible = show
|
||||
activity.findViewById<ViewPager2>(R.id.mediaViewPager).isVisible = show
|
||||
activity.findViewById<CardView>(R.id.mediaCover).isVisible = show
|
||||
activity.findViewById<CardView>(R.id.mediaClose).isVisible = show
|
||||
activity.navBar.isVisible = show
|
||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).isGone = show
|
||||
}
|
||||
}
|
||||
var itemSelected = false
|
||||
|
@ -492,7 +496,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
DownloadedType(
|
||||
media.mainName(),
|
||||
i,
|
||||
DownloadedType.Type.MANGA
|
||||
MediaType.MANGA
|
||||
)
|
||||
)
|
||||
chapterAdapter.deleteDownload(i)
|
||||
|
@ -510,7 +514,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
|||
DownloadedType(
|
||||
media.mainName(),
|
||||
i,
|
||||
DownloadedType.Type.MANGA
|
||||
MediaType.MANGA
|
||||
)
|
||||
)
|
||||
chapterAdapter.purgeDownload(i)
|
||||
|
|
|
@ -13,10 +13,14 @@ import androidx.core.view.GestureDetectorCompat
|
|||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.FileUrl
|
||||
import ani.dantotsu.GesturesListener
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.media.manga.MangaCache
|
||||
import ani.dantotsu.media.manga.MangaChapter
|
||||
import ani.dantotsu.px
|
||||
import ani.dantotsu.settings.CurrentReaderSettings
|
||||
import ani.dantotsu.tryWithSuspend
|
||||
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
|
@ -118,13 +122,13 @@ abstract class BaseImageAdapter(
|
|||
abstract suspend fun loadImage(position: Int, parent: View): Boolean
|
||||
|
||||
companion object {
|
||||
suspend fun Context.loadBitmap_old(
|
||||
suspend fun Context.loadBitmapOld(
|
||||
link: FileUrl,
|
||||
transforms: List<BitmapTransformation>
|
||||
): Bitmap? { //still used in some places
|
||||
return tryWithSuspend {
|
||||
withContext(Dispatchers.IO) {
|
||||
Glide.with(this@loadBitmap_old)
|
||||
Glide.with(this@loadBitmapOld)
|
||||
.asBitmap()
|
||||
.let {
|
||||
if (link.url.startsWith("file://")) {
|
||||
|
@ -168,8 +172,7 @@ abstract class BaseImageAdapter(
|
|||
mangaCache.get(link.url)?.let { imageData ->
|
||||
val bitmap = imageData.fetchAndProcessImage(
|
||||
imageData.page,
|
||||
imageData.source,
|
||||
context = this@loadBitmap
|
||||
imageData.source
|
||||
)
|
||||
it.load(bitmap)
|
||||
.skipMemoryCache(true)
|
||||
|
|
|
@ -10,8 +10,19 @@ import android.content.res.Resources
|
|||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.KeyEvent.*
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.KeyEvent
|
||||
import android.view.KeyEvent.ACTION_DOWN
|
||||
import android.view.KeyEvent.KEYCODE_DPAD_DOWN
|
||||
import android.view.KeyEvent.KEYCODE_DPAD_UP
|
||||
import android.view.KeyEvent.KEYCODE_PAGE_DOWN
|
||||
import android.view.KeyEvent.KEYCODE_PAGE_UP
|
||||
import android.view.KeyEvent.KEYCODE_VOLUME_DOWN
|
||||
import android.view.KeyEvent.KEYCODE_VOLUME_UP
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.AdapterView
|
||||
import android.widget.CheckBox
|
||||
|
@ -20,13 +31,16 @@ import androidx.activity.viewModels
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.math.MathUtils.clamp
|
||||
import androidx.core.view.GestureDetectorCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.PagerSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.GesturesListener
|
||||
import ani.dantotsu.NoPaddingArrayAdapter
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.connections.anilist.Anilist
|
||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||
import ani.dantotsu.connections.discord.Discord
|
||||
|
@ -34,7 +48,12 @@ import ani.dantotsu.connections.discord.DiscordService
|
|||
import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton
|
||||
import ani.dantotsu.connections.discord.RPC
|
||||
import ani.dantotsu.connections.updateProgress
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.databinding.ActivityMangaReaderBinding
|
||||
import ani.dantotsu.dp
|
||||
import ani.dantotsu.hideSystemBarsExtendView
|
||||
import ani.dantotsu.isOnline
|
||||
import ani.dantotsu.logError
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsViewModel
|
||||
import ani.dantotsu.media.MediaSingleton
|
||||
|
@ -45,14 +64,25 @@ import ani.dantotsu.others.ImageViewDialog
|
|||
import ani.dantotsu.parsers.HMangaSources
|
||||
import ani.dantotsu.parsers.MangaImage
|
||||
import ani.dantotsu.parsers.MangaSources
|
||||
import ani.dantotsu.px
|
||||
import ani.dantotsu.setSafeOnClickListener
|
||||
import ani.dantotsu.settings.CurrentReaderSettings
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Companion.applyWebtoon
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Directions.*
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.*
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.*
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Directions.BOTTOM_TO_TOP
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Directions.LEFT_TO_RIGHT
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Directions.RIGHT_TO_LEFT
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Directions.TOP_TO_BOTTOM
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.Automatic
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.Force
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.No
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.CONTINUOUS_PAGED
|
||||
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.PAGED
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.showSystemBarsRetractView
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.themes.ThemeManager
|
||||
import ani.dantotsu.tryWith
|
||||
import com.alexvasilkov.gestures.views.GestureFrameLayout
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
|
@ -65,11 +95,11 @@ import java.io.FileInputStream
|
|||
import java.io.FileOutputStream
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.util.*
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import kotlin.math.min
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
class MangaReaderActivity : AppCompatActivity() {
|
||||
private val mangaCache = Injekt.get<MangaCache>()
|
||||
|
||||
|
@ -88,7 +118,6 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
|
||||
private var isContVisible = false
|
||||
private var showProgressDialog = true
|
||||
private var hidescrollbar = false
|
||||
|
||||
private var maxChapterPage = 0L
|
||||
private var currentChapterPage = 0L
|
||||
|
@ -123,7 +152,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun hideSystemBars() {
|
||||
if (PrefManager.getVal<Boolean>(PrefName.ShowSystemBars))
|
||||
if (PrefManager.getVal(PrefName.ShowSystemBars))
|
||||
showSystemBarsRetractView()
|
||||
else
|
||||
hideSystemBarsExtendView()
|
||||
|
@ -223,8 +252,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
chapter = chapters[media.manga!!.selectedChapter] ?: return
|
||||
|
||||
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
|
||||
binding.mangaReaderSource.visibility =
|
||||
if (PrefManager.getVal(PrefName.ShowSource)) View.VISIBLE else View.GONE
|
||||
binding.mangaReaderSource.isVisible = PrefManager.getVal(PrefName.ShowSource)
|
||||
if (model.mangaReadSources!!.names.isEmpty()) {
|
||||
//try to reload sources
|
||||
try {
|
||||
|
@ -369,7 +397,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""),
|
||||
RPC.Link(
|
||||
"Stream on Dantotsu",
|
||||
"https://github.com/rebelonion/Dantotsu/"
|
||||
getString(R.string.github)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -741,12 +769,12 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
goneTimer.schedule(timerTask, controllerDuration)
|
||||
}
|
||||
|
||||
enum class pressPos {
|
||||
enum class PressPos {
|
||||
LEFT, RIGHT, CENTER
|
||||
}
|
||||
|
||||
fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) {
|
||||
var pressLocation = pressPos.CENTER
|
||||
var pressLocation = PressPos.CENTER
|
||||
if (!sliding) {
|
||||
if (event != null && defaultSettings.layout == PAGED) {
|
||||
if (event.action != MotionEvent.ACTION_UP) return
|
||||
|
@ -756,23 +784,23 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
//if in the 1st 1/5th of the screen width, left and lower than 1/5th of the screen height, left
|
||||
if (screenWidth / 5 in x + 1..<y) {
|
||||
pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
|
||||
pressPos.RIGHT
|
||||
PressPos.RIGHT
|
||||
} else {
|
||||
pressPos.LEFT
|
||||
PressPos.LEFT
|
||||
}
|
||||
}
|
||||
//if in the last 1/5th of the screen width, right and lower than 1/5th of the screen height, right
|
||||
else if (x > screenWidth - screenWidth / 5 && y > screenWidth / 5) {
|
||||
pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
|
||||
pressPos.LEFT
|
||||
PressPos.LEFT
|
||||
} else {
|
||||
pressPos.RIGHT
|
||||
PressPos.RIGHT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if pressLocation is left or right go to previous or next page (paged mode only)
|
||||
if (pressLocation == pressPos.LEFT) {
|
||||
if (pressLocation == PressPos.LEFT) {
|
||||
|
||||
if (binding.mangaReaderPager.currentItem > 0) {
|
||||
//if the current images zoomed in, go back to normal before going to previous page
|
||||
|
@ -783,7 +811,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
return
|
||||
}
|
||||
|
||||
} else if (pressLocation == pressPos.RIGHT) {
|
||||
} else if (pressLocation == PressPos.RIGHT) {
|
||||
if (binding.mangaReaderPager.currentItem < maxChapterPage - 1) {
|
||||
//if the current images zoomed in, go back to normal before going to next page
|
||||
if (imageAdapter?.isZoomed() == true) {
|
||||
|
@ -961,7 +989,7 @@ class MangaReaderActivity : AppCompatActivity() {
|
|||
if (!incognito && PrefManager.getCustomVal(
|
||||
"${media.id}_save_progress",
|
||||
true
|
||||
) && if (media.isAdult) PrefManager.getVal<Boolean>(PrefName.UpdateForHReader) else true
|
||||
) && if (media.isAdult) PrefManager.getVal(PrefName.UpdateForHReader) else true
|
||||
)
|
||||
updateProgress(
|
||||
media,
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.os.Environment
|
|||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -28,6 +27,7 @@ import ani.dantotsu.download.novel.NovelDownloaderService
|
|||
import ani.dantotsu.download.novel.NovelServiceDataSingleton
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.MediaDetailsViewModel
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.novel.novelreader.NovelReaderActivity
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.parsers.ShowResponse
|
||||
|
@ -90,7 +90,7 @@ class NovelReadFragment : Fragment(),
|
|||
DownloadedType(
|
||||
media.mainName(),
|
||||
novel.name,
|
||||
DownloadedType.Type.NOVEL
|
||||
MediaType.NOVEL
|
||||
)
|
||||
)
|
||||
) {
|
||||
|
@ -122,7 +122,7 @@ class NovelReadFragment : Fragment(),
|
|||
DownloadedType(
|
||||
media.mainName(),
|
||||
novel.name,
|
||||
DownloadedType.Type.NOVEL
|
||||
MediaType.NOVEL
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ class NovelReadFragment : Fragment(),
|
|||
DownloadedType(
|
||||
media.mainName(),
|
||||
novel.name,
|
||||
DownloadedType.Type.NOVEL
|
||||
MediaType.NOVEL
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.databinding.ItemNovelResponseBinding
|
||||
|
@ -71,8 +72,7 @@ class NovelResponseAdapter(
|
|||
}
|
||||
binding.itemEpisodeDesc2.text = novel.extra?.get("1") ?: ""
|
||||
val desc = novel.extra?.get("2")
|
||||
binding.itemEpisodeDesc.visibility =
|
||||
if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE
|
||||
binding.itemEpisodeDesc.isVisible = !desc.isNullOrBlank()
|
||||
binding.itemEpisodeDesc.text = desc ?: ""
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
|
|
|
@ -31,7 +31,6 @@ class UrlAdapter(
|
|||
)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: UrlViewHolder, position: Int) {
|
||||
val binding = holder.binding
|
||||
val url = urls[position]
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.util.TypedValue
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
|
@ -17,7 +16,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import ani.dantotsu.R
|
||||
import ani.dantotsu.Refresh
|
||||
import ani.dantotsu.databinding.ActivityListBinding
|
||||
import ani.dantotsu.navBarHeight
|
||||
import ani.dantotsu.hideSystemBarsExtendView
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.statusBarHeight
|
||||
|
@ -33,7 +32,6 @@ class ListActivity : AppCompatActivity() {
|
|||
private val scope = lifecycleScope
|
||||
private var selectedTabIdx = 0
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -72,10 +70,7 @@ class ListActivity : AppCompatActivity() {
|
|||
} else {
|
||||
binding.root.fitsSystemWindows = false
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
)
|
||||
hideSystemBarsExtendView()
|
||||
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = statusBarHeight
|
||||
}
|
||||
|
@ -83,8 +78,8 @@ class ListActivity : AppCompatActivity() {
|
|||
setContentView(binding.root)
|
||||
|
||||
val anime = intent.getBooleanExtra("anime", true)
|
||||
binding.listTitle.text =
|
||||
intent.getStringExtra("username") + "'s " + (if (anime) "Anime" else "Manga") + " List"
|
||||
binding.listTitle.text = getString(R.string.user_list, intent.getStringExtra("username"),
|
||||
if (anime) getString(R.string.anime) else getString(R.string.manga))
|
||||
binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||
this@ListActivity.selectedTabIdx = tab?.position ?: 0
|
||||
|
|
|
@ -11,7 +11,7 @@ class AndroidBug5497Workaround private constructor(activity: Activity, private v
|
|||
private val frameLayoutParams: FrameLayout.LayoutParams
|
||||
|
||||
init {
|
||||
val content = activity.findViewById(android.R.id.content) as FrameLayout
|
||||
val content: FrameLayout = activity.findViewById(android.R.id.content)
|
||||
mChildOfContent = content.getChildAt(0)
|
||||
mChildOfContent.viewTreeObserver.addOnGlobalLayoutListener { possiblyResizeChildOfContent() }
|
||||
frameLayoutParams = mChildOfContent.layoutParams as FrameLayout.LayoutParams
|
||||
|
@ -42,9 +42,15 @@ class AndroidBug5497Workaround private constructor(activity: Activity, private v
|
|||
return r.bottom
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes windowSoftInputMode adjustResize when used with setDecorFitsSystemWindows(false)
|
||||
*
|
||||
* @see <a href="https://issuetracker.google.com/issues/36911528">adjustResize breaks when activity is fullscreen </a>
|
||||
*/
|
||||
companion object {
|
||||
// For more information, see https://issuetracker.google.com/issues/36911528
|
||||
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
|
||||
/**
|
||||
* Called on an Activity after the content view has been set.
|
||||
*/
|
||||
fun assistActivity(activity: Activity, callback: (Boolean) -> Unit) {
|
||||
AndroidBug5497Workaround(activity, callback)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import ani.dantotsu.R
|
|||
import ani.dantotsu.databinding.BottomSheetImageBinding
|
||||
import ani.dantotsu.downloadsPermission
|
||||
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap
|
||||
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old
|
||||
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmapOld
|
||||
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap
|
||||
import ani.dantotsu.openLinkInBrowser
|
||||
import ani.dantotsu.saveImageToDownloads
|
||||
|
@ -84,9 +84,9 @@ class ImageViewDialog : BottomSheetDialogFragment() {
|
|||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
val binding = _binding ?: return@launch
|
||||
|
||||
var bitmap = context.loadBitmap_old(image, trans1 ?: listOf())
|
||||
var bitmap = context.loadBitmapOld(image, trans1 ?: listOf())
|
||||
var bitmap2 =
|
||||
if (image2 != null) context.loadBitmap_old(image2, trans2 ?: listOf()) else null
|
||||
if (image2 != null) context.loadBitmapOld(image2, trans2 ?: listOf()) else null
|
||||
if (bitmap == null) {
|
||||
bitmap = context.loadBitmap(image, trans1 ?: listOf())
|
||||
bitmap2 =
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package ani.dantotsu.others
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import ani.dantotsu.R
|
||||
|
||||
|
@ -54,14 +56,14 @@ class OutlineTextView : AppCompatTextView {
|
|||
setStrokeWidth(strokeWidth)
|
||||
}
|
||||
|
||||
private val Float.toPx get() = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics
|
||||
)
|
||||
|
||||
private fun setStrokeWidth(width: Float) {
|
||||
strokeWidth = width.toPx(context)
|
||||
strokeWidth = width.toPx
|
||||
}
|
||||
|
||||
private fun Float.toPx(context: Context) =
|
||||
(this * context.resources.displayMetrics.scaledDensity + 0.5F)
|
||||
|
||||
override fun invalidate() {
|
||||
if (isDrawing) return
|
||||
super.invalidate()
|
||||
|
|
|
@ -12,19 +12,19 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import ani.dantotsu.R
|
||||
import ani.dantotsu.themes.ThemeManager
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class CookieCatcher : AppCompatActivity() {
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ThemeManager(this).applyTheme()
|
||||
|
||||
//get url from intent
|
||||
val url = intent.getStringExtra("url") ?: "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
val headers: Map<String, String> = intent.getSerializableExtra("headers") as? Map<String, String> ?: emptyMap()
|
||||
val url = intent.getStringExtra("url") ?: getString(R.string.cursed_yt)
|
||||
val headers: Map<String, String> = intent.getSerializableExtraCompat("headers") as? Map<String, String> ?: emptyMap()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val process = Application.getProcessName()
|
||||
|
|
|
@ -11,11 +11,11 @@ import android.os.Environment
|
|||
import android.provider.MediaStore
|
||||
import ani.dantotsu.FileUrl
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.util.Logger
|
||||
import ani.dantotsu.media.anime.AnimeNameAdapter
|
||||
import ani.dantotsu.media.manga.ImageData
|
||||
import ani.dantotsu.media.manga.MangaCache
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||
|
@ -59,8 +59,6 @@ class AniyomiAdapter {
|
|||
fun aniyomiToAnimeParser(extension: AnimeExtension.Installed): DynamicAnimeParser {
|
||||
return DynamicAnimeParser(extension)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
||||
|
@ -192,7 +190,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||
// Group by season, sort within each season, and then renumber while keeping episode number 0 as is
|
||||
val seasonGroups =
|
||||
res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 }
|
||||
seasonGroups.keys.sortedBy { it.toInt() }
|
||||
seasonGroups.keys.sortedBy { it }
|
||||
.flatMap { season ->
|
||||
seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode ->
|
||||
if (episode.episode_number != 0f) { // Skip renumbering for episode number 0
|
||||
|
@ -209,7 +207,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
return sortedEpisodes.map { SEpisodeToEpisode(it) }
|
||||
return sortedEpisodes.map { sEpisodeToEpisode(it) }
|
||||
} catch (e: Exception) {
|
||||
Logger.log("Exception: $e")
|
||||
}
|
||||
|
@ -244,7 +242,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||
|
||||
return try {
|
||||
val videos = source.getVideoList(sEpisode)
|
||||
videos.map { VideoToVideoServer(it) }
|
||||
videos.map { videoToVideoServer(it) }
|
||||
} catch (e: Exception) {
|
||||
Logger.log("Exception occurred: ${e.message}")
|
||||
emptyList()
|
||||
|
@ -296,7 +294,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun SEpisodeToEpisode(sEpisode: SEpisode): Episode {
|
||||
private fun sEpisodeToEpisode(sEpisode: SEpisode): Episode {
|
||||
//if the float episode number is a whole number, convert it to an int
|
||||
val episodeNumberInt =
|
||||
if (sEpisode.episode_number % 1 == 0f) {
|
||||
|
@ -324,7 +322,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
|||
)
|
||||
}
|
||||
|
||||
private fun VideoToVideoServer(video: Video): VideoServer {
|
||||
private fun videoToVideoServer(video: Video): VideoServer {
|
||||
return VideoServer(
|
||||
video.quality,
|
||||
video.url,
|
||||
|
@ -363,7 +361,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||
return try {
|
||||
val res = source.getChapterList(sManga)
|
||||
val reversedRes = res.reversed()
|
||||
val chapterList = reversedRes.map { SChapterToMangaChapter(it) }
|
||||
val chapterList = reversedRes.map { sChapterToMangaChapter(it) }
|
||||
Logger.log("chapterList size: ${chapterList.size}")
|
||||
Logger.log("chapterList: ${chapterList[1].title}")
|
||||
Logger.log("chapterList: ${chapterList[1].description}")
|
||||
|
@ -382,7 +380,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||
sourceLanguage = 0
|
||||
extension.sources[sourceLanguage]
|
||||
} as? HttpSource ?: return emptyList()
|
||||
var imageDataList: List<ImageData> = listOf()
|
||||
val imageDataList: MutableList<ImageData> = mutableListOf()
|
||||
val ret = coroutineScope {
|
||||
try {
|
||||
Logger.log("source.name " + source.name)
|
||||
|
@ -632,7 +630,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
|
|||
}
|
||||
|
||||
|
||||
private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter {
|
||||
private fun sChapterToMangaChapter(sChapter: SChapter): MangaChapter {
|
||||
return MangaChapter(
|
||||
sChapter.name,
|
||||
sChapter.url,
|
||||
|
@ -676,8 +674,8 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||
get() = videoServer
|
||||
|
||||
override suspend fun extract(): VideoContainer {
|
||||
val vidList = listOfNotNull(videoServer.video?.let { AniVideoToSaiVideo(it) })
|
||||
val subList = videoServer.video?.subtitleTracks?.map { TrackToSubtitle(it) } ?: emptyList()
|
||||
val vidList = listOfNotNull(videoServer.video?.let { aniVideoToSaiVideo(it) })
|
||||
val subList = videoServer.video?.subtitleTracks?.map { trackToSubtitle(it) } ?: emptyList()
|
||||
|
||||
return if (vidList.isNotEmpty()) {
|
||||
VideoContainer(vidList, subList)
|
||||
|
@ -686,7 +684,7 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun AniVideoToSaiVideo(aniVideo: Video): ani.dantotsu.parsers.Video {
|
||||
private fun aniVideoToSaiVideo(aniVideo: Video): ani.dantotsu.parsers.Video {
|
||||
// Find the number value from the .quality string
|
||||
val number = Regex("""\d+""").find(aniVideo.quality)?.value?.toInt() ?: 0
|
||||
|
||||
|
@ -789,9 +787,9 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
|
|||
|
||||
}
|
||||
|
||||
private fun TrackToSubtitle(track: Track): Subtitle {
|
||||
private fun trackToSubtitle(track: Track): Subtitle {
|
||||
//use Dispatchers.IO to make a HTTP request to determine the subtitle type
|
||||
var type: SubtitleType? = null
|
||||
var type: SubtitleType?
|
||||
runBlocking {
|
||||
type = findSubtitleType(track.url)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.net.Uri
|
|||
import android.os.Environment
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.download.DownloadsManager
|
||||
import ani.dantotsu.media.MediaType
|
||||
import ani.dantotsu.media.anime.AnimeNameAdapter
|
||||
import ani.dantotsu.tryWithSuspend
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
|
@ -132,16 +133,16 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
|
|||
currContext()?.let {
|
||||
DownloadsManager.getDirectory(
|
||||
it,
|
||||
ani.dantotsu.download.DownloadedType.Type.ANIME,
|
||||
MediaType.ANIME,
|
||||
title,
|
||||
episode
|
||||
).listFiles()?.forEach {
|
||||
if (it.name.contains("subtitle")) {
|
||||
).listFiles()?.forEach { file ->
|
||||
if (file.name.contains("subtitle")) {
|
||||
return listOf(
|
||||
Subtitle(
|
||||
"Downloaded Subtitle",
|
||||
Uri.fromFile(it).toString(),
|
||||
determineSubtitletype(it.absolutePath)
|
||||
Uri.fromFile(file).toString(),
|
||||
determineSubtitletype(file.absolutePath)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ani.dantotsu.parsers.novel
|
||||
|
||||
import android.os.FileObserver
|
||||
import android.util.Log
|
||||
import ani.dantotsu.parsers.novel.FileObserver.fileObserver
|
||||
import ani.dantotsu.util.Logger
|
||||
import java.io.File
|
|
@ -10,7 +10,6 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
|
@ -63,7 +62,7 @@ internal class NovelExtensionInstaller(private val context: Context) {
|
|||
* @param url The url of the apk.
|
||||
* @param extension The extension to install.
|
||||
*/
|
||||
fun downloadAndInstall(url: String, extension: NovelExtension) = Observable.defer {
|
||||
fun downloadAndInstall(url: String, extension: NovelExtension): Observable<InstallStep> = Observable.defer {
|
||||
val pkgName = extension.pkgName
|
||||
|
||||
val oldDownload = activeDownloads[pkgName]
|
||||
|
|
|
@ -5,11 +5,10 @@ import android.content.pm.PackageInfo
|
|||
import android.content.pm.PackageManager.GET_SIGNATURES
|
||||
import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||
import ani.dantotsu.util.Logger
|
||||
import ani.dantotsu.parsers.NovelInterface
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import dalvik.system.PathClassLoader
|
||||
import eu.kanade.tachiyomi.util.lang.Hash
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
@ -134,10 +133,10 @@ internal object NovelExtensionLoader {
|
|||
}
|
||||
Logger.log("isFileWritable: ${file.canWrite()}")
|
||||
val classLoader = PathClassLoader(file.absolutePath, null, context.classLoader)
|
||||
val className =
|
||||
val extensionClassName =
|
||||
"some.random.novelextensions.${className.lowercase(Locale.getDefault())}.$className"
|
||||
val loadedClass = classLoader.loadClass(className)
|
||||
val instance = loadedClass.newInstance()
|
||||
val loadedClass = classLoader.loadClass(extensionClassName)
|
||||
val instance = loadedClass.getDeclaredConstructor().newInstance()
|
||||
val novelInterfaceInstance = instance as? NovelInterface
|
||||
listOfNotNull(novelInterfaceInstance)
|
||||
} catch (e: Exception) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package ani.dantotsu.profile
|
|||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
|
@ -11,6 +12,7 @@ import android.widget.PopupMenu
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updateMargins
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -47,7 +49,6 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||
private var selected: Int = 0
|
||||
private lateinit var navBar: AnimatedBottomBar
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ThemeManager(this).applyTheme()
|
||||
|
@ -56,8 +57,14 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||
setContentView(binding.root)
|
||||
screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
||||
navBar = binding.profileNavBar
|
||||
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarHeight }
|
||||
|
||||
val navBarRightMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0
|
||||
val navBarBottomMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
|
||||
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
rightMargin = navBarRightMargin
|
||||
bottomMargin = navBarBottomMargin
|
||||
}
|
||||
val feedTab = navBar.createTab(R.drawable.ic_round_filter_24, "Feed")
|
||||
val profileTab = navBar.createTab(R.drawable.ic_round_person_24, "Profile")
|
||||
val statsTab = navBar.createTab(R.drawable.ic_stats_24, "Stats")
|
||||
|
@ -105,20 +112,30 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||
val userLevel = intent.getStringExtra("userLVL") ?: ""
|
||||
binding.followButton.visibility =
|
||||
if (user.id == Anilist.userid || Anilist.userid == null) View.GONE else View.VISIBLE
|
||||
binding.followButton.text =
|
||||
if (user.isFollowing) "Unfollow" else if (user.isFollower) "Follows you" else "Follow"
|
||||
if (user.isFollowing && user.isFollower) binding.followButton.text = "Mutual"
|
||||
binding.followButton.text = getString(
|
||||
when {
|
||||
user.isFollowing -> R.string.unfollow
|
||||
user.isFollower -> R.string.follows_you
|
||||
else -> R.string.follow
|
||||
}
|
||||
)
|
||||
if (user.isFollowing && user.isFollower) binding.followButton.text = getString(R.string.mutual)
|
||||
binding.followButton.setOnClickListener {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val res = Anilist.query.toggleFollow(user.id)
|
||||
if (res?.data?.toggleFollow != null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
snackString("Success")
|
||||
snackString(R.string.success)
|
||||
user.isFollowing = res.data.toggleFollow.isFollowing
|
||||
binding.followButton.text =
|
||||
if (user.isFollowing) "Unfollow" else if (user.isFollower) "Follows you" else "Follow"
|
||||
if (user.isFollowing && user.isFollower) binding.followButton.text =
|
||||
"Mutual"
|
||||
binding.followButton.text = getString(
|
||||
when {
|
||||
user.isFollowing -> R.string.unfollow
|
||||
user.isFollower -> R.string.follows_you
|
||||
else -> R.string.follow
|
||||
}
|
||||
)
|
||||
if (user.isFollowing && user.isFollower)
|
||||
binding.followButton.text = getString(R.string.mutual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +189,8 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||
)
|
||||
}
|
||||
|
||||
binding.profileUserName.text = "${user.name} $userLevel"
|
||||
val userLevelText = "${user.name} $userLevel"
|
||||
binding.profileUserName.text = userLevelText
|
||||
if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.profileBannerImage.pause()
|
||||
blurImage(binding.profileBannerImage, user.bannerImage ?: user.avatar?.medium)
|
||||
binding.profileBannerImage.updateLayoutParams { height += statusBarHeight }
|
||||
|
@ -196,7 +214,7 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||
ContextCompat.startActivity(
|
||||
this@ProfileActivity,
|
||||
Intent(this@ProfileActivity, FollowActivity::class.java)
|
||||
.putExtra("title", "Followers")
|
||||
.putExtra("title", getString(R.string.followers))
|
||||
.putExtra("userId", user.id),
|
||||
null
|
||||
)
|
||||
|
@ -279,6 +297,17 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
|
|||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
val rightMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0
|
||||
val bottomMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
|
||||
val params : ViewGroup.MarginLayoutParams =
|
||||
navBar.layoutParams as ViewGroup.MarginLayoutParams
|
||||
params.updateMargins(right = rightMargin, bottom = bottomMargin)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (this::navBar.isInitialized) {
|
||||
navBar.selectTabAt(selected)
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.webkit.WebResourceRequest
|
|||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
|
@ -33,6 +34,7 @@ import ani.dantotsu.setSlideIn
|
|||
import ani.dantotsu.setSlideUp
|
||||
import ani.dantotsu.util.AniMarkdown.Companion.getFullAniHTML
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
@ -57,7 +59,7 @@ class ProfileFragment : Fragment() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
activity = requireActivity() as ProfileActivity
|
||||
|
||||
user = arguments?.getSerializable("user") as Query.UserProfile
|
||||
user = arguments?.getSerializableCompat<Query.UserProfile>("user") as Query.UserProfile
|
||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
|
||||
model.setData(user.id)
|
||||
}
|
||||
|
@ -101,8 +103,7 @@ class ProfileFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
binding.userInfoContainer.visibility =
|
||||
if (user.about != null) View.VISIBLE else View.GONE
|
||||
binding.userInfoContainer.isVisible = user.about != null
|
||||
|
||||
|
||||
binding.statsEpisodesWatched.text = user.statistics.anime.episodesWatched.toString()
|
||||
|
|
|
@ -20,6 +20,7 @@ import ani.dantotsu.profile.ChartBuilder.Companion.StatType
|
|||
import ani.dantotsu.statusBarHeight
|
||||
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType
|
||||
import com.xwray.groupie.GroupieAdapter
|
||||
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -48,7 +49,7 @@ class StatsFragment :
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
activity = requireActivity() as ProfileActivity
|
||||
user = arguments?.getSerializable("user") as Query.UserProfile
|
||||
user = arguments?.getSerializableCompat<Query.UserProfile>("user") as Query.UserProfile
|
||||
|
||||
binding.statisticList.adapter = adapter
|
||||
binding.statisticList.recycledViewPool.setMaxRecycledViews(0, 0)
|
||||
|
@ -95,7 +96,7 @@ class StatsFragment :
|
|||
}
|
||||
} else {
|
||||
stats.removeAll(
|
||||
stats.filter { it?.id == Anilist.userid }
|
||||
stats.filter { it?.id == Anilist.userid }.toSet()
|
||||
)
|
||||
loadStats(type == MediaType.ANIME)
|
||||
}
|
||||
|
@ -445,6 +446,7 @@ class StatsFragment :
|
|||
}.toMutableList()
|
||||
chartPackets.clear()
|
||||
chartPackets.addAll(standardizedPackets)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val genreChart = ChartBuilder.buildChart(
|
||||
activity,
|
||||
ChartType.TwoDimensional,
|
||||
|
@ -499,6 +501,7 @@ class StatsFragment :
|
|||
}.toMutableList()
|
||||
chartPackets.clear()
|
||||
chartPackets.addAll(standardizedPackets)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val tagChart = ChartBuilder.buildChart(
|
||||
activity,
|
||||
ChartType.TwoDimensional,
|
||||
|
@ -553,6 +556,7 @@ class StatsFragment :
|
|||
}.toMutableList()
|
||||
chartPackets.clear()
|
||||
chartPackets.addAll(standardizedPackets)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val countryChart = ChartBuilder.buildChart(
|
||||
activity,
|
||||
ChartType.OneDimensional,
|
||||
|
@ -609,6 +613,7 @@ class StatsFragment :
|
|||
}.toMutableList()
|
||||
chartPackets.clear()
|
||||
chartPackets.addAll(standardizedPackets)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val voiceActorsChart = ChartBuilder.buildChart(
|
||||
activity,
|
||||
ChartType.TwoDimensional,
|
||||
|
@ -663,6 +668,7 @@ class StatsFragment :
|
|||
}.toMutableList()
|
||||
chartPackets.clear()
|
||||
chartPackets.addAll(standardizedPackets)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val studioChart = ChartBuilder.buildChart(
|
||||
activity,
|
||||
ChartType.TwoDimensional,
|
||||
|
@ -720,6 +726,7 @@ class StatsFragment :
|
|||
}.toMutableList()
|
||||
chartPackets.clear()
|
||||
chartPackets.addAll(standardizedPackets)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val staffChart = ChartBuilder.buildChart(
|
||||
activity,
|
||||
ChartType.TwoDimensional,
|
||||
|
|
|
@ -3,6 +3,7 @@ package ani.dantotsu.profile.activity
|
|||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import ani.dantotsu.R
|
||||
|
@ -38,7 +39,6 @@ class ActivityItem(
|
|||
private lateinit var binding: ItemActivityBinding
|
||||
private lateinit var repliesAdapter: GroupieAdapter
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun bind(viewBinding: ItemActivityBinding, position: Int) {
|
||||
binding = viewBinding
|
||||
setAnimation(binding.root.context, binding.root)
|
||||
|
@ -58,8 +58,7 @@ class ActivityItem(
|
|||
val likeColor = ContextCompat.getColor(binding.root.context, R.color.yt_red)
|
||||
val notLikeColor = ContextCompat.getColor(binding.root.context, R.color.bg_opp)
|
||||
binding.activityLike.setColorFilter(if (activity.isLiked == true) likeColor else notLikeColor)
|
||||
binding.commentRepliesContainer.visibility =
|
||||
if (activity.replyCount > 0) View.VISIBLE else View.GONE
|
||||
binding.commentRepliesContainer.isVisible = activity.replyCount > 0
|
||||
binding.commentRepliesContainer.setOnClickListener {
|
||||
when (binding.activityReplies.visibility) {
|
||||
View.GONE -> {
|
||||
|
@ -73,13 +72,13 @@ class ActivityItem(
|
|||
} ?: emptyList()
|
||||
repliesAdapter.addAll(replyItems)
|
||||
binding.activityReplies.visibility = View.VISIBLE
|
||||
binding.commentTotalReplies.text = "Hide replies"
|
||||
binding.commentTotalReplies.setText(R.string.hide_replies)
|
||||
}
|
||||
|
||||
else -> {
|
||||
repliesAdapter.clear()
|
||||
binding.activityReplies.visibility = View.GONE
|
||||
binding.commentTotalReplies.text = "View replies"
|
||||
binding.commentTotalReplies.setText(R.string.view_replies)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +126,9 @@ class ActivityItem(
|
|||
binding.activityContent.visibility = View.GONE
|
||||
binding.activityBannerContainer.visibility = View.VISIBLE
|
||||
binding.activityMediaName.text = activity.media?.title?.userPreferred
|
||||
binding.activityText.text = "${activity.user!!.name} ${activity.status} ${activity.progress ?: activity.media?.title?.userPreferred}"
|
||||
val activityText = "${activity.user!!.name} ${activity.status} ${activity.progress
|
||||
?: activity.media?.title?.userPreferred}"
|
||||
binding.activityText.text = activityText
|
||||
binding.activityCover.loadImage(cover)
|
||||
blurImage(binding.activityBannerImage, banner ?: cover)
|
||||
binding.activityAvatarContainer.setOnClickListener {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package ani.dantotsu.profile.activity
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updateMargins
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -28,14 +30,16 @@ class FeedActivity : AppCompatActivity() {
|
|||
binding = ActivityFeedBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
navBar = binding.feedNavBar
|
||||
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
|
||||
val navBarMargin = if (resources.configuration.orientation ==
|
||||
Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
|
||||
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarMargin }
|
||||
val personalTab = navBar.createTab(R.drawable.ic_round_person_24, "Following")
|
||||
val globalTab = navBar.createTab(R.drawable.ic_globe_24, "Global")
|
||||
navBar.addTab(personalTab)
|
||||
navBar.addTab(globalTab)
|
||||
binding.listTitle.text = getString(R.string.activities)
|
||||
binding.feedViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
bottomMargin += navBarHeight
|
||||
bottomMargin = navBarMargin
|
||||
topMargin += statusBarHeight
|
||||
}
|
||||
binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
|
||||
|
@ -57,10 +61,20 @@ class FeedActivity : AppCompatActivity() {
|
|||
}
|
||||
})
|
||||
binding.listBack.setOnClickListener {
|
||||
onBackPressed()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
val margin = if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
|
||||
val params : ViewGroup.MarginLayoutParams =
|
||||
binding.feedViewPager.layoutParams as ViewGroup.MarginLayoutParams
|
||||
val paramsNav : ViewGroup.MarginLayoutParams = navBar.layoutParams as ViewGroup.MarginLayoutParams
|
||||
params.updateMargins(bottom = margin)
|
||||
paramsNav.updateMargins(bottom = margin)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
navBar.selectTabAt(selected)
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.core.view.isVisible
|
|||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.connections.anilist.Anilist
|
||||
import ani.dantotsu.connections.anilist.api.Notification
|
||||
import ani.dantotsu.databinding.ActivityFollowBinding
|
||||
|
@ -37,14 +38,14 @@ class NotificationActivity : AppCompatActivity() {
|
|||
private var currentPage: Int = 1
|
||||
private var hasNextPage: Boolean = true
|
||||
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
ThemeManager(this).applyTheme()
|
||||
initActivity(this)
|
||||
binding = ActivityFollowBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
binding.listTitle.text = "Notifications"
|
||||
binding.listTitle.text = getString(R.string.notifications)
|
||||
binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = statusBarHeight
|
||||
}
|
||||
|
@ -57,7 +58,7 @@ class NotificationActivity : AppCompatActivity() {
|
|||
binding.followerGrid.visibility = ViewGroup.GONE
|
||||
binding.followerList.visibility = ViewGroup.GONE
|
||||
binding.listBack.setOnClickListener {
|
||||
onBackPressed()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
binding.listProgressBar.visibility = ViewGroup.VISIBLE
|
||||
val activityId = intent.getIntExtra("activityId", -1)
|
||||
|
|
|
@ -12,6 +12,7 @@ import ani.dantotsu.databinding.ItemNotificationBinding
|
|||
import ani.dantotsu.loadImage
|
||||
import ani.dantotsu.profile.activity.NotificationActivity.Companion.NotificationClickType
|
||||
import ani.dantotsu.setAnimation
|
||||
import ani.dantotsu.toPx
|
||||
import com.xwray.groupie.viewbinding.BindableItem
|
||||
|
||||
class NotificationItem(
|
||||
|
@ -40,23 +41,11 @@ class NotificationItem(
|
|||
?: notification.media?.coverImage?.large
|
||||
blurImage(binding.notificationBannerImage, cover)
|
||||
|
||||
val defaultHeight = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
153f,
|
||||
binding.root.context.resources.displayMetrics
|
||||
).toInt()
|
||||
val defaultHeight = 153.toPx
|
||||
|
||||
val userHeight = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
90f,
|
||||
binding.root.context.resources.displayMetrics
|
||||
).toInt()
|
||||
val userHeight = 90.toPx
|
||||
|
||||
val textMarginStart = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
125f,
|
||||
binding.root.context.resources.displayMetrics
|
||||
).toInt()
|
||||
val textMarginStart = 125.toPx
|
||||
|
||||
if (user) {
|
||||
binding.notificationCover.visibility = View.GONE
|
||||
|
|
|
@ -29,7 +29,6 @@ import com.google.android.material.tabs.TabLayoutMediator
|
|||
class ExtensionsActivity : AppCompatActivity() {
|
||||
lateinit var binding: ActivityExtensionsBinding
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ import android.widget.ImageView
|
|||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
@ -59,15 +61,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||
val name = pkg.name
|
||||
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
||||
val activity = requireActivity() as ExtensionsActivity
|
||||
val visibility = if (show) View.VISIBLE else View.GONE
|
||||
activity.findViewById<ViewPager2>(R.id.viewPager).visibility = visibility
|
||||
activity.findViewById<TabLayout>(R.id.tabLayout).visibility = visibility
|
||||
activity.findViewById<TextInputLayout>(R.id.searchView).visibility = visibility
|
||||
activity.findViewById<ImageView>(R.id.languageselect).visibility = visibility
|
||||
activity.findViewById<ViewPager2>(R.id.viewPager).isVisible = show
|
||||
activity.findViewById<TabLayout>(R.id.tabLayout).isVisible = show
|
||||
activity.findViewById<TextInputLayout>(R.id.searchView).isVisible = show
|
||||
activity.findViewById<ImageView>(R.id.languageselect).isVisible = show
|
||||
activity.findViewById<TextView>(R.id.extensions).text =
|
||||
if (show) getString(R.string.extensions) else name
|
||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||
if (show) View.GONE else View.VISIBLE
|
||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).isGone = show
|
||||
}
|
||||
var itemSelected = false
|
||||
val allSettings = pkg.sources.filterIsInstance<ConfigurableAnimeSource>()
|
||||
|
@ -294,13 +294,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val extension = getItem(position)
|
||||
val nsfw = if (extension.isNsfw) "(18+)" else ""
|
||||
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
|
||||
holder.extensionNameTextView.text = extension.name
|
||||
holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw"
|
||||
val versionText = "$lang ${extension.versionName} $nsfw"
|
||||
holder.extensionVersionTextView.text = versionText
|
||||
if (!skipIcons) {
|
||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import android.widget.ImageView
|
|||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
@ -57,15 +59,13 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||
val name = pkg.name
|
||||
val changeUIVisibility: (Boolean) -> Unit = { show ->
|
||||
val activity = requireActivity() as ExtensionsActivity
|
||||
val visibility = if (show) View.VISIBLE else View.GONE
|
||||
activity.findViewById<ViewPager2>(R.id.viewPager).visibility = visibility
|
||||
activity.findViewById<TabLayout>(R.id.tabLayout).visibility = visibility
|
||||
activity.findViewById<TextInputLayout>(R.id.searchView).visibility = visibility
|
||||
activity.findViewById<ImageView>(R.id.languageselect).visibility = visibility
|
||||
activity.findViewById<ViewPager2>(R.id.viewPager).isVisible = show
|
||||
activity.findViewById<TabLayout>(R.id.tabLayout).isVisible = show
|
||||
activity.findViewById<TextInputLayout>(R.id.searchView).isVisible = show
|
||||
activity.findViewById<ImageView>(R.id.languageselect).isVisible = show
|
||||
activity.findViewById<TextView>(R.id.extensions).text =
|
||||
if (show) getString(R.string.extensions) else name
|
||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
|
||||
if (show) View.GONE else View.VISIBLE
|
||||
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).isGone = show
|
||||
}
|
||||
var itemSelected = false
|
||||
val allSettings = pkg.sources.filterIsInstance<ConfigurableSource>()
|
||||
|
@ -290,13 +290,14 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||
MangaSources.performReorderMangaSources()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val extension = getItem(position) // Use getItem() from ListAdapter
|
||||
val nsfw = if (extension.isNsfw) "(18+)" else ""
|
||||
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
|
||||
holder.extensionNameTextView.text = extension.name
|
||||
holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw"
|
||||
val versionText = "$lang ${extension.versionName} $nsfw"
|
||||
holder.extensionVersionTextView.text = versionText
|
||||
if (!skipIcons) {
|
||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||
}
|
||||
|
|
|
@ -221,13 +221,13 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler {
|
|||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val extension = getItem(position) // Use getItem() from ListAdapter
|
||||
val nsfw = ""
|
||||
val lang = LanguageMapper.mapLanguageCodeToName("all")
|
||||
holder.extensionNameTextView.text = extension.name
|
||||
holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw"
|
||||
val versionText = "$lang ${extension.versionName} $nsfw"
|
||||
holder.extensionVersionTextView.text = versionText
|
||||
if (!skipIcons) {
|
||||
holder.extensionIconImageView.setImageDrawable(extension.icon)
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
|||
binding.playerSettingsTimeStamps.isChecked = PrefManager.getVal(PrefName.TimeStampsEnabled)
|
||||
binding.playerSettingsTimeStamps.setOnCheckedChangeListener { _, isChecked ->
|
||||
PrefManager.setVal(PrefName.TimeStampsEnabled, isChecked)
|
||||
binding.playerSettingsAutoSkipOpEd.isEnabled = isChecked
|
||||
}
|
||||
|
||||
binding.playerSettingsTimeStampsAutoHide.isChecked = PrefManager.getVal(PrefName.AutoHideTimeStamps)
|
||||
|
@ -148,6 +149,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
|||
|
||||
// Auto
|
||||
binding.playerSettingsAutoSkipOpEd.isChecked = PrefManager.getVal(PrefName.AutoSkipOPED)
|
||||
binding.playerSettingsAutoSkipOpEd.isEnabled = binding.playerSettingsTimeStamps.isChecked
|
||||
binding.playerSettingsAutoSkipOpEd.setOnCheckedChangeListener { _, isChecked ->
|
||||
PrefManager.setVal(PrefName.AutoSkipOPED, isChecked)
|
||||
}
|
||||
|
@ -172,7 +174,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
|||
binding.playerSettingsAskChapterZero.isChecked =
|
||||
PrefManager.getVal(PrefName.ChapterZeroPlayer)
|
||||
binding.playerSettingsAskChapterZero.isEnabled =
|
||||
!PrefManager.getVal<Boolean>(PrefName.AskIndividualPlayer)
|
||||
!binding.playerSettingsAskUpdateProgress.isChecked
|
||||
binding.playerSettingsAskChapterZero.setOnCheckedChangeListener { _, isChecked ->
|
||||
PrefManager.setVal(PrefName.ChapterZeroPlayer, isChecked)
|
||||
}
|
||||
|
@ -411,7 +413,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
|||
"Magenta"
|
||||
)
|
||||
val subBackgroundDialog = AlertDialog.Builder(this, R.style.MyPopup)
|
||||
.setTitle(getString(R.string.outline_sub_color))
|
||||
.setTitle(getString(R.string.sub_background_color_select))
|
||||
binding.videoSubColorBackground.setOnClickListener {
|
||||
val dialog = subBackgroundDialog.setSingleChoiceItems(
|
||||
colorsSubBackground,
|
||||
|
@ -438,7 +440,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
|
|||
"Magenta"
|
||||
)
|
||||
val subWindowDialog = AlertDialog.Builder(this, R.style.MyPopup)
|
||||
.setTitle(getString(R.string.outline_sub_color))
|
||||
.setTitle(getString(R.string.sub_window_color_select))
|
||||
binding.videoSubColorWindow.setOnClickListener {
|
||||
val dialog = subWindowDialog.setSingleChoiceItems(
|
||||
colorsSubWindow,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,9 +9,9 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import ani.dantotsu.BottomSheetDialogFragment
|
||||
import ani.dantotsu.MainActivity
|
||||
import ani.dantotsu.profile.ProfileActivity
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.connections.anilist.Anilist
|
||||
import ani.dantotsu.databinding.BottomSheetSettingsBinding
|
||||
|
@ -24,13 +24,15 @@ import ani.dantotsu.home.MangaFragment
|
|||
import ani.dantotsu.home.NoInternet
|
||||
import ani.dantotsu.incognitoNotification
|
||||
import ani.dantotsu.loadImage
|
||||
import ani.dantotsu.profile.activity.NotificationActivity
|
||||
import ani.dantotsu.offline.OfflineFragment
|
||||
import ani.dantotsu.profile.ProfileActivity
|
||||
import ani.dantotsu.profile.activity.FeedActivity
|
||||
import ani.dantotsu.profile.activity.NotificationActivity
|
||||
import ani.dantotsu.setSafeOnClickListener
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.startMainActivity
|
||||
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
||||
import java.util.Timer
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
|
@ -41,7 +43,7 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
|
|||
private lateinit var pageType: PageType
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
pageType = arguments?.getSerializable("pageType") as? PageType ?: PageType.HOME
|
||||
pageType = arguments?.getSerializableCompat("pageType") as? PageType ?: PageType.HOME
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -94,7 +96,7 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
|
|||
Anilist.loginIntent(requireActivity())
|
||||
}
|
||||
}
|
||||
binding.settingsNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
|
||||
binding.settingsNotificationCount.isVisible = Anilist.unreadNotificationCount > 0
|
||||
binding.settingsNotificationCount.text = Anilist.unreadNotificationCount.toString()
|
||||
binding.settingsUserAvatar.setOnClickListener{
|
||||
ContextCompat.startActivity(
|
||||
|
|
|
@ -202,12 +202,12 @@ class AnimeExtensionAdapter(private val clickListener: OnAnimeInstallClickListen
|
|||
|
||||
val extensionIconImageView: ImageView = binding.extensionIconImageView
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(extension: AnimeExtension.Available) {
|
||||
val nsfw = if (extension.isNsfw) "(18+)" else ""
|
||||
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
|
||||
binding.extensionNameTextView.text = extension.name
|
||||
binding.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw"
|
||||
val versionText = "$lang ${extension.versionName} $nsfw"
|
||||
binding.extensionVersionTextView.text = versionText
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
|
|
|
@ -199,12 +199,12 @@ class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListen
|
|||
|
||||
val extensionIconImageView: ImageView = binding.extensionIconImageView
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(extension: MangaExtension.Available) {
|
||||
val nsfw = if (extension.isNsfw) "(18+)" else ""
|
||||
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
|
||||
binding.extensionNameTextView.text = extension.name
|
||||
binding.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw"
|
||||
val versionText = "$lang ${extension.versionName} $nsfw"
|
||||
binding.extensionVersionTextView.text = versionText
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
|
|
|
@ -41,13 +41,14 @@ import kotlinx.coroutines.withContext
|
|||
class NovelExtensionsViewModelFactory(
|
||||
private val novelExtensionManager: NovelExtensionManager
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return NovelExtensionsViewModel(novelExtensionManager) as T
|
||||
}
|
||||
}
|
||||
|
||||
class NovelExtensionsViewModel(
|
||||
private val novelExtensionManager: NovelExtensionManager
|
||||
novelExtensionManager: NovelExtensionManager
|
||||
) : ViewModel() {
|
||||
private val searchQuery = MutableStateFlow("")
|
||||
private var currentPagingSource: NovelExtensionPagingSource? = null
|
||||
|
@ -102,21 +103,20 @@ class NovelExtensionPagingSource(
|
|||
} else {
|
||||
availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
|
||||
}
|
||||
val filternfsw = filteredExtensions
|
||||
/*val filternfsw = if(isNsfwEnabled) { currently not implemented
|
||||
filteredExtensions
|
||||
} else {
|
||||
filteredExtensions.filterNot { it.isNsfw }
|
||||
}*/
|
||||
return try {
|
||||
val sublist = filternfsw.subList(
|
||||
val sublist = filteredExtensions.subList(
|
||||
fromIndex = position,
|
||||
toIndex = (position + params.loadSize).coerceAtMost(filternfsw.size)
|
||||
toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size)
|
||||
)
|
||||
LoadResult.Page(
|
||||
data = sublist,
|
||||
prevKey = if (position == 0) null else position - params.loadSize,
|
||||
nextKey = if (position + params.loadSize >= filternfsw.size) null else position + params.loadSize
|
||||
nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LoadResult.Error(e)
|
||||
|
|
|
@ -6,7 +6,7 @@ import ani.dantotsu.util.ColorEditor.Companion.toHexColor
|
|||
class AniMarkdown { //istg anilist has the worst api
|
||||
companion object {
|
||||
private fun convertNestedImageToHtml(markdown: String): String {
|
||||
val regex = """\[\!\[(.*?)\]\((.*?)\)\]\((.*?)\)""".toRegex()
|
||||
val regex = """\[!\[(.*?)]\((.*?)\)]\((.*?)\)""".toRegex()
|
||||
return regex.replace(markdown) { matchResult ->
|
||||
val altText = matchResult.groupValues[1]
|
||||
val imageUrl = matchResult.groupValues[2]
|
||||
|
@ -16,7 +16,7 @@ class AniMarkdown { //istg anilist has the worst api
|
|||
}
|
||||
|
||||
private fun convertImageToHtml(markdown: String): String {
|
||||
val regex = """\!\[(.*?)\]\((.*?)\)""".toRegex()
|
||||
val regex = """!\[(.*?)]\((.*?)\)""".toRegex()
|
||||
return regex.replace(markdown) { matchResult ->
|
||||
val altText = matchResult.groupValues[1]
|
||||
val imageUrl = matchResult.groupValues[2]
|
||||
|
@ -25,7 +25,7 @@ class AniMarkdown { //istg anilist has the worst api
|
|||
}
|
||||
|
||||
private fun convertLinkToHtml(markdown: String): String {
|
||||
val regex = """\[(.*?)\]\((.*?)\)""".toRegex()
|
||||
val regex = """\[(.*?)]\((.*?)\)""".toRegex()
|
||||
return regex.replace(markdown) { matchResult ->
|
||||
val linkText = matchResult.groupValues[1]
|
||||
val linkUrl = matchResult.groupValues[2]
|
||||
|
@ -50,7 +50,7 @@ class AniMarkdown { //istg anilist has the worst api
|
|||
private fun underlineToHtml(html: String): String {
|
||||
return html.replace("(?s)___(.*?)___".toRegex(), "<br><em><strong>$1</strong></em><br>")
|
||||
.replace("(?s)__(.*?)__".toRegex(), "<br><strong>$1</strong><br>")
|
||||
.replace("(?s)[\\s]+_([^_]+)_[\\s]+".toRegex(), "<em>$1</em>")
|
||||
.replace("(?s)\\s+_([^_]+)_\\s+".toRegex(), "<em>$1</em>")
|
||||
}
|
||||
|
||||
fun getBasicAniHTML(html: String): String {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ani.dantotsu.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.widget.RemoteViews
|
||||
|
@ -12,7 +11,7 @@ import java.io.InputStream
|
|||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
class CurrentlyAiringRemoteViewsFactory(private val context: Context, intent: Intent) :
|
||||
class CurrentlyAiringRemoteViewsFactory(private val context: Context) :
|
||||
RemoteViewsService.RemoteViewsFactory {
|
||||
private var widgetItems = mutableListOf<WidgetItem>()
|
||||
|
||||
|
|
|
@ -7,6 +7,6 @@ import ani.dantotsu.util.Logger
|
|||
class CurrentlyAiringRemoteViewsService : RemoteViewsService() {
|
||||
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
|
||||
Logger.log("CurrentlyAiringRemoteViewsFactory onGetViewFactory")
|
||||
return CurrentlyAiringRemoteViewsFactory(applicationContext, intent)
|
||||
return CurrentlyAiringRemoteViewsFactory(applicationContext)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class ExtensionInstallerPreference(
|
|||
|
||||
|
||||
val entries
|
||||
get() = ExtensionInstaller.values().run {
|
||||
get() = ExtensionInstaller.entries.toTypedArray().run {
|
||||
if (context.hasMiuiPackageInstaller) {
|
||||
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||
} else {
|
||||
|
|
|
@ -58,8 +58,7 @@ interface AnimeSource {
|
|||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
suspend fun getVideoList(episode: SEpisode): List<Video> {
|
||||
val list = fetchVideoList(episode).awaitSingle()
|
||||
return list
|
||||
return fetchVideoList(episode).awaitSingle()
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
|
|
|
@ -10,10 +10,11 @@ import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi
|
|||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallReceiver
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import eu.kanade.tachiyomi.util.preference.plusAssign
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
@ -51,7 +52,7 @@ class AnimeExtensionManager(
|
|||
/**
|
||||
* The installer which installs, updates and uninstalls the anime extensions.
|
||||
*/
|
||||
private val installer by lazy { AnimeExtensionInstaller(context) }
|
||||
private val installer by lazy { ExtensionInstaller(context) }
|
||||
|
||||
private val iconMap = mutableMapOf<String, Drawable>()
|
||||
|
||||
|
@ -92,14 +93,14 @@ class AnimeExtensionManager(
|
|||
|
||||
init {
|
||||
initAnimeExtensions()
|
||||
AnimeExtensionInstallReceiver(AnimeInstallationListener()).register(context)
|
||||
ExtensionInstallReceiver().setAnimeListener(InstallationListener()).register(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and registers the installed animeextensions.
|
||||
*/
|
||||
private fun initAnimeExtensions() {
|
||||
val animeextensions = AnimeExtensionLoader.loadExtensions(context)
|
||||
val animeextensions = ExtensionLoader.loadAnimeExtensions(context)
|
||||
|
||||
_installedAnimeExtensionsFlow.value = animeextensions
|
||||
.filterIsInstance<AnimeLoadResult.Success>()
|
||||
|
@ -254,12 +255,13 @@ class AnimeExtensionManager(
|
|||
*
|
||||
* @param signature The signature to whitelist.
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun trustSignature(signature: String) {
|
||||
val untrustedSignatures =
|
||||
_untrustedAnimeExtensionsFlow.value.map { it.signatureHash }.toSet()
|
||||
if (signature !in untrustedSignatures) return
|
||||
|
||||
AnimeExtensionLoader.trustedSignatures += signature
|
||||
ExtensionLoader.trustedSignaturesAnime += signature
|
||||
preferences.trustedSignatures() += signature
|
||||
|
||||
val nowTrustedAnimeExtensions =
|
||||
|
@ -271,7 +273,7 @@ class AnimeExtensionManager(
|
|||
nowTrustedAnimeExtensions
|
||||
.map { animeextension ->
|
||||
async {
|
||||
AnimeExtensionLoader.loadExtensionFromPkgName(
|
||||
ExtensionLoader.loadAnimeExtensionFromPkgName(
|
||||
ctx,
|
||||
animeextension.pkgName
|
||||
)
|
||||
|
@ -333,7 +335,7 @@ class AnimeExtensionManager(
|
|||
/**
|
||||
* Listener which receives events of the anime extensions being installed, updated or removed.
|
||||
*/
|
||||
private inner class AnimeInstallationListener : AnimeExtensionInstallReceiver.Listener {
|
||||
private inner class InstallationListener : ExtensionInstallReceiver.AnimeListener {
|
||||
|
||||
override fun onExtensionInstalled(extension: AnimeExtension.Installed) {
|
||||
registerNewExtension(extension.withUpdateCheck())
|
||||
|
|
|
@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
|
|||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
|
@ -87,7 +87,7 @@ internal class AnimeExtensionGithubApi {
|
|||
findExtensions().also { lastExtCheck.set(Date().time) }
|
||||
}
|
||||
|
||||
val installedExtensions = AnimeExtensionLoader.loadExtensions(context)
|
||||
val installedExtensions = ExtensionLoader.loadAnimeExtensions(context)
|
||||
.filterIsInstance<AnimeLoadResult.Success>()
|
||||
.map { it.extension }
|
||||
|
||||
|
@ -115,7 +115,7 @@ internal class AnimeExtensionGithubApi {
|
|||
return this
|
||||
.filter {
|
||||
val libVersion = it.extractLibVersion()
|
||||
libVersion >= AnimeExtensionLoader.LIB_VERSION_MIN && libVersion <= AnimeExtensionLoader.LIB_VERSION_MAX
|
||||
libVersion >= ExtensionLoader.ANIME_LIB_VERSION_MIN && libVersion <= ExtensionLoader.ANIME_LIB_VERSION_MAX
|
||||
}
|
||||
.map {
|
||||
AnimeExtension.Available(
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.anime.installer
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import ani.dantotsu.snackString
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.tachiyomi.extension.InstallStep
|
||||
import eu.kanade.tachiyomi.util.lang.use
|
||||
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
|
||||
import eu.kanade.tachiyomi.util.system.getUriSize
|
||||
|
||||
class PackageInstallerInstallerAnime(private val service: Service) : InstallerAnime(service) {
|
||||
|
||||
private val packageInstaller = service.packageManager.packageInstaller
|
||||
|
||||
private val packageActionReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.getIntExtra(
|
||||
PackageInstaller.EXTRA_STATUS,
|
||||
PackageInstaller.STATUS_FAILURE
|
||||
)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
val userAction = intent.getParcelableExtraCompat<Intent>(Intent.EXTRA_INTENT)
|
||||
if (userAction == null) {
|
||||
Logger.log("Fatal error for $intent")
|
||||
continueQueue(InstallStep.Error)
|
||||
return
|
||||
}
|
||||
userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
service.startActivity(userAction)
|
||||
}
|
||||
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
||||
continueQueue(InstallStep.Idle)
|
||||
}
|
||||
|
||||
PackageInstaller.STATUS_SUCCESS -> continueQueue(InstallStep.Installed)
|
||||
else -> continueQueue(InstallStep.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var activeSession: Pair<Entry, Int>? = null
|
||||
|
||||
// Always ready
|
||||
override var ready = true
|
||||
|
||||
override fun processEntry(entry: Entry) {
|
||||
super.processEntry(entry)
|
||||
activeSession = null
|
||||
try {
|
||||
val installParams =
|
||||
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||
}
|
||||
activeSession = entry to packageInstaller.createSession(installParams)
|
||||
val fileSize = service.getUriSize(entry.uri) ?: throw IllegalStateException()
|
||||
installParams.setSize(fileSize)
|
||||
|
||||
val inputStream =
|
||||
service.contentResolver.openInputStream(entry.uri) ?: throw IllegalStateException()
|
||||
val session = packageInstaller.openSession(activeSession!!.second)
|
||||
val outputStream = session.openWrite(entry.downloadId.toString(), 0, fileSize)
|
||||
session.use {
|
||||
arrayOf(inputStream, outputStream).use {
|
||||
inputStream.copyTo(outputStream)
|
||||
session.fsync(outputStream)
|
||||
}
|
||||
|
||||
val intentSender = PendingIntent.getBroadcast(
|
||||
service,
|
||||
activeSession!!.second,
|
||||
Intent(INSTALL_ACTION),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
} else 0
|
||||
).intentSender
|
||||
session.commit(intentSender)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.log(e)
|
||||
Logger.log("Failed to install extension ${entry.downloadId} ${entry.uri}")
|
||||
snackString("Failed to install extension ${entry.downloadId} ${entry.uri}")
|
||||
activeSession?.let { (_, sessionId) ->
|
||||
packageInstaller.abandonSession(sessionId)
|
||||
}
|
||||
continueQueue(InstallStep.Error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelEntry(entry: Entry): Boolean {
|
||||
activeSession?.let { (activeEntry, sessionId) ->
|
||||
if (activeEntry == entry) {
|
||||
packageInstaller.abandonSession(sessionId)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
service.unregisterReceiver(packageActionReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
init {
|
||||
ContextCompat.registerReceiver(
|
||||
service,
|
||||
packageActionReceiver,
|
||||
IntentFilter(INSTALL_ACTION),
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val INSTALL_ACTION = "PackageInstallerInstaller.INSTALL_ACTION"
|
|
@ -3,5 +3,5 @@ package eu.kanade.tachiyomi.extension.anime.model
|
|||
sealed class AnimeLoadResult {
|
||||
class Success(val extension: AnimeExtension.Installed) : AnimeLoadResult()
|
||||
class Untrusted(val extension: AnimeExtension.Untrusted) : AnimeLoadResult()
|
||||
object Error : AnimeLoadResult()
|
||||
data object Error : AnimeLoadResult()
|
||||
}
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.anime.util
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import androidx.core.content.ContextCompat
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import tachiyomi.core.util.lang.launchNow
|
||||
|
||||
/**
|
||||
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only
|
||||
* notifies the given [listener] when the package is an extension.
|
||||
*
|
||||
* @param listener The listener that should be notified of extension installation events.
|
||||
*/
|
||||
internal class AnimeExtensionInstallReceiver(private val listener: Listener) :
|
||||
BroadcastReceiver() {
|
||||
|
||||
/**
|
||||
* Registers this broadcast receiver
|
||||
*/
|
||||
fun register(context: Context) {
|
||||
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_EXPORTED)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the intent filter this receiver should subscribe to.
|
||||
*/
|
||||
private val filter
|
||||
get() = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REPLACED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addDataScheme("package")
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the events of the [filter] is received. When the package is an extension,
|
||||
* it's loaded in background and it notifies the [listener] when finished.
|
||||
*/
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
if (intent == null) return
|
||||
|
||||
when (intent.action) {
|
||||
Intent.ACTION_PACKAGE_ADDED -> {
|
||||
if (isReplacing(intent)) return
|
||||
|
||||
launchNow {
|
||||
when (val result = getExtensionFromIntent(context, intent)) {
|
||||
is AnimeLoadResult.Success -> listener.onExtensionInstalled(result.extension)
|
||||
|
||||
is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Intent.ACTION_PACKAGE_REPLACED -> {
|
||||
launchNow {
|
||||
when (val result = getExtensionFromIntent(context, intent)) {
|
||||
is AnimeLoadResult.Success -> listener.onExtensionUpdated(result.extension)
|
||||
// Not needed as a package can't be upgraded if the signature is different
|
||||
// is LoadResult.Untrusted -> {}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Intent.ACTION_PACKAGE_REMOVED -> {
|
||||
if (isReplacing(intent)) return
|
||||
|
||||
val pkgName = getPackageNameFromIntent(intent)
|
||||
if (pkgName != null) {
|
||||
listener.onPackageUninstalled(pkgName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this package is performing an update.
|
||||
*
|
||||
* @param intent The intent that triggered the event.
|
||||
*/
|
||||
private fun isReplacing(intent: Intent): Boolean {
|
||||
return intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension triggered by the given intent.
|
||||
*
|
||||
* @param context The application context.
|
||||
* @param intent The intent containing the package name of the extension.
|
||||
*/
|
||||
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): AnimeLoadResult {
|
||||
val pkgName = getPackageNameFromIntent(intent)
|
||||
if (pkgName == null) {
|
||||
Logger.log("Package name not found")
|
||||
return AnimeLoadResult.Error
|
||||
}
|
||||
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) {
|
||||
AnimeExtensionLoader.loadExtensionFromPkgName(
|
||||
context,
|
||||
pkgName,
|
||||
)
|
||||
}.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name of the installed, updated or removed application.
|
||||
*/
|
||||
private fun getPackageNameFromIntent(intent: Intent?): String? {
|
||||
return intent?.data?.encodedSchemeSpecificPart ?: return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener that receives extension installation events.
|
||||
*/
|
||||
interface Listener {
|
||||
fun onExtensionInstalled(extension: AnimeExtension.Installed)
|
||||
fun onExtensionUpdated(extension: AnimeExtension.Installed)
|
||||
fun onExtensionUntrusted(extension: AnimeExtension.Untrusted)
|
||||
fun onPackageUninstalled(pkgName: String)
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.anime.util
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime
|
||||
import eu.kanade.tachiyomi.extension.anime.installer.PackageInstallerInstallerAnime
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
|
||||
import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
|
||||
class AnimeExtensionInstallService : Service() {
|
||||
|
||||
private var installer: InstallerAnime? = null
|
||||
|
||||
override fun onCreate() {
|
||||
val notification = notificationBuilder(Notifications.CHANNEL_EXTENSIONS_UPDATE) {
|
||||
setSmallIcon(R.drawable.ic_download_24)
|
||||
setAutoCancel(false)
|
||||
setOngoing(true)
|
||||
setShowWhen(false)
|
||||
setContentTitle("Installing Anime Extension...")
|
||||
setProgress(100, 100, true)
|
||||
}.build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(
|
||||
Notifications.ID_EXTENSION_INSTALLER,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
)
|
||||
} else {
|
||||
startForeground(Notifications.ID_EXTENSION_INSTALLER, notification)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val uri = intent?.data
|
||||
val id = intent?.getLongExtra(EXTRA_DOWNLOAD_ID, -1)?.takeIf { it != -1L }
|
||||
val installerUsed = intent?.getSerializableExtraCompat<BasePreferences.ExtensionInstaller>(
|
||||
EXTRA_INSTALLER,
|
||||
)
|
||||
if (uri == null || id == null || installerUsed == null) {
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
if (installer == null) {
|
||||
installer = when (installerUsed) {
|
||||
BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerAnime(
|
||||
this
|
||||
)
|
||||
|
||||
else -> {
|
||||
Logger.log("Not implemented for installer $installerUsed")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
}
|
||||
}
|
||||
installer!!.addToQueue(id, uri)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
installer?.onDestroy()
|
||||
installer = null
|
||||
}
|
||||
|
||||
override fun onBind(i: Intent?): IBinder? = null
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_INSTALLER = "EXTRA_INSTALLER"
|
||||
|
||||
fun getIntent(
|
||||
context: Context,
|
||||
downloadId: Long,
|
||||
uri: Uri,
|
||||
installer: BasePreferences.ExtensionInstaller,
|
||||
): Intent {
|
||||
return Intent(context, AnimeExtensionInstallService::class.java)
|
||||
.setDataAndType(uri, AnimeExtensionInstaller.APK_MIME)
|
||||
.putExtra(EXTRA_DOWNLOAD_ID, downloadId)
|
||||
.putExtra(EXTRA_INSTALLER, installer)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.anime.util
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
import ani.dantotsu.util.Logger
|
||||
import com.jakewharton.rxrelay.PublishRelay
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.extension.InstallStep
|
||||
import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* The installer which installs, updates and uninstalls the extensions.
|
||||
*
|
||||
* @param context The application context.
|
||||
*/
|
||||
internal class AnimeExtensionInstaller(private val context: Context) {
|
||||
|
||||
/**
|
||||
* The system's download manager
|
||||
*/
|
||||
private val downloadManager = context.getSystemService<DownloadManager>()!!
|
||||
|
||||
/**
|
||||
* The broadcast receiver which listens to download completion events.
|
||||
*/
|
||||
private val downloadReceiver = DownloadCompletionReceiver()
|
||||
|
||||
/**
|
||||
* The currently requested downloads, with the package name (unique id) as key, and the id
|
||||
* returned by the download manager.
|
||||
*/
|
||||
private val activeDownloads = hashMapOf<String, Long>()
|
||||
|
||||
/**
|
||||
* Relay used to notify the installation step of every download.
|
||||
*/
|
||||
private val downloadsRelay = PublishRelay.create<Pair<Long, InstallStep>>()
|
||||
|
||||
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
||||
|
||||
/**
|
||||
* Adds the given extension to the downloads queue and returns an observable containing its
|
||||
* step in the installation process.
|
||||
*
|
||||
* @param url The url of the apk.
|
||||
* @param extension The extension to install.
|
||||
*/
|
||||
fun downloadAndInstall(url: String, extension: AnimeExtension) = Observable.defer {
|
||||
val pkgName = extension.pkgName
|
||||
|
||||
val oldDownload = activeDownloads[pkgName]
|
||||
if (oldDownload != null) {
|
||||
deleteDownload(pkgName)
|
||||
}
|
||||
|
||||
// Register the receiver after removing (and unregistering) the previous download
|
||||
downloadReceiver.register()
|
||||
|
||||
val downloadUri = url.toUri()
|
||||
val request = DownloadManager.Request(downloadUri)
|
||||
.setTitle(extension.name)
|
||||
.setMimeType(APK_MIME)
|
||||
.setDestinationInExternalFilesDir(
|
||||
context,
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
downloadUri.lastPathSegment
|
||||
)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
|
||||
val id = downloadManager.enqueue(request)
|
||||
activeDownloads[pkgName] = id
|
||||
|
||||
downloadsRelay.filter { it.first == id }
|
||||
.map { it.second }
|
||||
// Poll download status
|
||||
.mergeWith(pollStatus(id))
|
||||
// Stop when the application is installed or errors
|
||||
.takeUntil { it.isCompleted() }
|
||||
// Always notify on main thread
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Always remove the download when unsubscribed
|
||||
.doOnUnsubscribe { deleteDownload(pkgName) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable that polls the given download id for its status every second, as the
|
||||
* manager doesn't have any notification system. It'll stop once the download finishes.
|
||||
*
|
||||
* @param id The id of the download to poll.
|
||||
*/
|
||||
private fun pollStatus(id: Long): Observable<InstallStep> {
|
||||
val query = DownloadManager.Query().setFilterById(id)
|
||||
|
||||
return Observable.interval(0, 1, TimeUnit.SECONDS)
|
||||
// Get the current download status
|
||||
.map {
|
||||
downloadManager.query(query).use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
|
||||
} else {
|
||||
DownloadManager.STATUS_FAILED
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ignore duplicate results
|
||||
.distinctUntilChanged()
|
||||
// Stop polling when the download fails or finishes
|
||||
.takeUntil { it == DownloadManager.STATUS_SUCCESSFUL || it == DownloadManager.STATUS_FAILED }
|
||||
// Map to our model
|
||||
.flatMap { status ->
|
||||
when (status) {
|
||||
DownloadManager.STATUS_PENDING -> Observable.just(InstallStep.Pending)
|
||||
DownloadManager.STATUS_RUNNING -> Observable.just(InstallStep.Downloading)
|
||||
else -> Observable.empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an intent to install the extension at the given uri.
|
||||
*
|
||||
* @param uri The uri of the extension to install.
|
||||
*/
|
||||
fun installApk(downloadId: Long, uri: Uri) {
|
||||
when (val installer = extensionInstaller.get()) {
|
||||
BasePreferences.ExtensionInstaller.LEGACY -> {
|
||||
val intent = Intent(context, AnimeExtensionInstallActivity::class.java)
|
||||
.setDataAndType(uri, APK_MIME)
|
||||
.putExtra(EXTRA_DOWNLOAD_ID, downloadId)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val intent =
|
||||
AnimeExtensionInstallService.getIntent(context, downloadId, uri, installer)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels extension install and remove from download manager and installer.
|
||||
*/
|
||||
fun cancelInstall(pkgName: String) {
|
||||
val downloadId = activeDownloads.remove(pkgName) ?: return
|
||||
downloadManager.remove(downloadId)
|
||||
InstallerAnime.cancelInstallQueue(context, downloadId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an intent to uninstall the extension by the given package name.
|
||||
*
|
||||
* @param pkgName The package name of the extension to uninstall
|
||||
*/
|
||||
fun uninstallApk(pkgName: String) {
|
||||
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, "package:$pkgName".toUri())
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the step of the installation of an extension.
|
||||
*
|
||||
* @param downloadId The id of the download.
|
||||
* @param step New install step.
|
||||
*/
|
||||
fun updateInstallStep(downloadId: Long, step: InstallStep) {
|
||||
downloadsRelay.call(downloadId to step)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the download for the given package name.
|
||||
*
|
||||
* @param pkgName The package name of the download to delete.
|
||||
*/
|
||||
private fun deleteDownload(pkgName: String) {
|
||||
val downloadId = activeDownloads.remove(pkgName)
|
||||
if (downloadId != null) {
|
||||
downloadManager.remove(downloadId)
|
||||
}
|
||||
if (activeDownloads.isEmpty()) {
|
||||
downloadReceiver.unregister()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receiver that listens to download status events.
|
||||
*/
|
||||
private inner class DownloadCompletionReceiver : BroadcastReceiver() {
|
||||
|
||||
/**
|
||||
* Whether this receiver is currently registered.
|
||||
*/
|
||||
private var isRegistered = false
|
||||
|
||||
/**
|
||||
* Registers this receiver if it's not already.
|
||||
*/
|
||||
fun register() {
|
||||
if (isRegistered) return
|
||||
isRegistered = true
|
||||
|
||||
val filter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||
ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_EXPORTED)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this receiver if it's not already.
|
||||
*/
|
||||
fun unregister() {
|
||||
if (!isRegistered) return
|
||||
isRegistered = false
|
||||
|
||||
context.unregisterReceiver(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a download event is received. It looks for the download in the current active
|
||||
* downloads and notifies its installation step.
|
||||
*/
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) ?: return
|
||||
|
||||
// Avoid events for downloads we didn't request
|
||||
if (id !in activeDownloads.values) return
|
||||
|
||||
val uri = downloadManager.getUriForDownloadedFile(id)
|
||||
|
||||
// Set next installation step
|
||||
if (uri == null) {
|
||||
Logger.log("Couldn't locate downloaded APK")
|
||||
downloadsRelay.call(id to InstallStep.Error)
|
||||
return
|
||||
}
|
||||
|
||||
val query = DownloadManager.Query().setFilterById(id)
|
||||
downloadManager.query(query).use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val localUri = cursor.getString(
|
||||
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI),
|
||||
).removePrefix(FILE_SCHEME)
|
||||
|
||||
installApk(id, File(localUri).getUriCompat(context))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val APK_MIME = "application/vnd.android.package-archive"
|
||||
const val EXTRA_DOWNLOAD_ID = "AnimeExtensionInstaller.extra.DOWNLOAD_ID"
|
||||
const val FILE_SCHEME = "file://"
|
||||
}
|
||||
}
|
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