Merge pull request #271 from RepoDevil/cleanup

The motherload
This commit is contained in:
rebel onion 2024-03-23 16:56:18 -05:00 committed by GitHub
commit d43d643bbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
163 changed files with 5058 additions and 5910 deletions

View file

@ -112,14 +112,13 @@ dependencies {
implementation "androidx.media3:media3-exoplayer-dash:$exo_version" implementation "androidx.media3:media3-exoplayer-dash:$exo_version"
implementation "androidx.media3:media3-datasource-okhttp:$exo_version" implementation "androidx.media3:media3-datasource-okhttp:$exo_version"
implementation "androidx.media3:media3-session:$exo_version" implementation "androidx.media3:media3-session:$exo_version"
//media3 casting // Media3 Casting
implementation "androidx.media3:media3-cast:$exo_version" 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 'com.google.android.material:material:1.11.0'
//implementation 'nl.joery.animatedbottombar:library:1.1.0' implementation 'com.github.RepoDevil:AnimatedBottomBar:7fcb9af'
implementation 'com.github.rebelonion:AnimatedBottomBar:v1.1.0'
implementation 'com.flaviofaria:kenburnsview:1.0.7' implementation 'com.flaviofaria:kenburnsview:1.0.7'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
implementation 'com.alexvasilkov:gesture-views:2.8.3' implementation 'com.alexvasilkov:gesture-views:2.8.3'
@ -143,7 +142,7 @@ dependencies {
implementation "com.github.lisawray.groupie:groupie:$groupie_version" implementation "com.github.lisawray.groupie:groupie:$groupie_version"
implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version" implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version"
// string matching // String Matching
implementation 'me.xdrop:fuzzywuzzy:1.4.0' implementation 'me.xdrop:fuzzywuzzy:1.4.0'
// Aniyomi // Aniyomi

View file

@ -19,7 +19,7 @@
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> android:maxSdkVersion="29" />
<uses-permission <uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> <!-- For background jobs --> android:maxSdkVersion="32" /> <!-- For background jobs -->
@ -53,11 +53,13 @@
android:label="@string/app_name" android:label="@string/app_name"
android:largeHeap="true" android:largeHeap="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:enableOnBackInvokedCallback="true"
android:roundIcon="${icon_placeholder_round}" android:roundIcon="${icon_placeholder_round}"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Dantotsu" android:theme="@style/Theme.Dantotsu"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:ignore="AllowBackup"> tools:ignore="AllowBackup"
tools:targetApi="tiramisu">
<receiver <receiver
android:name=".widgets.CurrentlyAiringWidget" android:name=".widgets.CurrentlyAiringWidget"
android:exported="false"> android:exported="false">
@ -117,6 +119,7 @@
<activity <activity
android:name=".profile.activity.FeedActivity" android:name=".profile.activity.FeedActivity"
android:label="Inbox Activity" android:label="Inbox Activity"
android:configChanges="orientation|screenSize|screenLayout"
android:parentActivityName=".MainActivity" > android:parentActivityName=".MainActivity" >
</activity> </activity>
<activity <activity
@ -299,11 +302,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity" android:name="eu.kanade.tachiyomi.extension.util.ExtensionInstallActivity"
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity
android:name="eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallActivity"
android:exported="false" android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar" /> android:theme="@android:style/Theme.Translucent.NoTitleBar" />
@ -354,11 +353,7 @@
</intent-filter> </intent-filter>
</service> </service>
<service <service
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallService" android:name="eu.kanade.tachiyomi.extension.util.ExtensionInstallService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name="eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallService"
android:exported="false" android:exported="false"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
<service <service

View file

@ -86,7 +86,7 @@ class App : MultiDexApplication() {
Thread.setDefaultUncaughtExceptionHandler(FinalExceptionHandler()) Thread.setDefaultUncaughtExceptionHandler(FinalExceptionHandler())
Logger.log("App: Logging started") Logger.log("App: Logging started")
initializeNetwork(baseContext) initializeNetwork()
setupNotificationChannels() setupNotificationChannels()
if (!LogcatLogger.isInstalled) { if (!LogcatLogger.isInstalled) {
@ -154,6 +154,9 @@ class App : MultiDexApplication() {
companion object { companion object {
private var instance: App? = null private var instance: App? = null
/** Reference to the application context.
*
* USE WITH EXTREME CAUTION!**/
var context: Context? = null var context: Context? = null
fun currentContext(): Context? { fun currentContext(): Context? {
return instance?.mFTActivityLifecycleCallbacks?.currentActivity ?: context return instance?.mFTActivityLifecycleCallbacks?.currentActivity ?: context

View file

@ -10,11 +10,13 @@ import android.app.PendingIntent
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources
import android.content.res.Resources.getSystem import android.content.res.Resources.getSystem
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
@ -182,6 +184,10 @@ fun currActivity(): Activity? {
var loadMedia: Int? = null var loadMedia: Int? = null
var loadIsMAL = false var loadIsMAL = false
val Int.toPx get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics
).toInt()
fun initActivity(a: Activity) { fun initActivity(a: Activity) {
val window = a.window val window = a.window
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
@ -201,6 +207,7 @@ fun initActivity(a: Activity) {
ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content)) ViewCompat.getRootWindowInsets(window.decorView.findViewById(android.R.id.content))
?.apply { ?.apply {
navBarHeight = this.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom navBarHeight = this.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) navBarHeight += 48.toPx
} }
} }
WindowInsetsControllerCompat( WindowInsetsControllerCompat(
@ -222,6 +229,7 @@ fun initActivity(a: Activity) {
statusBarHeight = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top statusBarHeight = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()).top
navBarHeight = navBarHeight =
windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) navBarHeight += 48.toPx
} }
} }
if (a !is MainActivity) a.setNavigationTheme() if (a !is MainActivity) a.setNavigationTheme()
@ -394,7 +402,6 @@ class InputFilterMinMax(
return "" return ""
} }
@SuppressLint("SetTextI18n")
private fun isInRange(a: Double, b: Double, c: Double): Boolean { private fun isInRange(a: Double, b: Double, c: Double): Boolean {
val statusStrings = currContext()!!.resources.getStringArray(R.array.status_manga)[2] 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) { fun saveImageToDownloads(title: String, bitmap: Bitmap, context: Activity) {
FileProvider.getUriForFile( FileProvider.getUriForFile(
context, context,
@ -897,9 +921,9 @@ fun copyToClipboard(string: String, toast: Boolean = true) {
} }
} }
@SuppressLint("SetTextI18n")
fun countDown(media: Media, view: ViewGroup) { 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) val v = ItemCountDownBinding.inflate(LayoutInflater.from(view.context), view, false)
view.addView(v.root, 0) view.addView(v.root, 0)
v.mediaCountdownText.text = v.mediaCountdownText.text =
@ -1000,6 +1024,10 @@ class EmptyAdapter(private val count: Int) : RecyclerView.Adapter<RecyclerView.V
inner class EmptyViewHolder(view: View) : RecyclerView.ViewHolder(view) inner class EmptyViewHolder(view: View) : RecyclerView.ViewHolder(view)
} }
fun getAppString(res: Int): String {
return currContext()!!.getString(res) ?: ""
}
fun toast(string: String?) { fun toast(string: String?) {
if (string != null) { if (string != null) {
Logger.log(string) 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? { fun snackString(s: String?, activity: Activity? = null, clipboard: String? = null): Snackbar? {
try { //I have no idea why this sometimes crashes for some people... try { //I have no idea why this sometimes crashes for some people...
if (s != null) { if (s != null) {
@ -1050,6 +1082,10 @@ fun snackString(s: String?, activity: Activity? = null, clipboard: String? = nul
return null 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>) : open class NoPaddingArrayAdapter<T>(context: Context, layoutId: Int, items: List<T>) :
ArrayAdapter<T>(context, layoutId, items) { ArrayAdapter<T>(context, layoutId, items) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {

View file

@ -5,7 +5,6 @@ import android.annotation.SuppressLint
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.net.Uri import android.net.Uri
@ -14,7 +13,6 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.Settings import android.provider.Settings
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -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) { 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 currentColor = backgroundDrawable.color?.defaultColor ?: 0
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xF9000000.toInt() val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xF9000000.toInt()
backgroundDrawable.setColor(semiTransparentColor) 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 offset = try {
val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android") val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android")
@ -339,7 +337,7 @@ class MainActivity : AppCompatActivity() {
startActivity(Intent(this, NoInternet::class.java)) startActivity(Intent(this, NoInternet::class.java))
} else { } else {
val model: AnilistHomeViewModel by viewModels() val model: AnilistHomeViewModel by viewModels()
model.genres.observe(this) { it -> model.genres.observe(this) {
if (it != null) { if (it != null) {
if (it) { if (it) {
val navbar = binding.includedNavbar.navbar val navbar = binding.includedNavbar.navbar
@ -364,7 +362,7 @@ class MainActivity : AppCompatActivity() {
mainViewPager.setCurrentItem(newIndex, false) mainViewPager.setCurrentItem(newIndex, false)
} }
}) })
if (mainViewPager.getCurrentItem() != selectedOption) { if (mainViewPager.currentItem != selectedOption) {
navbar.selectTabAt(selectedOption) navbar.selectTabAt(selectedOption)
mainViewPager.post { mainViewPager.post {
mainViewPager.setCurrentItem( mainViewPager.setCurrentItem(
@ -467,18 +465,12 @@ class MainActivity : AppCompatActivity() {
window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent) 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) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
val margin = if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 8 else 32
val params : ViewGroup.MarginLayoutParams = val params : ViewGroup.MarginLayoutParams =
binding.includedNavbar.navbar.layoutParams as ViewGroup.MarginLayoutParams binding.includedNavbar.navbar.layoutParams as ViewGroup.MarginLayoutParams
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) params.updateMargins(bottom = margin.toPx)
params.updateMargins(bottom = 8.toPx)
else
params.updateMargins(bottom = 32.toPx)
} }
private fun passwordAlertDialog(callback: (CharArray?) -> Unit) { private fun passwordAlertDialog(callback: (CharArray?) -> Unit) {

View file

@ -1,6 +1,5 @@
package ani.dantotsu package ani.dantotsu
import android.content.Context
import android.os.Build import android.os.Build
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import ani.dantotsu.others.webview.CloudFlare import ani.dantotsu.others.webview.CloudFlare
@ -35,7 +34,7 @@ lateinit var defaultHeaders: Map<String, String>
lateinit var okHttpClient: OkHttpClient lateinit var okHttpClient: OkHttpClient
lateinit var client: Requests lateinit var client: Requests
fun initializeNetwork(context: Context) { fun initializeNetwork() {
val networkHelper = Injekt.get<NetworkHelper>() val networkHelper = Injekt.get<NetworkHelper>()

View file

@ -387,6 +387,7 @@ class AnilistQueries {
returnArray.addAll(map.values) returnArray.addAll(map.values)
return returnArray return returnArray
} }
@Suppress("UNCHECKED_CAST")
val list = PrefManager.getNullableCustomVal( val list = PrefManager.getNullableCustomVal(
"continueAnimeList", "continueAnimeList",
listOf<Int>(), listOf<Int>(),
@ -544,6 +545,7 @@ class AnilistQueries {
returnMap["current$type"] = returnArray returnMap["current$type"] = returnArray
return return
} }
@Suppress("UNCHECKED_CAST")
val list = PrefManager.getNullableCustomVal( val list = PrefManager.getNullableCustomVal(
"continueAnimeList", "continueAnimeList",
listOf<Int>(), listOf<Int>(),
@ -573,6 +575,7 @@ class AnilistQueries {
subMap[m.id] = m subMap[m.id] = m
} }
} }
@Suppress("UNCHECKED_CAST")
val list = PrefManager.getNullableCustomVal( val list = PrefManager.getNullableCustomVal(
"continueAnimeList", "continueAnimeList",
listOf<Int>(), listOf<Int>(),
@ -734,7 +737,7 @@ class AnilistQueries {
} }
sorted["All"] = all 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) else PrefManager.getVal(PrefName.MangaListSortOrder)
val sort = listSort ?: sortOrder ?: options?.rowOrder val sort = listSort ?: sortOrder ?: options?.rowOrder
for (i in sorted.keys) { for (i in sorted.keys) {

View file

@ -112,8 +112,8 @@ class AnilistHomeViewModel : ViewModel() {
suspend fun loadMain(context: FragmentActivity) { suspend fun loadMain(context: FragmentActivity) {
Anilist.getSavedToken() Anilist.getSavedToken()
MAL.getSavedToken(context) MAL.getSavedToken()
Discord.getSavedToken(context) Discord.getSavedToken()
if (!BuildConfig.FLAVOR.contains("fdroid")) { if (!BuildConfig.FLAVOR.contains("fdroid")) {
if (PrefManager.getVal(PrefName.CheckUpdate)) AppUpdater.check(context) if (PrefManager.getVal(PrefName.CheckUpdate)) AppUpdater.check(context)
} }
@ -159,7 +159,7 @@ class AnilistAnimeViewModel : ViewModel() {
fun getPopular(): LiveData<SearchResults?> = animePopular fun getPopular(): LiveData<SearchResults?> = animePopular
suspend fun loadPopular( suspend fun loadPopular(
type: String, type: String,
search_val: String? = null, searchVal: String? = null,
genres: ArrayList<String>? = null, genres: ArrayList<String>? = null,
sort: String = Anilist.sortBy[1], sort: String = Anilist.sortBy[1],
onList: Boolean = true, onList: Boolean = true,
@ -167,7 +167,7 @@ class AnilistAnimeViewModel : ViewModel() {
animePopular.postValue( animePopular.postValue(
Anilist.query.search( Anilist.query.search(
type, type,
search = search_val, search = searchVal,
onList = if (onList) null else false, onList = if (onList) null else false,
sort = sort, sort = sort,
genres = genres genres = genres
@ -231,7 +231,7 @@ class AnilistMangaViewModel : ViewModel() {
fun getPopular(): LiveData<SearchResults?> = mangaPopular fun getPopular(): LiveData<SearchResults?> = mangaPopular
suspend fun loadPopular( suspend fun loadPopular(
type: String, type: String,
search_val: String? = null, searchVal: String? = null,
genres: ArrayList<String>? = null, genres: ArrayList<String>? = null,
sort: String = Anilist.sortBy[1], sort: String = Anilist.sortBy[1],
onList: Boolean = true, onList: Boolean = true,
@ -239,7 +239,7 @@ class AnilistMangaViewModel : ViewModel() {
mangaPopular.postValue( mangaPopular.postValue(
Anilist.query.search( Anilist.query.search(
type, type,
search = search_val, search = searchVal,
onList = if (onList) null else false, onList = if (onList) null else false,
sort = sort, sort = sort,
genres = genres genres = genres

View file

@ -20,14 +20,14 @@ object Discord {
var avatar: String? = null var avatar: String? = null
fun getSavedToken(context: Context): Boolean { fun getSavedToken(): Boolean {
token = PrefManager.getVal( token = PrefManager.getVal(
PrefName.DiscordToken, null as String? PrefName.DiscordToken, null as String?
) )
return token != null return token != null
} }
fun saveToken(context: Context, token: String) { fun saveToken(token: String) {
PrefManager.setVal(PrefName.DiscordToken, token) PrefManager.setVal(PrefName.DiscordToken, token)
} }

View file

@ -5,16 +5,12 @@ import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import android.provider.MediaStore
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
@ -37,7 +33,6 @@ import okhttp3.Response
import okhttp3.WebSocket import okhttp3.WebSocket
import okhttp3.WebSocketListener import okhttp3.WebSocketListener
import java.io.File import java.io.File
import java.io.OutputStreamWriter
class DiscordService : Service() { class DiscordService : Service() {
private var heartbeat: Int = 0 private var heartbeat: Int = 0
@ -162,8 +157,8 @@ class DiscordService : Service() {
inner class DiscordWebSocketListener : WebSocketListener() { inner class DiscordWebSocketListener : WebSocketListener() {
var retryAttempts = 0 private var retryAttempts = 0
val maxRetryAttempts = 10 private val maxRetryAttempts = 10
override fun onOpen(webSocket: WebSocket, response: Response) { override fun onOpen(webSocket: WebSocket, response: Response) {
super.onOpen(webSocket, response) super.onOpen(webSocket, response)
this@DiscordService.webSocket = webSocket this@DiscordService.webSocket = webSocket
@ -232,7 +227,7 @@ class DiscordService : Service() {
resume() resume()
resume = false resume = false
} else { } else {
identify(webSocket, baseContext) identify(webSocket)
log("WebSocket: Identified") log("WebSocket: Identified")
} }
} }
@ -245,13 +240,13 @@ class DiscordService : Service() {
} }
} }
fun identify(webSocket: WebSocket, context: Context) { private fun identify(webSocket: WebSocket) {
val properties = JsonObject() val properties = JsonObject()
properties.addProperty("os", "linux") properties.addProperty("os", "linux")
properties.addProperty("browser", "unknown") properties.addProperty("browser", "unknown")
properties.addProperty("device", "unknown") properties.addProperty("device", "unknown")
val d = JsonObject() val d = JsonObject()
d.addProperty("token", getToken(context)) d.addProperty("token", getToken())
d.addProperty("intents", 0) d.addProperty("intents", 0)
d.add("properties", properties) d.add("properties", properties)
val payload = JsonObject() 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?) val token = PrefManager.getVal(PrefName.DiscordToken, null as String?)
return if (token == null) { return if (token == null) {
log("WebSocket: Token not found") log("WebSocket: Token not found")
@ -375,10 +370,10 @@ class DiscordService : Service() {
log("WebSocket: Simple Test Presence Saved") log("WebSocket: Simple Test Presence Saved")
} }
fun setPresence(String: String) { fun setPresence(string: String) {
log("WebSocket: Sending Presence payload") log("WebSocket: Sending Presence payload")
log(String) log(string)
webSocket.send(String) webSocket.send(string)
} }
fun log(string: String) { fun log(string: String) {
@ -388,7 +383,7 @@ class DiscordService : Service() {
fun resume() { fun resume() {
log("Sending Resume payload") log("Sending Resume payload")
val d = JsonObject() val d = JsonObject()
d.addProperty("token", getToken(baseContext)) d.addProperty("token", getToken())
d.addProperty("session_id", sessionId) d.addProperty("session_id", sessionId)
d.addProperty("seq", sequence) d.addProperty("seq", sequence)
val json = JsonObject() val json = JsonObject()
@ -404,8 +399,7 @@ class DiscordService : Service() {
Thread.sleep(heartbeat.toLong()) Thread.sleep(heartbeat.toLong())
heartbeatSend(webSocket, sequence) heartbeatSend(webSocket, sequence)
log("WebSocket: Heartbeat Sent") log("WebSocket: Heartbeat Sent")
} catch (e: InterruptedException) { } catch (ignored: InterruptedException) { }
}
} }
} }

View file

@ -75,7 +75,7 @@ class Login : AppCompatActivity() {
} }
Toast.makeText(this, "Logged in successfully", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Logged in successfully", Toast.LENGTH_SHORT).show()
finish() finish()
saveToken(this, token) saveToken(token)
startMainActivity(this@Login) startMainActivity(this@Login)
} }

View file

@ -5,7 +5,6 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.fragment.app.FragmentActivity
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.client import ani.dantotsu.client
import ani.dantotsu.currContext import ani.dantotsu.currContext
@ -64,7 +63,7 @@ object MAL {
} }
suspend fun getSavedToken(context: FragmentActivity): Boolean { suspend fun getSavedToken(): Boolean {
return tryWithSuspend(false) { return tryWithSuspend(false) {
var res: ResponseToken = var res: ResponseToken =
PrefManager.getNullableVal<ResponseToken>(PrefName.MALToken, null) PrefManager.getNullableVal<ResponseToken>(PrefName.MALToken, null)
@ -77,7 +76,7 @@ object MAL {
} ?: false } ?: false
} }
fun removeSavedToken(context: Context) { fun removeSavedToken() {
token = null token = null
username = null username = null
userid = null userid = null

View file

@ -3,6 +3,7 @@ package ani.dantotsu.download
import android.content.Context import android.content.Context
import android.os.Environment import android.os.Environment
import android.widget.Toast import android.widget.Toast
import ani.dantotsu.media.MediaType
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import com.google.gson.Gson import com.google.gson.Gson
@ -15,11 +16,11 @@ class DownloadsManager(private val context: Context) {
private val downloadsList = loadDownloads().toMutableList() private val downloadsList = loadDownloads().toMutableList()
val mangaDownloadedTypes: List<DownloadedType> val mangaDownloadedTypes: List<DownloadedType>
get() = downloadsList.filter { it.type == DownloadedType.Type.MANGA } get() = downloadsList.filter { it.type == MediaType.MANGA }
val animeDownloadedTypes: List<DownloadedType> val animeDownloadedTypes: List<DownloadedType>
get() = downloadsList.filter { it.type == DownloadedType.Type.ANIME } get() = downloadsList.filter { it.type == MediaType.ANIME }
val novelDownloadedTypes: List<DownloadedType> val novelDownloadedTypes: List<DownloadedType>
get() = downloadsList.filter { it.type == DownloadedType.Type.NOVEL } get() = downloadsList.filter { it.type == MediaType.NOVEL }
private fun saveDownloads() { private fun saveDownloads() {
val jsonString = gson.toJson(downloadsList) val jsonString = gson.toJson(downloadsList)
@ -47,14 +48,8 @@ class DownloadsManager(private val context: Context) {
saveDownloads() saveDownloads()
} }
fun removeMedia(title: String, type: DownloadedType.Type) { fun removeMedia(title: String, type: MediaType) {
val subDirectory = if (type == DownloadedType.Type.MANGA) { val subDirectory = type.asText()
"Manga"
} else if (type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
}
val directory = File( val directory = File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$subDirectory/$title" "Dantotsu/$subDirectory/$title"
@ -71,53 +66,45 @@ class DownloadsManager(private val context: Context) {
cleanDownloads() cleanDownloads()
} }
when (type) { when (type) {
DownloadedType.Type.MANGA -> { MediaType.MANGA -> {
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.MANGA } downloadsList.removeAll { it.title == title && it.type == MediaType.MANGA }
} }
DownloadedType.Type.ANIME -> { MediaType.ANIME -> {
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.ANIME } downloadsList.removeAll { it.title == title && it.type == MediaType.ANIME }
} }
DownloadedType.Type.NOVEL -> { MediaType.NOVEL -> {
downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.NOVEL } downloadsList.removeAll { it.title == title && it.type == MediaType.NOVEL }
} }
} }
saveDownloads() saveDownloads()
} }
private fun cleanDownloads() { private fun cleanDownloads() {
cleanDownload(DownloadedType.Type.MANGA) cleanDownload(MediaType.MANGA)
cleanDownload(DownloadedType.Type.ANIME) cleanDownload(MediaType.ANIME)
cleanDownload(DownloadedType.Type.NOVEL) cleanDownload(MediaType.NOVEL)
} }
private fun cleanDownload(type: DownloadedType.Type) { private fun cleanDownload(type: MediaType) {
// remove all folders that are not in the downloads list // remove all folders that are not in the downloads list
val subDirectory = if (type == DownloadedType.Type.MANGA) { val subDirectory = type.asText()
"Manga"
} else if (type == DownloadedType.Type.ANIME) {
"Anime"
} else {
"Novel"
}
val directory = File( val directory = File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$subDirectory" "Dantotsu/$subDirectory"
) )
val downloadsSubLists = if (type == DownloadedType.Type.MANGA) { val downloadsSubLists = when (type) {
mangaDownloadedTypes MediaType.MANGA -> mangaDownloadedTypes
} else if (type == DownloadedType.Type.ANIME) { MediaType.ANIME -> animeDownloadedTypes
animeDownloadedTypes else -> novelDownloadedTypes
} else {
novelDownloadedTypes
} }
if (directory.exists()) { if (directory.exists()) {
val files = directory.listFiles() val files = directory.listFiles()
if (files != null) { if (files != null) {
for (file in files) { for (file in files) {
if (!downloadsSubLists.any { it.title == file.name }) { 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) 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) { return if (type == null) {
downloadsList.any { it.title == title && it.chapter == chapter } downloadsList.any { it.title == title && it.chapter == chapter }
} else { } else {
@ -162,22 +149,26 @@ class DownloadsManager(private val context: Context) {
} }
private fun removeDirectory(downloadedType: DownloadedType) { private fun removeDirectory(downloadedType: DownloadedType) {
val directory = if (downloadedType.type == DownloadedType.Type.MANGA) { val directory = when (downloadedType.type) {
MediaType.MANGA -> {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}" "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
) )
} else if (downloadedType.type == DownloadedType.Type.ANIME) { }
MediaType.ANIME -> {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}" "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
) )
} else { }
else -> {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}" "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
) )
} }
}
// Check if the directory exists and delete it recursively // Check if the directory exists and delete it recursively
if (directory.exists()) { if (directory.exists()) {
@ -193,22 +184,26 @@ class DownloadsManager(private val context: Context) {
} }
fun exportDownloads(downloadedType: DownloadedType) { //copies to the downloads folder available to the user fun exportDownloads(downloadedType: DownloadedType) { //copies to the downloads folder available to the user
val directory = if (downloadedType.type == DownloadedType.Type.MANGA) { val directory = when (downloadedType.type) {
MediaType.MANGA -> {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}" "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}"
) )
} else if (downloadedType.type == DownloadedType.Type.ANIME) { }
MediaType.ANIME -> {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}" "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}"
) )
} else { }
else -> {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}" "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}"
) )
} }
}
val destination = File( val destination = File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/${downloadedType.title}/${downloadedType.chapter}" "Dantotsu/${downloadedType.title}/${downloadedType.chapter}"
@ -225,14 +220,18 @@ class DownloadsManager(private val context: Context) {
} }
} }
fun purgeDownloads(type: DownloadedType.Type) { fun purgeDownloads(type: MediaType) {
val directory = if (type == DownloadedType.Type.MANGA) { val directory = when (type) {
MediaType.MANGA -> {
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga") File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga")
} else if (type == DownloadedType.Type.ANIME) { }
MediaType.ANIME -> {
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime") File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime")
} else { }
else -> {
File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel") File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel")
} }
}
if (directory.exists()) { if (directory.exists()) {
val deleted = directory.deleteRecursively() val deleted = directory.deleteRecursively()
if (deleted) { if (deleted) {
@ -255,11 +254,12 @@ class DownloadsManager(private val context: Context) {
fun getDirectory( fun getDirectory(
context: Context, context: Context,
type: DownloadedType.Type, type: MediaType,
title: String, title: String,
chapter: String? = null chapter: String? = null
): File { ): File {
return if (type == DownloadedType.Type.MANGA) { return when (type) {
MediaType.MANGA -> {
if (chapter != null) { if (chapter != null) {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
@ -271,7 +271,8 @@ class DownloadsManager(private val context: Context) {
"$mangaLocation/$title" "$mangaLocation/$title"
) )
} }
} else if (type == DownloadedType.Type.ANIME) { }
MediaType.ANIME -> {
if (chapter != null) { if (chapter != null) {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
@ -283,7 +284,8 @@ class DownloadsManager(private val context: Context) {
"$animeLocation/$title" "$animeLocation/$title"
) )
} }
} else { }
else -> {
if (chapter != null) { if (chapter != null) {
File( File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
@ -298,13 +300,7 @@ class DownloadsManager(private val context: Context) {
} }
} }
} }
}
} }
data class DownloadedType(val title: String, val chapter: String, val type: Type) : Serializable { data class DownloadedType(val title: String, val chapter: String, val type: MediaType) : Serializable
enum class Type {
MANGA,
ANIME,
NOVEL
}
}

View file

@ -27,14 +27,15 @@ import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.video.ExoplayerDownloadService import ani.dantotsu.download.video.ExoplayerDownloadService
import ani.dantotsu.download.video.Helper import ani.dantotsu.download.video.Helper
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.SubtitleDownloader import ani.dantotsu.media.SubtitleDownloader
import ani.dantotsu.media.anime.AnimeWatchFragment import ani.dantotsu.media.anime.AnimeWatchFragment
import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.Video import ani.dantotsu.parsers.Video
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
@ -242,7 +243,7 @@ class AnimeDownloaderService : Service() {
DownloadedType( DownloadedType(
task.title, task.title,
task.episode, task.episode,
DownloadedType.Type.ANIME, MediaType.ANIME,
) )
) )
} }
@ -273,7 +274,7 @@ class AnimeDownloaderService : Service() {
DownloadedType( DownloadedType(
task.title, task.title,
task.episode, task.episode,
DownloadedType.Type.ANIME, MediaType.ANIME,
) )
) )
Injekt.get<CrashlyticsInterface>().logException( Injekt.get<CrashlyticsInterface>().logException(
@ -302,7 +303,7 @@ class AnimeDownloaderService : Service() {
DownloadedType( DownloadedType(
task.title, task.title,
task.episode, task.episode,
DownloadedType.Type.ANIME, MediaType.ANIME,
) )
) )
currentTasks.removeAll { it.getTaskName() == task.getTaskName() } currentTasks.removeAll { it.getTaskName() == task.getTaskName() }

View file

@ -1,7 +1,6 @@
package ani.dantotsu.download.anime package ani.dantotsu.download.anime
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -38,7 +37,6 @@ class OfflineAnimeAdapter(
return position.toLong() return position.toLong()
} }
@SuppressLint("SetTextI18n")
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view: View = convertView ?: when (style) { val view: View = convertView ?: when (style) {
@ -61,14 +59,14 @@ class OfflineAnimeAdapter(
if (style == 0) { if (style == 0) {
val bannerView = view.findViewById<ImageView>(R.id.itemCompactBanner) // for large view val bannerView = view.findViewById<ImageView>(R.id.itemCompactBanner) // for large view
val episodes = view.findViewById<TextView>(R.id.itemTotal) val episodes = view.findViewById<TextView>(R.id.itemTotal)
episodes.text = " Episodes" episodes.text = context.getString(R.string.episodes)
bannerView.setImageURI(item.banner ?: item.image) bannerView.setImageURI(item.banner ?: item.image)
totalepisodes.text = item.totalEpisodeList totalepisodes.text = item.totalEpisodeList
} else if (style == 1) { } else if (style == 1) {
val watchedEpisodes = val watchedEpisodes =
view.findViewById<TextView>(R.id.itemCompactUserProgress) // for compact view view.findViewById<TextView>(R.id.itemCompactUserProgress) // for compact view
watchedEpisodes.text = item.watchedEpisode watchedEpisodes.text = item.watchedEpisode
totalepisodes.text = " | " + item.totalEpisode totalepisodes.text = context.getString(R.string.total_divider, item.totalEpisode)
} }
// Bind item data to the views // Bind item data to the views

View file

@ -22,6 +22,7 @@ import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@ -33,15 +34,16 @@ import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.SettingsDialogFragment import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
@ -187,8 +189,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
gridView.setOnItemLongClickListener { _, _, position, _ -> gridView.setOnItemLongClickListener { _, _, position, _ ->
// Get the OfflineAnimeModel that was clicked // Get the OfflineAnimeModel that was clicked
val item = adapter.getItem(position) as OfflineAnimeModel val item = adapter.getItem(position) as OfflineAnimeModel
val type: DownloadedType.Type = val type: MediaType = MediaType.ANIME
DownloadedType.Type.ANIME
// Alert dialog to confirm deletion // Alert dialog to confirm deletion
val builder = val builder =
@ -250,7 +251,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
val visibility = first != null && first.top < 0 val visibility = first != null && first.top < 0
scrollTop.translationY = scrollTop.translationY =
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat() -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE scrollTop.isVisible = visibility
} }
}) })
initActivity(requireActivity()) initActivity(requireActivity())
@ -292,11 +293,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
} }
private fun getMedia(downloadedType: DownloadedType): Media? { private fun getMedia(downloadedType: DownloadedType): Media? {
val type = when (downloadedType.type) { val type = downloadedType.type.asText()
DownloadedType.Type.MANGA -> "Manga"
DownloadedType.Type.ANIME -> "Anime"
else -> "Novel"
}
val directory = File( val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$type/${downloadedType.title}" "Dantotsu/$type/${downloadedType.title}"
@ -326,11 +323,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
} }
private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel { private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel {
val type = when (downloadedType.type) { val type = downloadedType.type.asText()
DownloadedType.Type.MANGA -> "Manga"
DownloadedType.Type.ANIME -> "Anime"
else -> "Novel"
}
val directory = File( val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$type/${downloadedType.title}" "Dantotsu/$type/${downloadedType.title}"

View file

@ -21,8 +21,8 @@ import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.manga.ImageData 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_FAILED
import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FINISHED 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.ACTION_DOWNLOAD_STARTED
import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS
@ -211,8 +212,7 @@ class MangaDownloaderService : Service() {
while (bitmap == null && retryCount < task.retries) { while (bitmap == null && retryCount < task.retries) {
bitmap = image.fetchAndProcessImage( bitmap = image.fetchAndProcessImage(
image.page, image.page,
image.source, image.source
this@MangaDownloaderService
) )
retryCount++ retryCount++
} }
@ -246,7 +246,7 @@ class MangaDownloaderService : Service() {
DownloadedType( DownloadedType(
task.title, task.title,
task.chapter, task.chapter,
DownloadedType.Type.MANGA MediaType.MANGA
) )
) )
broadcastDownloadFinished(task.chapter) broadcastDownloadFinished(task.chapter)

View file

@ -37,7 +37,6 @@ class OfflineMangaAdapter(
return position.toLong() return position.toLong()
} }
@SuppressLint("SetTextI18n")
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view: View = convertView ?: when (style) { val view: View = convertView ?: when (style) {
@ -60,14 +59,14 @@ class OfflineMangaAdapter(
if (style == 0) { if (style == 0) {
val bannerView = view.findViewById<ImageView>(R.id.itemCompactBanner) // for large view val bannerView = view.findViewById<ImageView>(R.id.itemCompactBanner) // for large view
val chapters = view.findViewById<TextView>(R.id.itemTotal) val chapters = view.findViewById<TextView>(R.id.itemTotal)
chapters.text = " Chapters" chapters.text = context.getString(R.string.chapters)
bannerView.setImageURI(item.banner ?: item.image) bannerView.setImageURI(item.banner ?: item.image)
totalChapter.text = item.totalChapter totalChapter.text = item.totalChapter
} else if (style == 1) { } else if (style == 1) {
val readChapter = val readChapter =
view.findViewById<TextView>(R.id.itemCompactUserProgress) // for compact view view.findViewById<TextView>(R.id.itemCompactUserProgress) // for compact view
readChapter.text = item.readChapter readChapter.text = item.readChapter
totalChapter.text = " | " + item.totalChapter totalChapter.text = context.getString(R.string.total_divider, item.totalChapter)
} }
// Bind item data to the views // Bind item data to the views

View file

@ -20,6 +20,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import ani.dantotsu.R import ani.dantotsu.R
@ -30,15 +31,16 @@ import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.SettingsDialogFragment import ani.dantotsu.settings.SettingsDialogFragment
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
@ -178,11 +180,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
gridView.setOnItemLongClickListener { _, _, position, _ -> gridView.setOnItemLongClickListener { _, _, position, _ ->
// Get the OfflineMangaModel that was clicked // Get the OfflineMangaModel that was clicked
val item = adapter.getItem(position) as OfflineMangaModel val item = adapter.getItem(position) as OfflineMangaModel
val type: DownloadedType.Type = val type: MediaType =
if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) { if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) {
DownloadedType.Type.MANGA MediaType.MANGA
} else { } else {
DownloadedType.Type.NOVEL MediaType.NOVEL
} }
// Alert dialog to confirm deletion // Alert dialog to confirm deletion
val builder = val builder =
@ -234,7 +236,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
) { ) {
val first = view.getChildAt(0) val first = view.getChildAt(0)
val visibility = first != null && first.top < 0 val visibility = first != null && first.top < 0
scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE scrollTop.isVisible = visibility
scrollTop.translationY = scrollTop.translationY =
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat() -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
} }
@ -288,11 +290,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
} }
private fun getMedia(downloadedType: DownloadedType): Media? { private fun getMedia(downloadedType: DownloadedType): Media? {
val type = when (downloadedType.type) { val type = downloadedType.type.asText()
DownloadedType.Type.MANGA -> "Manga"
DownloadedType.Type.ANIME -> "Anime"
else -> "Novel"
}
val directory = File( val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$type/${downloadedType.title}" "Dantotsu/$type/${downloadedType.title}"
@ -316,11 +314,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
} }
private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel { private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel {
val type = when (downloadedType.type) { val type = downloadedType.type.asText()
DownloadedType.Type.MANGA -> "Manga"
DownloadedType.Type.ANIME -> "Anime"
else -> "Novel"
}
val directory = File( val directory = File(
currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
"Dantotsu/$type/${downloadedType.title}" "Dantotsu/$type/${downloadedType.title}"

View file

@ -20,10 +20,11 @@ import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.util.Logger
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.novel.NovelReadFragment import ani.dantotsu.media.novel.NovelReadFragment
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.InstanceCreator import com.google.gson.InstanceCreator
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
@ -335,7 +336,7 @@ class NovelDownloaderService : Service() {
DownloadedType( DownloadedType(
task.title, task.title,
task.chapter, task.chapter,
DownloadedType.Type.NOVEL MediaType.NOVEL
) )
) )
broadcastDownloadFinished(task.originalLink) broadcastDownloadFinished(task.originalLink)

View file

@ -9,7 +9,6 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -37,6 +36,7 @@ import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.download.anime.AnimeServiceDataSingleton import ani.dantotsu.download.anime.AnimeServiceDataSingleton
import ani.dantotsu.logError import ani.dantotsu.logError
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaType
import ani.dantotsu.okHttpClient import ani.dantotsu.okHttpClient
import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Subtitle
import ani.dantotsu.parsers.SubtitleType import ani.dantotsu.parsers.SubtitleType
@ -49,13 +49,14 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.* import java.util.concurrent.Executors
@SuppressLint("UnsafeOptInUsageError")
object Helper { object Helper {
private var simpleCache: SimpleCache? = null private var simpleCache: SimpleCache? = null
@SuppressLint("UnsafeOptInUsageError")
fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) { fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) {
val dataSourceFactory = DataSource.Factory { val dataSourceFactory = DataSource.Factory {
val dataSource: HttpDataSource = val dataSource: HttpDataSource =
@ -157,16 +158,14 @@ object Helper {
download: Download, download: Download,
finalException: Exception? finalException: Exception?
) { ) {
if (download.state == Download.STATE_COMPLETED) { when (download.state) {
Logger.log("Download Completed") Download.STATE_COMPLETED -> Logger.log("Download Completed")
} else if (download.state == Download.STATE_FAILED) { Download.STATE_FAILED -> Logger.log("Download Failed")
Logger.log("Download Failed") Download.STATE_STOPPED -> Logger.log("Download Stopped")
} else if (download.state == Download.STATE_STOPPED) { Download.STATE_QUEUED -> Logger.log("Download Queued")
Logger.log("Download Stopped") Download.STATE_DOWNLOADING -> Logger.log("Download Downloading")
} else if (download.state == Download.STATE_QUEUED) { Download.STATE_REMOVING -> Logger.log("Download Removing")
Logger.log("Download Queued") Download.STATE_RESTARTING -> Logger.log("Download Restarting")
} else if (download.state == Download.STATE_DOWNLOADING) {
Logger.log("Download Downloading")
} }
} }
} }
@ -220,7 +219,7 @@ object Helper {
val downloadsManger = Injekt.get<DownloadsManager>() val downloadsManger = Injekt.get<DownloadsManager>()
val downloadCheck = downloadsManger val downloadCheck = downloadsManger
.queryDownload(title, episode, DownloadedType.Type.ANIME) .queryDownload(title, episode, MediaType.ANIME)
if (downloadCheck) { if (downloadCheck) {
AlertDialog.Builder(context, R.style.MyPopup) AlertDialog.Builder(context, R.style.MyPopup)
@ -243,7 +242,7 @@ object Helper {
DownloadedType( DownloadedType(
title, title,
episode, episode,
DownloadedType.Type.ANIME MediaType.ANIME
) )
) )
AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask) AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask)

View file

@ -10,6 +10,7 @@ import android.view.ViewGroup
import android.view.animation.LayoutAnimationController import android.view.animation.LayoutAnimationController
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -21,6 +22,7 @@ import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemAnimePageBinding import ani.dantotsu.databinding.ItemAnimePageBinding
import ani.dantotsu.databinding.LayoutTrendingBinding
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.media.CalendarActivity import ani.dantotsu.media.CalendarActivity
import ani.dantotsu.media.GenreActivity import ani.dantotsu.media.GenreActivity
@ -41,6 +43,7 @@ import com.google.android.material.textfield.TextInputLayout
class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHolder>() { class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHolder>() {
val ready = MutableLiveData(false) val ready = MutableLiveData(false)
lateinit var binding: ItemAnimePageBinding lateinit var binding: ItemAnimePageBinding
private lateinit var trendingBinding: LayoutTrendingBinding
private var trendHandler: Handler? = null private var trendHandler: Handler? = null
private lateinit var trendRun: Runnable private lateinit var trendRun: Runnable
var trendingViewPager: ViewPager2? = null var trendingViewPager: ViewPager2? = null
@ -53,14 +56,15 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
override fun onBindViewHolder(holder: AnimePageViewHolder, position: Int) { override fun onBindViewHolder(holder: AnimePageViewHolder, position: Int) {
binding = holder.binding 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 currentColor = textInputLayout.boxBackgroundColor
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt() val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
textInputLayout.boxBackgroundColor = semiTransparentColor textInputLayout.boxBackgroundColor = semiTransparentColor
val materialCardView = val materialCardView =
holder.itemView.findViewById<MaterialCardView>(R.id.animeUserAvatarContainer) holder.itemView.findViewById<MaterialCardView>(R.id.userAvatarContainer)
materialCardView.setCardBackgroundColor(semiTransparentColor) materialCardView.setCardBackgroundColor(semiTransparentColor)
val typedValue = TypedValue() val typedValue = TypedValue()
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true) currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
@ -69,33 +73,29 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000 textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
materialCardView.setCardBackgroundColor((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 bottomMargin = (-108f).px
} }
updateAvatar() updateAvatar()
binding.animeSearchBar.hint = "ANIME" trendingBinding.searchBar.hint = "ANIME"
binding.animeSearchBarText.setOnClickListener { trendingBinding.searchBarText.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
it.context, it.context,
Intent(it.context, SearchActivity::class.java).putExtra("type", "ANIME"), Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"),
null null
) )
} }
binding.animeSearchBar.setEndIconOnClickListener { trendingBinding.userAvatar.setSafeOnClickListener {
binding.animeSearchBarText.performClick()
}
binding.animeUserAvatar.setSafeOnClickListener {
val dialogFragment = val dialogFragment =
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.ANIME) SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.ANIME)
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog") dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
} }
binding.animeUserAvatar.setOnLongClickListener { view -> trendingBinding.userAvatar.setOnLongClickListener { view ->
ContextCompat.startActivity( ContextCompat.startActivity(
view.context, view.context,
Intent(view.context, ProfileActivity::class.java) Intent(view.context, ProfileActivity::class.java)
@ -104,8 +104,12 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
false false
} }
binding.animeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE trendingBinding.searchBar.setEndIconOnClickListener {
binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString() trendingBinding.searchBar.performClick()
}
trendingBinding.notificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
listOf( listOf(
binding.animePreviousSeason, binding.animePreviousSeason,
@ -134,8 +138,7 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
) )
} }
binding.animeIncludeList.visibility = binding.animeIncludeList.isVisible = Anilist.userid != null
if (Anilist.userid != null) View.VISIBLE else View.GONE
binding.animeIncludeList.isChecked = PrefManager.getVal(PrefName.PopularAnimeList) binding.animeIncludeList.isChecked = PrefManager.getVal(PrefName.PopularAnimeList)
@ -159,18 +162,17 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
} }
fun updateTrending(adaptor: MediaAdaptor) { fun updateTrending(adaptor: MediaAdaptor) {
binding.animeTrendingProgressBar.visibility = View.GONE trendingBinding.trendingProgressBar.visibility = View.GONE
binding.animeTrendingViewPager.adapter = adaptor trendingBinding.trendingViewPager.adapter = adaptor
binding.animeTrendingViewPager.offscreenPageLimit = 3 trendingBinding.trendingViewPager.offscreenPageLimit = 3
binding.animeTrendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER trendingBinding.trendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
binding.animeTrendingViewPager.setPageTransformer(MediaPageTransformer()) trendingBinding.trendingViewPager.setPageTransformer(MediaPageTransformer())
trendHandler = Handler(Looper.getMainLooper()) trendHandler = Handler(Looper.getMainLooper())
trendRun = Runnable { trendRun = Runnable {
binding.animeTrendingViewPager.currentItem = trendingBinding.trendingViewPager.currentItem += 1
binding.animeTrendingViewPager.currentItem + 1
} }
binding.animeTrendingViewPager.registerOnPageChangeCallback( trendingBinding.trendingViewPager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() { object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
@ -180,9 +182,9 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
} }
) )
binding.animeTrendingViewPager.layoutAnimation = trendingBinding.trendingViewPager.layoutAnimation =
LayoutAnimationController(setSlideIn(), 0.25f) LayoutAnimationController(setSlideIn(), 0.25f)
binding.animeTitleContainer.startAnimation(setSlideUp()) trendingBinding.titleContainer.startAnimation(setSlideUp())
binding.animeListContainer.layoutAnimation = binding.animeListContainer.layoutAnimation =
LayoutAnimationController(setSlideIn(), 0.25f) LayoutAnimationController(setSlideIn(), 0.25f)
binding.animeSeasonsCont.layoutAnimation = binding.animeSeasonsCont.layoutAnimation =
@ -210,16 +212,16 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
fun updateAvatar() { fun updateAvatar() {
if (Anilist.avatar != null && ready.value == true) { if (Anilist.avatar != null && ready.value == true) {
binding.animeUserAvatar.loadImage(Anilist.avatar) trendingBinding.userAvatar.loadImage(Anilist.avatar)
binding.animeUserAvatar.imageTintList = null trendingBinding.userAvatar.imageTintList = null
} }
} }
fun updateNotificationCount() { fun updateNotificationCount() {
if (this::binding.isInitialized) { if (this::binding.isInitialized) {
binding.animeNotificationCount.visibility = trendingBinding.notificationCount.visibility =
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString() trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
} }
} }

View file

@ -10,6 +10,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.LayoutAnimationController import android.view.animation.LayoutAnimationController
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -81,7 +82,7 @@ class HomeFragment : Fragment() {
if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.homeUserBg.pause() if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.homeUserBg.pause()
blurImage(binding.homeUserBg, Anilist.bg) blurImage(binding.homeUserBg, Anilist.bg)
binding.homeUserDataProgressBar.visibility = View.GONE 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.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
binding.homeAnimeList.setOnClickListener { binding.homeAnimeList.setOnClickListener {
@ -375,7 +376,7 @@ class HomeFragment : Fragment() {
override fun onResume() { override fun onResume() {
if (!model.loaded) Refresh.activity[1]!!.postValue(true) if (!model.loaded) Refresh.activity[1]!!.postValue(true)
if (_binding != null) { 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() binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString()
} }
super.onResume() super.onResume()

View file

@ -10,6 +10,7 @@ import android.view.ViewGroup
import android.view.animation.LayoutAnimationController import android.view.animation.LayoutAnimationController
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -21,6 +22,7 @@ import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemMangaPageBinding import ani.dantotsu.databinding.ItemMangaPageBinding
import ani.dantotsu.databinding.LayoutTrendingBinding
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.media.GenreActivity import ani.dantotsu.media.GenreActivity
import ani.dantotsu.media.MediaAdaptor import ani.dantotsu.media.MediaAdaptor
@ -40,6 +42,7 @@ import com.google.android.material.textfield.TextInputLayout
class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHolder>() { class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHolder>() {
val ready = MutableLiveData(false) val ready = MutableLiveData(false)
lateinit var binding: ItemMangaPageBinding lateinit var binding: ItemMangaPageBinding
private lateinit var trendingBinding: LayoutTrendingBinding
private var trendHandler: Handler? = null private var trendHandler: Handler? = null
private lateinit var trendRun: Runnable private lateinit var trendRun: Runnable
var trendingViewPager: ViewPager2? = null var trendingViewPager: ViewPager2? = null
@ -52,33 +55,34 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
override fun onBindViewHolder(holder: MangaPageViewHolder, position: Int) { override fun onBindViewHolder(holder: MangaPageViewHolder, position: Int) {
binding = holder.binding 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 currentColor = textInputLayout.boxBackgroundColor
val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt() val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt()
textInputLayout.boxBackgroundColor = semiTransparentColor textInputLayout.boxBackgroundColor = semiTransparentColor
val materialCardView = val materialCardView =
holder.itemView.findViewById<MaterialCardView>(R.id.mangaUserAvatarContainer) holder.itemView.findViewById<MaterialCardView>(R.id.userAvatarContainer)
materialCardView.setCardBackgroundColor(semiTransparentColor) materialCardView.setCardBackgroundColor(semiTransparentColor)
val typedValue = TypedValue() val typedValue = TypedValue()
currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true) currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
val color = typedValue.data val color = typedValue.data
textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt() textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000
materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt()) 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 bottomMargin = (-108f).px
} }
updateAvatar() updateAvatar()
binding.mangaNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0
binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString() trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
binding.mangaSearchBar.hint = "MANGA" trendingBinding.searchBar.hint = "MANGA"
binding.mangaSearchBarText.setOnClickListener { trendingBinding.searchBarText.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
it.context, it.context,
Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"), 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 = val dialogFragment =
SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.MANGA) SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.MANGA)
dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog") dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog")
} }
binding.mangaUserAvatar.setOnLongClickListener { view -> trendingBinding.userAvatar.setOnLongClickListener { view ->
ContextCompat.startActivity( ContextCompat.startActivity(
view.context, view.context,
Intent(view.context, ProfileActivity::class.java) Intent(view.context, ProfileActivity::class.java)
@ -100,8 +104,8 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
false false
} }
binding.mangaSearchBar.setEndIconOnClickListener { trendingBinding.searchBar.setEndIconOnClickListener {
binding.mangaSearchBarText.performClick() trendingBinding.searchBarText.performClick()
} }
binding.mangaGenreImage.loadImage("https://s4.anilist.co/file/anilistcdn/media/manga/banner/105778-wk5qQ7zAaTGl.jpg") 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 = binding.mangaIncludeList.isVisible = Anilist.userid != null
if (Anilist.userid != null) View.VISIBLE else View.GONE
binding.mangaIncludeList.isChecked = PrefManager.getVal(PrefName.PopularMangaList) binding.mangaIncludeList.isChecked = PrefManager.getVal(PrefName.PopularMangaList)
@ -148,16 +151,16 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
} }
fun updateTrending(adaptor: MediaAdaptor) { fun updateTrending(adaptor: MediaAdaptor) {
binding.mangaTrendingProgressBar.visibility = View.GONE trendingBinding.trendingProgressBar.visibility = View.GONE
binding.mangaTrendingViewPager.adapter = adaptor trendingBinding.trendingViewPager.adapter = adaptor
binding.mangaTrendingViewPager.offscreenPageLimit = 3 trendingBinding.trendingViewPager.offscreenPageLimit = 3
binding.mangaTrendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER trendingBinding.trendingViewPager.getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER
binding.mangaTrendingViewPager.setPageTransformer(MediaPageTransformer()) trendingBinding.trendingViewPager.setPageTransformer(MediaPageTransformer())
trendHandler = Handler(Looper.getMainLooper()) trendHandler = Handler(Looper.getMainLooper())
trendRun = Runnable { trendRun = Runnable {
binding.mangaTrendingViewPager.currentItem += 1 trendingBinding.trendingViewPager.currentItem += 1
} }
binding.mangaTrendingViewPager.registerOnPageChangeCallback( trendingBinding.trendingViewPager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() { object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
@ -167,9 +170,9 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
} }
) )
binding.mangaTrendingViewPager.layoutAnimation = trendingBinding.trendingViewPager.layoutAnimation =
LayoutAnimationController(setSlideIn(), 0.25f) LayoutAnimationController(setSlideIn(), 0.25f)
binding.mangaTitleContainer.startAnimation(setSlideUp()) trendingBinding.titleContainer.startAnimation(setSlideUp())
binding.mangaListContainer.layoutAnimation = binding.mangaListContainer.layoutAnimation =
LayoutAnimationController(setSlideIn(), 0.25f) LayoutAnimationController(setSlideIn(), 0.25f)
} }
@ -195,16 +198,16 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
fun updateAvatar() { fun updateAvatar() {
if (Anilist.avatar != null && ready.value == true) { if (Anilist.avatar != null && ready.value == true) {
binding.mangaUserAvatar.loadImage(Anilist.avatar) trendingBinding.userAvatar.loadImage(Anilist.avatar)
binding.mangaUserAvatar.imageTintList = null trendingBinding.userAvatar.imageTintList = null
} }
} }
fun updateNotificationCount() { fun updateNotificationCount() {
if (this::binding.isInitialized) { if (this::binding.isInitialized) {
binding.mangaNotificationCount.visibility = trendingBinding.notificationCount.visibility =
if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString() trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString()
} }
} }

View file

@ -24,7 +24,6 @@ class AuthorAdapter(
return AuthorViewHolder(binding) return AuthorViewHolder(binding)
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder:AuthorViewHolder, position: Int) { override fun onBindViewHolder(holder:AuthorViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
setAnimation(binding.root.context, holder.binding.root) setAnimation(binding.root.context, holder.binding.root)

View file

@ -6,7 +6,6 @@ import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.view.WindowManager
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -16,8 +15,8 @@ import androidx.lifecycle.lifecycleScope
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.Refresh import ani.dantotsu.Refresh
import ani.dantotsu.databinding.ActivityListBinding import ani.dantotsu.databinding.ActivityListBinding
import ani.dantotsu.hideSystemBarsExtendView
import ani.dantotsu.media.user.ListViewPagerAdapter import ani.dantotsu.media.user.ListViewPagerAdapter
import ani.dantotsu.navBarHeight
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
@ -34,7 +33,6 @@ class CalendarActivity : AppCompatActivity() {
private var selectedTabIdx = 1 private var selectedTabIdx = 1
private val model: OtherDetailsViewModel by viewModels() private val model: OtherDetailsViewModel by viewModels()
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -74,10 +72,7 @@ class CalendarActivity : AppCompatActivity() {
} else { } else {
binding.root.fitsSystemWindows = false binding.root.fitsSystemWindows = false
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
window.setFlags( hideSystemBarsExtendView()
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight topMargin = statusBarHeight
} }

View file

@ -24,12 +24,12 @@ class CharacterAdapter(
return CharacterViewHolder(binding) return CharacterViewHolder(binding)
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) { override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
setAnimation(binding.root.context, holder.binding.root) setAnimation(binding.root.context, holder.binding.root)
val character = characterList[position] val character = characterList[position]
binding.itemCompactRelation.text = character.role + " " val whitespace = "${character.role} "
binding.itemCompactRelation.text = whitespace
binding.itemCompactImage.loadImage(character.image) binding.itemCompactImage.loadImage(character.image)
binding.itemCompactTitle.text = character.name binding.itemCompactTitle.text = character.name
} }

View file

@ -8,6 +8,7 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils.clamp import androidx.core.math.MathUtils.clamp
import androidx.core.view.isGone
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -152,7 +153,7 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang
} }
override fun onResume() { override fun onResume() {
binding.characterProgress.visibility = if (!loaded) View.VISIBLE else View.GONE binding.characterProgress.isGone = loaded
super.onResume() super.onResume()
} }

View file

@ -20,15 +20,16 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
return GenreViewHolder(binding) return GenreViewHolder(binding)
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: GenreViewHolder, position: Int) { override fun onBindViewHolder(holder: GenreViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
val desc = val desc =
(if (character.age != "null") currActivity()!!.getString(R.string.age) + " " + character.age else "") + (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.dateOfBirth.toString() != "")
(if (character.gender != "null") currActivity()!!.getString(R.string.gender) + " " + when (character.gender) { "${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") +
"Male" -> currActivity()!!.getString(R.string.male) (if (character.gender != "null")
"Female" -> currActivity()!!.getString(R.string.female) 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 -> character.gender
} else "") + "\n" + character.description } else "") + "\n" + character.description

View file

@ -17,6 +17,7 @@ import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.util.Pair import androidx.core.util.Pair
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -85,7 +86,7 @@ class MediaAdaptor(
} }
@SuppressLint("SetTextI18n", "ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (type) { when (type) {
0 -> { 0 -> {
@ -94,8 +95,8 @@ class MediaAdaptor(
val media = mediaList?.getOrNull(position) val media = mediaList?.getOrNull(position)
if (media != null) { if (media != null) {
b.itemCompactImage.loadImage(media.cover) b.itemCompactImage.loadImage(media.cover)
b.itemCompactOngoing.visibility = b.itemCompactOngoing.isVisible =
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE media.status == currActivity()!!.getString(R.string.status_releasing)
b.itemCompactTitle.text = media.userPreferredName b.itemCompactTitle.text = media.userPreferredName
b.itemCompactScore.text = b.itemCompactScore.text =
((if (media.userScore == 0) (media.meanScore ((if (media.userScore == 0) (media.meanScore
@ -140,8 +141,8 @@ class MediaAdaptor(
if (media != null) { if (media != null) {
b.itemCompactImage.loadImage(media.cover) b.itemCompactImage.loadImage(media.cover)
blurImage(b.itemCompactBanner, media.banner ?: media.cover) blurImage(b.itemCompactBanner, media.banner ?: media.cover)
b.itemCompactOngoing.visibility = b.itemCompactOngoing.isVisible =
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE media.status == currActivity()!!.getString(R.string.status_releasing)
b.itemCompactTitle.text = media.userPreferredName b.itemCompactTitle.text = media.userPreferredName
b.itemCompactScore.text = b.itemCompactScore.text =
((if (media.userScore == 0) (media.meanScore ((if (media.userScore == 0) (media.meanScore
@ -188,8 +189,8 @@ class MediaAdaptor(
) )
) )
blurImage(b.itemCompactBanner, media.banner ?: media.cover) blurImage(b.itemCompactBanner, media.banner ?: media.cover)
b.itemCompactOngoing.visibility = b.itemCompactOngoing.isVisible =
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE media.status == currActivity()!!.getString(R.string.status_releasing)
b.itemCompactTitle.text = media.userPreferredName b.itemCompactTitle.text = media.userPreferredName
b.itemCompactScore.text = b.itemCompactScore.text =
((if (media.userScore == 0) (media.meanScore ((if (media.userScore == 0) (media.meanScore
@ -237,8 +238,8 @@ class MediaAdaptor(
) )
) )
blurImage(b.itemCompactBanner, media.banner ?: media.cover) blurImage(b.itemCompactBanner, media.banner ?: media.cover)
b.itemCompactOngoing.visibility = b.itemCompactOngoing.isVisible =
if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE media.status == currActivity()!!.getString(R.string.status_releasing)
b.itemCompactTitle.text = media.userPreferredName b.itemCompactTitle.text = media.userPreferredName
b.itemCompactScore.text = b.itemCompactScore.text =
((if (media.userScore == 0) (media.meanScore ((if (media.userScore == 0) (media.meanScore

View file

@ -2,9 +2,9 @@ package ani.dantotsu.media
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Rect import android.graphics.Rect
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.util.TypedValue import android.util.TypedValue
@ -12,9 +12,7 @@ import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -22,8 +20,10 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.bold import androidx.core.text.bold
import androidx.core.text.color import androidx.core.text.color
import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -62,6 +62,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import nl.joery.animatedbottombar.AnimatedBottomBar
import kotlin.math.abs import kotlin.math.abs
@ -70,12 +71,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
lateinit var binding: ActivityMediaBinding lateinit var binding: ActivityMediaBinding
private val scope = lifecycleScope private val scope = lifecycleScope
private val model: MediaDetailsViewModel by viewModels() private val model: MediaDetailsViewModel by viewModels()
lateinit var tabLayout: TripleNavAdapter
var selected = 0 var selected = 0
lateinit var navBar: AnimatedBottomBar
var anime = true var anime = true
private var adult = false private var adult = false
@SuppressLint("SetTextI18n", "ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia() var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia()
@ -83,8 +84,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
if (id != -1) { if (id != -1) {
runBlocking { runBlocking {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
media = media = Anilist.query.getMedia(id, false) ?: emptyMedia()
Anilist.query.getMedia(id, false) ?: emptyMedia()
} }
} }
} }
@ -100,26 +100,34 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
binding = ActivityMediaBinding.inflate(layoutInflater) binding = ActivityMediaBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
screenWidth = resources.displayMetrics.widthPixels.toFloat() screenWidth = resources.displayMetrics.widthPixels.toFloat()
navBar = binding.mediaBottomBar
val isVertical = resources.configuration.orientation
// Ui init // Ui init
initActivity(this) initActivity(this)
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight } binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = navBarHeight }
val oldMargin = binding.mediaViewPager.marginBottom val oldMargin = binding.mediaViewPager.marginBottom
AndroidBug5497Workaround.assistActivity(this) { AndroidBug5497Workaround.assistActivity(this) {
if (it) { if (it) {
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = 0 bottomMargin = 0
} }
binding.mediaTabContainer.visibility = View.GONE navBar.visibility = View.GONE
} else { } else {
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = oldMargin 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.mediaBanner.updateLayoutParams { height += statusBarHeight }
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight } binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
binding.mediaClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight } binding.mediaClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
@ -147,7 +155,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
val banner = val banner =
if (bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen if (bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen
val viewPager = binding.mediaViewPager val viewPager = binding.mediaViewPager
//tabLayout = binding.mediaTab as AnimatedBottomBar
viewPager.isUserInputEnabled = false viewPager.isUserInputEnabled = false
viewPager.setPageTransformer(ZoomOutPageTransformer()) viewPager.setPageTransformer(ZoomOutPageTransformer())
@ -157,9 +164,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
binding.mediaCoverImage.loadImage(media.cover) binding.mediaCoverImage.loadImage(media.cover)
binding.mediaCoverImage.setOnLongClickListener { binding.mediaCoverImage.setOnLongClickListener {
val coverTitle = "${media.userPreferredName}[Cover]"
ImageViewDialog.newInstance( ImageViewDialog.newInstance(
this, this,
media.userPreferredName + "[Cover]", coverTitle,
media.cover media.cover
) )
} }
@ -176,9 +184,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
} }
override fun onLongClick(event: MotionEvent) { override fun onLongClick(event: MotionEvent) {
val bannerTitle = "${media.userPreferredName}[Banner]"
ImageViewDialog.newInstance( ImageViewDialog.newInstance(
this@MediaDetailsActivity, this@MediaDetailsActivity,
media.userPreferredName + "[Banner]", bannerTitle,
media.banner ?: media.cover media.banner ?: media.cover
) )
banner.performClick() banner.performClick()
@ -186,7 +195,8 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
}) })
banner.setOnTouchListener { _, motionEvent -> gestureDetector.onTouchEvent(motionEvent);true } banner.setOnTouchListener { _, motionEvent -> gestureDetector.onTouchEvent(motionEvent);true }
if (PrefManager.getVal(PrefName.Incognito)) { if (PrefManager.getVal(PrefName.Incognito)) {
binding.mediaTitle.text = " ${media.userPreferredName}" val mediaTitle = " ${media.userPreferredName}"
binding.mediaTitle.text = mediaTitle
binding.incognito.visibility = View.VISIBLE binding.incognito.visibility = View.VISIBLE
} else { } else {
binding.mediaTitle.text = media.userPreferredName binding.mediaTitle.text = media.userPreferredName
@ -246,13 +256,13 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
@SuppressLint("ResourceType") @SuppressLint("ResourceType")
fun total() { fun total() {
val text = SpannableStringBuilder().apply { val text = SpannableStringBuilder().apply {
val typedValue = TypedValue() val mediaTypedValue = TypedValue()
this@MediaDetailsActivity.theme.resolveAttribute( this@MediaDetailsActivity.theme.resolveAttribute(
com.google.android.material.R.attr.colorOnBackground, com.google.android.material.R.attr.colorOnBackground,
typedValue, mediaTypedValue,
true true
) )
val white = typedValue.data val white = mediaTypedValue.data
if (media.userStatus != null) { if (media.userStatus != null) {
append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num)) append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num))
val typedValue = TypedValue() val typedValue = TypedValue()
@ -342,14 +352,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
progress() progress()
} }
} }
tabLayout = TripleNavAdapter(
binding.mediaTab1,
binding.mediaTab2,
binding.mediaTab3,
media.anime != null,
media.format ?: "",
isVertical == 1
)
adult = media.isAdult adult = media.isAdult
if (media.anime != null) { if (media.anime != null) {
viewPager.adapter = viewPager.adapter =
@ -365,22 +367,36 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
anime = false anime = false
} }
selected = media.selected!!.window selected = media.selected!!.window
binding.mediaTitle.translationX = -screenWidth binding.mediaTitle.translationX = -screenWidth
tabLayout.selectionListener = { selected, newId -> val infoTab = navBar.createTab(R.drawable.ic_round_info_24, R.string.info, R.id.info)
binding.commentInputLayout.visibility = if (selected == 2) View.VISIBLE else View.GONE val watchTab = if (anime) {
this.selected = selected navBar.createTab(R.drawable.ic_round_movie_filter_24, R.string.watch, R.id.watch)
selectFromID(newId) } else if (media.format == "NOVEL") {
viewPager.setCurrentItem(selected, false) 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)
}
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) val sel = model.loadSelected(media, isDownload)
sel.window = selected sel.window = selected
model.saveSelected(media.id, sel) model.saveSelected(media.id, sel)
} }
tabLayout.selectTab(selected) })
selectFromID(tabLayout.selected)
viewPager.setCurrentItem(selected, false)
if (model.continueMedia == null && media.cameFromContinue) { if (model.continueMedia == null && media.cameFromContinue) {
model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia) model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia)
@ -390,6 +406,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
if (frag != null) { if (frag != null) {
selected = 2 selected = 2
} }
navBar.selectTabAt(selected)
binding.commentInputLayout.isVisible = selected == 2
viewPager.setCurrentItem(selected, false)
val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(true) } val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(true) }
live.observe(this) { live.observe(this) {
@ -402,40 +421,19 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
} }
} }
private fun selectFromID(id: Int) { override fun onConfigurationChanged(newConfig: Configuration) {
when (id) { super.onConfigurationChanged(newConfig)
R.id.info -> { val rightMargin = if (resources.configuration.orientation ==
selected = 0 Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0
} val bottomMargin = if (resources.configuration.orientation ==
Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight
R.id.watch, R.id.read -> { val params : ViewGroup.MarginLayoutParams =
selected = 1 navBar.layoutParams as ViewGroup.MarginLayoutParams
} params.updateMargins(right = rightMargin, bottom = bottomMargin)
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 onResume() { override fun onResume() {
if (this::tabLayout.isInitialized) { navBar.selectTabAt(selected)
tabLayout.selectTab(selected)
}
super.onResume() super.onResume()
} }

View file

@ -52,14 +52,18 @@ class MediaDetailsViewModel : ViewModel() {
it it
} }
if (isDownload) { if (isDownload) {
data.sourceIndex = if (media.anime != null) { data.sourceIndex = when {
media.anime != null -> {
AnimeSources.list.size - 1 AnimeSources.list.size - 1
} else if (media.format == "MANGA" || media.format == "ONE_SHOT") { }
media.format == "MANGA" || media.format == "ONE_SHOT" -> {
MangaSources.list.size - 1 MangaSources.list.size - 1
} else { }
else -> {
NovelSources.list.size - 1 NovelSources.list.size - 1
} }
} }
}
return data return data
} }
@ -152,10 +156,10 @@ class MediaDetailsViewModel : ViewModel() {
watchSources?.get(i)?.apply { watchSources?.get(i)?.apply {
if (!post && !allowsPreloading) return@apply if (!post && !allowsPreloading) return@apply
ep.sEpisode?.let { ep.sEpisode?.let {
loadByVideoServers(link, ep.extra, it) { loadByVideoServers(link, ep.extra, it) { extractor ->
if (it.videos.isNotEmpty()) { if (extractor.videos.isNotEmpty()) {
list.add(it) list.add(extractor)
ep.extractorCallback?.invoke(it) ep.extractorCallback?.invoke(extractor)
} }
} }
} }

View file

@ -16,6 +16,8 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@ -37,7 +39,7 @@ import java.io.Serializable
import java.net.URLEncoder import java.net.URLEncoder
@SuppressLint("SetTextI18n")
class MediaInfoFragment : Fragment() { class MediaInfoFragment : Fragment() {
private var _binding: FragmentMediaInfoBinding? = null private var _binding: FragmentMediaInfoBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -46,6 +48,8 @@ class MediaInfoFragment : Fragment() {
private var type = "ANIME" private var type = "ANIME"
private val genreModel: GenresViewModel by activityViewModels() private val genreModel: GenresViewModel by activityViewModels()
private val tripleTab = "\t\t\t"
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -63,8 +67,8 @@ class MediaInfoFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val model: MediaDetailsViewModel by activityViewModels() val model: MediaDetailsViewModel by activityViewModels()
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode)
binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE binding.mediaInfoProgressBar.isGone = loaded
binding.mediaInfoContainer.visibility = if (loaded) View.VISIBLE else View.GONE binding.mediaInfoContainer.isVisible = loaded
binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight } binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight }
model.scrolledToTop.observe(viewLifecycleOwner) { model.scrolledToTop.observe(viewLifecycleOwner) {
@ -78,14 +82,16 @@ class MediaInfoFragment : Fragment() {
binding.mediaInfoProgressBar.visibility = View.GONE binding.mediaInfoProgressBar.visibility = View.GONE
binding.mediaInfoContainer.visibility = View.VISIBLE 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 { binding.mediaInfoName.setOnLongClickListener {
copyToClipboard(media.name ?: media.nameRomaji) copyToClipboard(media.name ?: media.nameRomaji)
true true
} }
if (media.name != null) binding.mediaInfoNameRomajiContainer.visibility = if (media.name != null) binding.mediaInfoNameRomajiContainer.visibility =
View.VISIBLE View.VISIBLE
binding.mediaInfoNameRomaji.text = "\t\t\t" + media.nameRomaji val infoNameRomanji = tripleTab + media.nameRomaji
binding.mediaInfoNameRomaji.text = infoNameRomanji
binding.mediaInfoNameRomaji.setOnLongClickListener { binding.mediaInfoNameRomaji.setOnLongClickListener {
copyToClipboard(media.nameRomaji) copyToClipboard(media.nameRomaji)
true true
@ -127,8 +133,9 @@ class MediaInfoFragment : Fragment() {
} }
binding.mediaInfoDurationContainer.visibility = View.VISIBLE binding.mediaInfoDurationContainer.visibility = View.VISIBLE
binding.mediaInfoSeasonContainer.visibility = View.VISIBLE binding.mediaInfoSeasonContainer.visibility = View.VISIBLE
binding.mediaInfoSeason.text = val seasonInfo = "${(media.anime.season ?: "??")} ${(media.anime.seasonYear ?: "??")}"
(media.anime.season ?: "??") + " " + (media.anime.seasonYear ?: "??") binding.mediaInfoSeason.text = seasonInfo
if (media.anime.mainStudio != null) { if (media.anime.mainStudio != null) {
binding.mediaInfoStudioContainer.visibility = View.VISIBLE binding.mediaInfoStudioContainer.visibility = View.VISIBLE
binding.mediaInfoStudio.text = media.anime.mainStudio!!.name binding.mediaInfoStudio.text = media.anime.mainStudio!!.name
@ -162,9 +169,12 @@ class MediaInfoFragment : Fragment() {
} }
} }
binding.mediaInfoTotalTitle.setText(R.string.total_eps) binding.mediaInfoTotalTitle.setText(R.string.total_eps)
binding.mediaInfoTotal.text = val infoTotal = if (media.anime.nextAiringEpisode != null)
if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " | " + (media.anime.totalEpisodes "${media.anime.nextAiringEpisode} | ${media.anime.totalEpisodes ?: "~"}"
?: "~").toString()) else (media.anime.totalEpisodes ?: "~").toString() else
(media.anime.totalEpisodes ?: "~").toString()
binding.mediaInfoTotal.text = infoTotal
} else if (media.manga != null) { } else if (media.manga != null) {
type = "MANGA" type = "MANGA"
binding.mediaInfoTotalTitle.setText(R.string.total_chaps) binding.mediaInfoTotalTitle.setText(R.string.total_chaps)
@ -191,8 +201,9 @@ class MediaInfoFragment : Fragment() {
(media.description ?: "null").replace("\\n", "<br>").replace("\\\"", "\""), (media.description ?: "null").replace("\\n", "<br>").replace("\\\"", "\""),
HtmlCompat.FROM_HTML_MODE_LEGACY HtmlCompat.FROM_HTML_MODE_LEGACY
) )
binding.mediaInfoDescription.text = val infoDesc = tripleTab + if (desc.toString() != "null") desc else getString(R.string.no_description_available)
"\t\t\t" + if (desc.toString() != "null") desc else getString(R.string.no_description_available) binding.mediaInfoDescription.text = infoDesc
binding.mediaInfoDescription.setOnClickListener { binding.mediaInfoDescription.setOnClickListener {
if (binding.mediaInfoDescription.maxLines == 5) { if (binding.mediaInfoDescription.maxLines == 5) {
ObjectAnimator.ofInt(binding.mediaInfoDescription, "maxLines", 100) ObjectAnimator.ofInt(binding.mediaInfoDescription, "maxLines", 100)
@ -550,7 +561,7 @@ class MediaInfoFragment : Fragment() {
} }
override fun onResume() { override fun onResume() {
binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE binding.mediaInfoProgressBar.isGone = loaded
super.onResume() super.onResume()
} }

View file

@ -36,7 +36,6 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
return binding.root return binding.root
} }
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight } binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
var media: Media? var media: Media?
@ -168,9 +167,10 @@ class MediaListDialogFragment : BottomSheetDialogFragment() {
val init = val init =
if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString() if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString()
.toInt() else 0 .toInt() else 0
if (init < (total if (init < (total ?: 5000)) {
?: 5000) val progressText = "${init + 1}"
) binding.mediaListProgress.setText((init + 1).toString()) binding.mediaListProgress.setText(progressText)
}
if (init + 1 == (total ?: 5000)) { if (init + 1 == (total ?: 5000)) {
binding.mediaListStatus.setText(statusStrings[2], false) binding.mediaListStatus.setText(statusStrings[2], false)
onComplete() onComplete()

View file

@ -54,7 +54,6 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
} }
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight } binding.mediaListContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += navBarHeight }
val scope = viewLifecycleOwner.lifecycleScope val scope = viewLifecycleOwner.lifecycleScope
@ -68,7 +67,7 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
MAL.query.deleteList(media.anime != null, media.idMAL) MAL.query.deleteList(media.anime != null, media.idMAL)
} catch (e: Exception) { } catch (e: Exception) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
snackString("Failed to delete because of... ${e.message}") snackString(getString(R.string.delete_fail_reason, e.message))
} }
return@withContext return@withContext
} }
@ -154,7 +153,10 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() {
val init = val init =
if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString() if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString()
.toInt() else 0 .toInt() else 0
if (init < (total ?: 5000)) binding.mediaListProgress.setText((init + 1).toString()) if (init < (total ?: 5000)) {
val progressText = "${init + 1}"
binding.mediaListProgress.setText(progressText)
}
if (init + 1 == (total ?: 5000)) { if (init + 1 == (total ?: 5000)) {
binding.mediaListStatus.setText(statusStrings[2], false) binding.mediaListStatus.setText(statusStrings[2], false)
} }

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

View file

@ -27,7 +27,7 @@ class ProgressAdapter(private val horizontal: Boolean = true, searched: Boolean)
return ProgressViewHolder(binding) return ProgressViewHolder(binding)
} }
@SuppressLint("SetTextI18n", "ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ProgressViewHolder, position: Int) { override fun onBindViewHolder(holder: ProgressViewHolder, position: Int) {
val progressBar = holder.binding.root val progressBar = holder.binding.root
bar = progressBar bar = progressBar

View file

@ -6,6 +6,7 @@ import android.os.Parcelable
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.core.view.updatePaddingRelative import androidx.core.view.updatePaddingRelative
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
@ -137,7 +138,7 @@ class SearchActivity : AppCompatActivity() {
model.searchResults.results.addAll(it.results) model.searchResults.results.addAll(it.results)
mediaAdaptor.notifyItemRangeInserted(prev, it.results.size) mediaAdaptor.notifyItemRangeInserted(prev, it.results.size)
progressAdapter.bar?.visibility = if (it.hasNextPage) View.VISIBLE else View.GONE progressAdapter.bar?.isVisible = it.hasNextPage
} }
} }

View file

@ -1,6 +1,5 @@
package ani.dantotsu.media package ani.dantotsu.media
import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -22,7 +21,6 @@ abstract class SourceAdapter(
return SourceViewHolder(binding) return SourceViewHolder(binding)
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: SourceViewHolder, position: Int) { override fun onBindViewHolder(holder: SourceViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
val character = sources[position] val character = sources[position]

View file

@ -65,7 +65,7 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() {
i = media!!.selected!!.sourceIndex i = media!!.selected!!.sourceIndex
val source = if (media!!.anime != null) { val source = if (media!!.anime != null) {
(if (!media!!.isAdult) AnimeSources else HAnimeSources)[i!!] (if (media!!.isAdult) HAnimeSources else AnimeSources)[i!!]
} else { } else {
anime = false anime = false
(if (media!!.isAdult) HMangaSources else MangaSources)[i!!] (if (media!!.isAdult) HMangaSources else MangaSources)[i!!]

View file

@ -6,6 +6,7 @@ import android.view.ViewGroup
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -114,7 +115,7 @@ class StudioActivity : AppCompatActivity() {
} }
override fun onResume() { override fun onResume() {
binding.studioProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE binding.studioProgressBar.isGone = loaded
super.onResume() super.onResume()
} }
} }

View file

@ -17,7 +17,7 @@ class SubtitleDownloader {
companion object { companion object {
//doesn't really download the subtitles -\_(o_o)_/- //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) { withContext(Dispatchers.IO) {
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it // Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
val networkHelper = Injekt.get<NetworkHelper>() val networkHelper = Injekt.get<NetworkHelper>()
@ -60,7 +60,7 @@ class SubtitleDownloader {
if (!directory.exists()) { //just in case if (!directory.exists()) { //just in case
directory.mkdirs() directory.mkdirs()
} }
val type = loadSubtitleType(context, url) val type = loadSubtitleType(url)
val subtiteFile = File(directory, "subtitle.${type}") val subtiteFile = File(directory, "subtitle.${type}")
if (subtiteFile.exists()) { if (subtiteFile.exists()) {
subtiteFile.delete() subtiteFile.delete()

View file

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

View file

@ -7,7 +7,7 @@ import java.util.regex.Pattern
class AnimeNameAdapter { class AnimeNameAdapter {
companion object { companion object {
const val episodeRegex = 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 = const val failedEpisodeNumberRegex =
"(?<!part\\s)\\b(\\d+)\\b" "(?<!part\\s)\\b(\\d+)\\b"
const val seasonRegex = "(season|s)[\\s:.\\-]*(\\d+)[\\s:.\\-]*" const val seasonRegex = "(season|s)[\\s:.\\-]*(\\d+)[\\s:.\\-]*"
@ -114,7 +114,7 @@ class AnimeNameAdapter {
val regexPattern = Regex(episodeRegex, RegexOption.IGNORE_CASE) val regexPattern = Regex(episodeRegex, RegexOption.IGNORE_CASE)
val removedNumber = text.replace(regexPattern, "") val removedNumber = text.replace(regexPattern, "")
return if (removedNumber.equals(text, true)) { // if nothing was removed return if (removedNumber.equals(text, true)) { // if nothing was removed
val failedEpisodeNumberPattern: Regex = val failedEpisodeNumberPattern =
Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE) Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE)
failedEpisodeNumberPattern.replace(removedNumber) { mr -> failedEpisodeNumberPattern.replace(removedNumber) { mr ->
mr.value.replaceFirst(mr.groupValues[1], "") mr.value.replaceFirst(mr.groupValues[1], "")

View file

@ -13,6 +13,8 @@ import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.*
@ -54,7 +56,6 @@ class AnimeWatchAdapter(
private var nestedDialog: AlertDialog? = null private var nestedDialog: AlertDialog? = null
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
_binding = binding _binding = binding
@ -97,15 +98,12 @@ class AnimeWatchAdapter(
null null
) )
} }
val offline = if (!isOnline(binding.root.context) || PrefManager.getVal( val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
PrefName.OfflineMode
)
) View.GONE else View.VISIBLE
binding.animeSourceNameContainer.visibility = offline binding.animeSourceNameContainer.isGone = offline
binding.animeSourceSettings.visibility = offline binding.animeSourceSettings.isGone = offline
binding.animeSourceSearch.visibility = offline binding.animeSourceSearch.isGone = offline
binding.animeSourceTitle.visibility = offline binding.animeSourceTitle.isGone = offline
//Source Selection //Source Selection
var source = var source =
@ -117,8 +115,7 @@ class AnimeWatchAdapter(
this.selectDub = media.selected!!.preferDub this.selectDub = media.selected!!.preferDub
binding.animeSourceTitle.text = showUserText binding.animeSourceTitle.text = showUserText
showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } }
binding.animeSourceDubbedCont.visibility = binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
} }
} }
@ -137,8 +134,7 @@ class AnimeWatchAdapter(
changing = true changing = true
binding.animeSourceDubbed.isChecked = selectDub binding.animeSourceDubbed.isChecked = selectDub
changing = false changing = false
binding.animeSourceDubbedCont.visibility = binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
source = i source = i
setLanguageList(0, i) setLanguageList(0, i)
} }
@ -158,8 +154,7 @@ class AnimeWatchAdapter(
changing = true changing = true
binding.animeSourceDubbed.isChecked = selectDub binding.animeSourceDubbed.isChecked = selectDub
changing = false changing = false
binding.animeSourceDubbedCont.visibility = binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately()
if (isDubAvailableSeparately()) View.VISIBLE else View.GONE
setLanguageList(i, source) setLanguageList(i, source)
} }
subscribeButton(false) subscribeButton(false)
@ -223,9 +218,9 @@ class AnimeWatchAdapter(
else -> dialogBinding.animeSourceList else -> dialogBinding.animeSourceList
} }
when (style) { when (style) {
0 -> dialogBinding.layoutText.text = "List" 0 -> dialogBinding.layoutText.setText(R.string.list)
1 -> dialogBinding.layoutText.text = "Grid" 1 -> dialogBinding.layoutText.setText(R.string.grid)
2 -> dialogBinding.layoutText.text = "Compact" 2 -> dialogBinding.layoutText.setText(R.string.compact)
else -> dialogBinding.animeSourceList else -> dialogBinding.animeSourceList
} }
selected.alpha = 1f selected.alpha = 1f
@ -237,24 +232,24 @@ class AnimeWatchAdapter(
dialogBinding.animeSourceList.setOnClickListener { dialogBinding.animeSourceList.setOnClickListener {
selected(it as ImageButton) selected(it as ImageButton)
style = 0 style = 0
dialogBinding.layoutText.text = "List" dialogBinding.layoutText.setText(R.string.list)
run = true run = true
} }
dialogBinding.animeSourceGrid.setOnClickListener { dialogBinding.animeSourceGrid.setOnClickListener {
selected(it as ImageButton) selected(it as ImageButton)
style = 1 style = 1
dialogBinding.layoutText.text = "Grid" dialogBinding.layoutText.setText(R.string.grid)
run = true run = true
} }
dialogBinding.animeSourceCompact.setOnClickListener { dialogBinding.animeSourceCompact.setOnClickListener {
selected(it as ImageButton) selected(it as ImageButton)
style = 2 style = 2
dialogBinding.layoutText.text = "Compact" dialogBinding.layoutText.setText(R.string.compact)
run = true run = true
} }
dialogBinding.animeWebviewContainer.setOnClickListener { dialogBinding.animeWebviewContainer.setOnClickListener {
if (!WebViewUtil.supportsWebView(fragment.requireContext())) { if (!WebViewUtil.supportsWebView(fragment.requireContext())) {
toast("WebView not installed") toast(R.string.webview_not_installed)
} }
//start CookieCatcher activity //start CookieCatcher activity
if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) {
@ -307,7 +302,6 @@ class AnimeWatchAdapter(
} }
//Chips //Chips
@SuppressLint("SetTextI18n")
fun updateChips(limit: Int, names: Array<String>, arr: Array<Int>, selected: Int = 0) { fun updateChips(limit: Int, names: Array<String>, arr: Array<Int>, selected: Int = 0) {
val binding = _binding val binding = _binding
if (binding != null) { if (binding != null) {
@ -329,7 +323,8 @@ class AnimeWatchAdapter(
0 0
) )
} }
chip.text = "${names[limit * (position)]} - ${names[last - 1]}" val chipText = "${names[limit * (position)]} - ${names[last - 1]}"
chip.text = chipText
chip.setTextColor( chip.setTextColor(
ContextCompat.getColorStateList( ContextCompat.getColorStateList(
fragment.requireContext(), fragment.requireContext(),
@ -363,7 +358,6 @@ class AnimeWatchAdapter(
_binding?.animeSourceChipGroup?.removeAllViews() _binding?.animeSourceChipGroup?.removeAllViews()
} }
@SuppressLint("SetTextI18n")
fun handleEpisodes() { fun handleEpisodes() {
val binding = _binding val binding = _binding
if (binding != null) { if (binding != null) {
@ -371,9 +365,9 @@ class AnimeWatchAdapter(
val episodes = media.anime.episodes!!.keys.toTypedArray() val episodes = media.anime.episodes!!.keys.toTypedArray()
val anilistEp = (media.userProgress ?: 0).plus(1) val anilistEp = (media.userProgress ?: 0).plus(1)
val appEp = val appEp = PrefManager.getCustomVal<String?>(
PrefManager.getCustomVal<String?>("${media.id}_current_ep", "")?.toIntOrNull() "${media.id}_current_ep", ""
?: 1 )?.toIntOrNull() ?: 1
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString() var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
if (episodes.contains(continueEp)) { if (episodes.contains(continueEp)) {
@ -409,15 +403,19 @@ class AnimeWatchAdapter(
ep.thumb ?: FileUrl[media.banner ?: media.cover], 0 ep.thumb ?: FileUrl[media.banner ?: media.cover], 0
) )
if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE
binding.animeSourceContinueText.text = binding.animeSourceContinueText.text =
currActivity()!!.getString(R.string.continue_episode) + "${ep.number}${if (ep.filler) " - Filler" else ""}${"\n$cleanedTitle"}" currActivity()!!.getString(R.string.continue_episode, ep.number, if (ep.filler)
currActivity()!!.getString(R.string.filler_tag)
else
"", cleanedTitle)
binding.animeSourceContinue.setOnClickListener { binding.animeSourceContinue.setOnClickListener {
fragment.onEpisodeClick(continueEp) fragment.onEpisodeClick(continueEp)
} }
if (fragment.continueEp) { if (fragment.continueEp) {
if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < PrefManager.getVal<Float>( if (
PrefName.WatchPercentage (binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams)
) .weight < PrefManager.getVal<Float>(PrefName.WatchPercentage)
) { ) {
binding.animeSourceContinue.performClick() binding.animeSourceContinue.performClick()
fragment.continueEp = false fragment.continueEp = false
@ -428,13 +426,10 @@ class AnimeWatchAdapter(
} }
binding.animeSourceProgressBar.visibility = View.GONE binding.animeSourceProgressBar.visibility = View.GONE
if (media.anime.episodes!!.isNotEmpty()) {
binding.animeSourceNotFound.visibility = View.GONE val sourceFound = media.anime.episodes!!.isNotEmpty()
binding.faqbutton.visibility = View.GONE} binding.animeSourceNotFound.isGone = sourceFound
else { binding.faqbutton.isGone = sourceFound
binding.animeSourceNotFound.visibility = View.VISIBLE
binding.faqbutton.visibility = View.VISIBLE
}
} else { } else {
binding.animeSourceContinue.visibility = View.GONE binding.animeSourceContinue.visibility = View.GONE
binding.animeSourceNotFound.visibility = View.GONE binding.animeSourceNotFound.visibility = View.GONE

View file

@ -17,6 +17,8 @@ import androidx.annotation.OptIn
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils import androidx.core.math.MathUtils
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@ -27,19 +29,24 @@ import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.* import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.databinding.FragmentAnimeWatchBinding
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.download.video.ExoplayerDownloadService import ani.dantotsu.download.video.ExoplayerDownloadService
import ani.dantotsu.dp
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LanguageMapper import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.AnimeParser import ani.dantotsu.parsers.AnimeParser
import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.HAnimeSources import ani.dantotsu.parsers.HAnimeSources
import ani.dantotsu.setNavigationTheme
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
@ -340,16 +347,12 @@ class AnimeWatchFragment : Fragment() {
val changeUIVisibility: (Boolean) -> Unit = { show -> val changeUIVisibility: (Boolean) -> Unit = { show ->
val activity = activity val activity = activity
if (activity is MediaDetailsActivity && isAdded) { if (activity is MediaDetailsActivity && isAdded) {
val visibility = if (show) View.VISIBLE else View.GONE activity.findViewById<AppBarLayout>(R.id.mediaAppBar).isVisible = show
activity.findViewById<AppBarLayout>(R.id.mediaAppBar).visibility = visibility activity.findViewById<ViewPager2>(R.id.mediaViewPager).isVisible = show
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility activity.findViewById<CardView>(R.id.mediaCover).isVisible = show
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility activity.findViewById<CardView>(R.id.mediaClose).isVisible = show
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility activity.navBar.isVisible = show
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).isGone = show
activity.tabLayout.setVisibility(visibility)
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
if (show) View.GONE else View.VISIBLE
} }
} }
var itemSelected = false var itemSelected = false
@ -435,7 +438,7 @@ class AnimeWatchFragment : Fragment() {
DownloadedType( DownloadedType(
media.mainName(), media.mainName(),
i, i,
DownloadedType.Type.ANIME MediaType.ANIME
) )
) )
episodeAdapter.purgeDownload(i) episodeAdapter.purgeDownload(i)
@ -447,7 +450,7 @@ class AnimeWatchFragment : Fragment() {
DownloadedType( DownloadedType(
media.mainName(), media.mainName(),
i, i,
DownloadedType.Type.ANIME MediaType.ANIME
) )
) )
val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i) val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)

View file

@ -8,18 +8,21 @@ import android.view.ViewGroup
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.core.view.isVisible
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.DownloadIndex import androidx.media3.exoplayer.offline.DownloadIndex
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.R
import ani.dantotsu.connections.updateProgress import ani.dantotsu.connections.updateProgress
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemEpisodeCompactBinding import ani.dantotsu.databinding.ItemEpisodeCompactBinding
import ani.dantotsu.databinding.ItemEpisodeGridBinding import ani.dantotsu.databinding.ItemEpisodeGridBinding
import ani.dantotsu.databinding.ItemEpisodeListBinding import ani.dantotsu.databinding.ItemEpisodeListBinding
import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeDownloaderService
import ani.dantotsu.download.video.Helper import ani.dantotsu.download.video.Helper
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.setAnimation
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
@ -97,7 +100,6 @@ class EpisodeAdapter(
return type return type
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val ep = arr[position] val ep = arr[position]
val title = if (!ep.title.isNullOrEmpty() && ep.title != "null") { val title = if (!ep.title.isNullOrEmpty() && ep.title != "null") {
@ -125,8 +127,7 @@ class EpisodeAdapter(
binding.itemEpisodeFiller.visibility = View.GONE binding.itemEpisodeFiller.visibility = View.GONE
binding.itemEpisodeFillerView.visibility = View.GONE binding.itemEpisodeFillerView.visibility = View.GONE
} }
binding.itemEpisodeDesc.visibility = binding.itemEpisodeDesc.isVisible = !ep.desc.isNullOrBlank()
if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE
binding.itemEpisodeDesc.text = ep.desc ?: "" binding.itemEpisodeDesc.text = ep.desc ?: ""
holder.bind(ep.number, ep.downloadProgress, ep.desc) holder.bind(ep.number, ep.downloadProgress, ep.desc)
@ -203,8 +204,7 @@ class EpisodeAdapter(
val binding = holder.binding val binding = holder.binding
setAnimation(fragment.requireContext(), holder.binding.root) setAnimation(fragment.requireContext(), holder.binding.root)
binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeNumber.text = ep.number
binding.itemEpisodeFillerView.visibility = binding.itemEpisodeFillerView.isVisible = ep.filler
if (ep.filler) View.VISIBLE else View.GONE
if (media.userProgress != null) { if (media.userProgress != null) {
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat())
binding.itemEpisodeViewedCover.visibility = View.VISIBLE binding.itemEpisodeViewedCover.visibility = View.VISIBLE
@ -429,7 +429,7 @@ class EpisodeAdapter(
if (bytes < 0) return null if (bytes < 0) return null
val unit = 1000 val unit = 1000
if (bytes < unit) return "$bytes B" 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] val pre = ("KMGTPE")[exp - 1]
return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre) return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
} }

View file

@ -16,7 +16,10 @@ import android.graphics.Color
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.hardware.SensorManager import android.hardware.SensorManager
import android.media.AudioManager import android.media.AudioManager
import android.media.AudioManager.* import android.media.AudioManager.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.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -27,8 +30,18 @@ import android.provider.Settings.System
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Rational import android.util.Rational
import android.util.TypedValue import android.util.TypedValue
import android.view.* import android.view.GestureDetector
import android.view.KeyEvent.* 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.view.animation.AnimationUtils
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ImageButton import android.widget.ImageButton
@ -46,27 +59,43 @@ import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.cast.CastPlayer import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener 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.AUDIO_CONTENT_TYPE_MOVIE
import androidx.media3.common.C.TRACK_TYPE_VIDEO 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.UnstableApi
import androidx.media3.common.util.Util
import androidx.media3.datasource.DataSource import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DefaultDataSourceFactory import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.HttpDataSource import androidx.media3.datasource.HttpDataSource
import androidx.media3.datasource.cache.CacheDataSource import androidx.media3.datasource.cache.CacheDataSource
import androidx.media3.datasource.okhttp.OkHttpDataSource import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.util.EventLogger import androidx.media3.exoplayer.util.EventLogger
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
import androidx.media3.ui.* import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.CaptionStyleCompat.* import androidx.media3.ui.CaptionStyleCompat
import androidx.media3.exoplayer.DefaultLoadControl 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 androidx.mediarouter.app.MediaRouteButton
import ani.dantotsu.* import ani.dantotsu.GesturesListener
import ani.dantotsu.NoPaddingArrayAdapter
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.brightnessConverter
import ani.dantotsu.circularReveal
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.connections.discord.Discord 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.discord.RPC
import ani.dantotsu.connections.updateProgress import ani.dantotsu.connections.updateProgress
import ani.dantotsu.databinding.ActivityExoplayerBinding import ani.dantotsu.databinding.ActivityExoplayerBinding
import ani.dantotsu.defaultHeaders
import ani.dantotsu.download.video.Helper 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.Media
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.SubtitleDownloader import ani.dantotsu.media.SubtitleDownloader
import ani.dantotsu.okHttpClient
import ani.dantotsu.others.AniSkip import ani.dantotsu.others.AniSkip
import ani.dantotsu.others.AniSkip.getType import ani.dantotsu.others.AniSkip.getType
import ani.dantotsu.others.ResettableTimer import ani.dantotsu.others.ResettableTimer
import ani.dantotsu.others.getSerialized 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.PlayerSettingsActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.startMainActivity
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.toast
import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastButtonFactory
@ -103,14 +151,17 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.* import java.util.Calendar
import java.util.concurrent.* 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.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@UnstableApi @UnstableApi
@SuppressLint("SetTextI18n", "ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityListener { class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityListener {
private val resumeWindow = "resumeWindow" private val resumeWindow = "resumeWindow"
@ -344,15 +395,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
isCastApiAvailable = GoogleApiAvailability.getInstance() isCastApiAvailable = GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS .isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
try { try {
castContext = CastContext.getSharedInstance(this) castContext = CastContext.getSharedInstance(this, Executors.newSingleThreadExecutor()).result
castPlayer = CastPlayer(castContext!!) castPlayer = CastPlayer(castContext!!)
castPlayer!!.setSessionAvailabilityListener(this) castPlayer!!.setSessionAvailabilityListener(this)
} catch (e: Exception) { } catch (e: Exception) {
isCastApiAvailable = false isCastApiAvailable = false
} }
WindowCompat.setDecorFitsSystemWindows(window, false) hideSystemBarsExtendView()
hideSystemBars()
onBackPressedDispatcher.addCallback(this) { onBackPressedDispatcher.addCallback(this) {
finishAndRemoveTask() finishAndRemoveTask()
@ -397,17 +447,20 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
orientationListener = orientationListener =
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) { object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
override fun onOrientationChanged(orientation: Int) { override fun onOrientationChanged(orientation: Int) {
if (orientation in 45..135) { when (orientation) {
in 45..135 -> {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
exoRotate.visibility = View.VISIBLE exoRotate.visibility = View.VISIBLE
} }
rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
} else if (orientation in 225..315) { }
in 225..315 -> {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
exoRotate.visibility = View.VISIBLE exoRotate.visibility = View.VISIBLE
} }
rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} else if (orientation in 315..360 || orientation in 0..45) { }
in 315..360, in 0..45 -> {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
exoRotate.visibility = View.VISIBLE exoRotate.visibility = View.VISIBLE
} }
@ -415,6 +468,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
} }
} }
}
orientationListener?.enable() orientationListener?.enable()
} }
@ -703,11 +757,13 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
fun seek(forward: Boolean, event: MotionEvent? = null) { fun seek(forward: Boolean, event: MotionEvent? = null) {
val seekTime = PrefManager.getVal<Int>(PrefName.SeekTime) val seekTime = PrefManager.getVal<Int>(PrefName.SeekTime)
val (card, text) = if (forward) { val (card, text) = if (forward) {
forwardText.text = "+${seekTime * ++seekTimesF}" val text = "+${seekTime * ++seekTimesF}"
forwardText.text = text
handler.post { exoPlayer.seekTo(exoPlayer.currentPosition + seekTime * 1000) } handler.post { exoPlayer.seekTo(exoPlayer.currentPosition + seekTime * 1000) }
fastForwardCard to forwardText fastForwardCard to forwardText
} else { } else {
rewindText.text = "-${seekTime * ++seekTimesR}" val text = "-${seekTime * ++seekTimesR}"
rewindText.text = text
handler.post { exoPlayer.seekTo(exoPlayer.currentPosition - seekTime * 1000) } handler.post { exoPlayer.seekTo(exoPlayer.currentPosition - seekTime * 1000) }
fastRewindCard to rewindText fastRewindCard to rewindText
} }
@ -941,7 +997,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
episodeArr = episodes.keys.toList() episodeArr = episodes.keys.toList()
currentEpisodeIndex = episodeArr.indexOf(media.anime!!.selectedEpisode!!) currentEpisodeIndex = episodeArr.indexOf(media.anime!!.selectedEpisode!!)
episodeTitleArr = arrayListOf<String>() episodeTitleArr = arrayListOf()
episodes.forEach { episodes.forEach {
val episode = it.value val episode = it.value
val cleanedTitle = AnimeNameAdapter.removeEpisodeNumberCompletely(episode.title ?: "") 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(getString(R.string.view_anime), media.shareLink ?: ""),
RPC.Link( RPC.Link(
"Stream on Dantotsu", "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!! media.anime!!.selectedEpisode!!
) )
@Suppress("UNCHECKED_CAST")
val list = (PrefManager.getNullableCustomVal("continueAnimeList", listOf<Int>(), List::class.java) as List<Int>).toMutableList() val list = (PrefManager.getNullableCustomVal("continueAnimeList", listOf<Int>(), List::class.java) as List<Int>).toMutableList()
if (list.contains(media.id)) list.remove(media.id) if (list.contains(media.id)) list.remove(media.id)
list.add(media.id) list.add(media.id)
@ -1293,7 +1350,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
//Subtitles //Subtitles
exoSubtitle.visibility = if (ext.subtitles.isNotEmpty()) View.VISIBLE else View.GONE exoSubtitle.isVisible = ext.subtitles.isNotEmpty()
exoSubtitle.setOnClickListener { exoSubtitle.setOnClickListener {
subClick() subClick()
} }
@ -1301,9 +1358,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
if (subtitle != null) { if (subtitle != null) {
//var localFile: String? = null //var localFile: String? = null
if (subtitle?.type == SubtitleType.UNKNOWN) { if (subtitle?.type == SubtitleType.UNKNOWN) {
val context = this
runBlocking { runBlocking {
val type = SubtitleDownloader.loadSubtitleType(context, subtitle!!.file.url) val type = SubtitleDownloader.loadSubtitleType(subtitle!!.file.url)
val fileUri = Uri.parse(subtitle!!.file.url) val fileUri = Uri.parse(subtitle!!.file.url)
sub = MediaItem.SubtitleConfiguration sub = MediaItem.SubtitleConfiguration
.Builder(fileUri) .Builder(fileUri)
@ -1358,8 +1414,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
dataSource dataSource
} }
val dafuckDataSourceFactory = val dafuckDataSourceFactory = DefaultDataSource.Factory(this)
DefaultDataSourceFactory(this, Util.getUserAgent(this, R.string.app_name.toString()))
cacheFactory = CacheDataSource.Factory().apply { cacheFactory = CacheDataSource.Factory().apply {
setCache(Helper.getSimpleCache(this@ExoplayerView)) setCache(Helper.getSimpleCache(this@ExoplayerView))
if (ext.server.offline) { if (ext.server.offline) {
@ -1659,7 +1714,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
aspectRatio = Rational(width, height) aspectRatio = Rational(width, height)
videoInfo.text = "Quality: ${height}p" videoInfo.text = getString(R.string.video_quality, height)
if (exoPlayer.duration < playbackPosition) if (exoPlayer.duration < playbackPosition)
exoPlayer.seekTo(0) exoPlayer.seekTo(0)
@ -1735,7 +1790,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
timer = null timer = null
return return
} }
if (timer == null) {
timer = object : CountDownTimer(5000, 1000) { timer = object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
if (new == null) { if (new == null) {
@ -1756,7 +1810,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
} }
} }
timer?.start() timer?.start()
}
} }
if (PrefManager.getVal(PrefName.ShowTimeStampButton)) { if (PrefManager.getVal(PrefName.ShowTimeStampButton)) {

View file

@ -12,6 +12,7 @@ import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -302,7 +303,6 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
) )
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: UrlViewHolder, position: Int) { override fun onBindViewHolder(holder: UrlViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
val video = extractor.videos[position] val video = extractor.videos[position]
@ -401,12 +401,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
dismiss() dismiss()
} }
if (video.format == VideoType.CONTAINER) { if (video.format == VideoType.CONTAINER) {
binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE binding.urlSize.isVisible = video.size != null
binding.urlSize.text =
// if video size is null or 0, show "Unknown Size" else show the size in MB // if video size is null or 0, show "Unknown Size" else show the size in MB
(if (video.extraNote != null) " : " else "") + (if (video.size == 0.0) "Unknown Size" else (DecimalFormat( 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)
).format(video.size ?: 0).toString() + " MB")) }")
binding.urlSize.text = sizeText
} }
binding.urlNote.visibility = View.VISIBLE binding.urlNote.visibility = View.VISIBLE
binding.urlNote.text = video.format.name binding.urlNote.text = video.format.name

View file

@ -11,6 +11,7 @@ import ani.dantotsu.connections.comments.Comment
import ani.dantotsu.connections.comments.CommentsAPI import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.copyToClipboard import ani.dantotsu.copyToClipboard
import ani.dantotsu.databinding.ItemCommentsBinding import ani.dantotsu.databinding.ItemCommentsBinding
import ani.dantotsu.getAppString
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
@ -28,6 +29,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.sqrt import kotlin.math.sqrt
@ -52,7 +54,6 @@ class CommentItem(val comment: Comment,
adapter.add(repliesSection) adapter.add(repliesSection)
} }
@SuppressLint("SetTextI18n")
override fun bind(viewBinding: ItemCommentsBinding, position: Int) { override fun bind(viewBinding: ItemCommentsBinding, position: Int) {
binding = viewBinding binding = viewBinding
setAnimation(binding.root.context, binding.root) setAnimation(binding.root.context, binding.root)
@ -76,8 +77,15 @@ class CommentItem(val comment: Comment,
if ((comment.replyCount ?: 0) > 0) { if ((comment.replyCount ?: 0) > 0) {
viewBinding.commentTotalReplies.visibility = View.VISIBLE viewBinding.commentTotalReplies.visibility = View.VISIBLE
viewBinding.commentRepliesDivider.visibility = View.VISIBLE viewBinding.commentRepliesDivider.visibility = View.VISIBLE
viewBinding.commentTotalReplies.text = if(repliesVisible) "Hide Replies" else viewBinding.commentTotalReplies.context.run {
"View ${comment.replyCount} repl${if (comment.replyCount == 1) "y" else "ies"}" 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 { } else {
viewBinding.commentTotalReplies.visibility = View.GONE viewBinding.commentTotalReplies.visibility = View.GONE
viewBinding.commentRepliesDivider.visibility = View.GONE viewBinding.commentRepliesDivider.visibility = View.GONE
@ -87,10 +95,15 @@ class CommentItem(val comment: Comment,
if (repliesVisible) { if (repliesVisible) {
repliesSection.clear() repliesSection.clear()
removeSubCommentIds() 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 repliesVisible = false
} else { } else {
viewBinding.commentTotalReplies.text = "Hide Replies" viewBinding.commentTotalReplies.setText(R.string.hide_replies)
repliesSection.clear() repliesSection.clear()
commentsFragment.viewReplyCallback(this) commentsFragment.viewReplyCallback(this)
repliesVisible = true repliesVisible = true
@ -128,35 +141,37 @@ class CommentItem(val comment: Comment,
viewBinding.modBadge.visibility = if (comment.isMod == true) View.VISIBLE else View.GONE 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.adminBadge.visibility = if (comment.isAdmin == true) View.VISIBLE else View.GONE
viewBinding.commentDelete.setOnClickListener { viewBinding.commentDelete.setOnClickListener {
dialogBuilder("Delete Comment", "Are you sure you want to delete this comment?") { dialogBuilder(getAppString(R.string.delete_comment), getAppString(R.string.delete_comment_confirm)) {
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) CoroutineScope(Dispatchers.Main + SupervisorJob()).launch {
scope.launch {
val success = CommentsAPI.deleteComment(comment.commentId) val success = CommentsAPI.deleteComment(comment.commentId)
if (success) { if (success) {
snackString("Comment Deleted") snackString(R.string.comment_deleted)
parentSection.remove(this@CommentItem) parentSection.remove(this@CommentItem)
} }
} }
} }
} }
viewBinding.commentBanUser.setOnClickListener { viewBinding.commentBanUser.setOnClickListener {
dialogBuilder("Ban User", "Are you sure you want to ban this user?") { dialogBuilder(getAppString(R.string.ban_user), getAppString(R.string.ban_user_confirm)) {
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) CoroutineScope(Dispatchers.Main + SupervisorJob()).launch {
scope.launch {
val success = CommentsAPI.banUser(comment.userId) val success = CommentsAPI.banUser(comment.userId)
if (success) { if (success) {
snackString("User Banned") snackString(R.string.user_banned)
} }
} }
} }
} }
viewBinding.commentReport.setOnClickListener { viewBinding.commentReport.setOnClickListener {
dialogBuilder("Report Comment", "Only report comments that violate the rules. Are you sure you want to report this comment?") { dialogBuilder(getAppString(R.string.report_comment), getAppString(R.string.report_comment_confirm)) {
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) CoroutineScope(Dispatchers.Main + SupervisorJob()).launch {
scope.launch { val success = CommentsAPI.reportComment(
val success = CommentsAPI.reportComment(comment.commentId, comment.username, commentsFragment.mediaName, comment.userId) comment.commentId,
comment.username,
commentsFragment.mediaName,
comment.userId
)
if (success) { 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) } comment.profilePictureUrl?.let { viewBinding.commentUserAvatar.loadImage(it) }
viewBinding.commentUserName.text = comment.username viewBinding.commentUserName.text = comment.username
viewBinding.commentUserLevel.text = "[${levelColor.second}]" val userColor = "[${levelColor.second}]"
viewBinding.commentUserLevel.text = userColor
viewBinding.commentUserLevel.setTextColor(levelColor.first) viewBinding.commentUserLevel.setTextColor(levelColor.first)
viewBinding.commentUserTime.text = formatTimestamp(comment.timestamp) viewBinding.commentUserTime.text = formatTimestamp(comment.timestamp)
} }
@ -243,6 +259,7 @@ class CommentItem(val comment: Comment,
private fun removeSubCommentIds(){ private fun removeSubCommentIds(){
subCommentIds.forEach { id -> subCommentIds.forEach { id ->
@Suppress("UNCHECKED_CAST")
val parentComments = parentSection.groups as? List<CommentItem> ?: emptyList() val parentComments = parentSection.groups as? List<CommentItem> ?: emptyList()
val commentToRemove = parentComments.find { it.comment.commentId == id } val commentToRemove = parentComments.find { it.comment.commentId == id }
commentToRemove?.let { commentToRemove?.let {
@ -272,9 +289,10 @@ class CommentItem(val comment: Comment,
} }
} }
@SuppressLint("SimpleDateFormat")
private fun formatTimestamp(timestamp: String): String { private fun formatTimestamp(timestamp: String): String {
return try { 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") dateFormat.timeZone = TimeZone.getTimeZone("UTC")
val parsedDate = dateFormat.parse(timestamp) val parsedDate = dateFormat.parse(timestamp)
val currentDate = Date() val currentDate = Date()
@ -297,8 +315,9 @@ class CommentItem(val comment: Comment,
} }
companion object { companion object {
@SuppressLint("SimpleDateFormat")
fun timestampToMillis(timestamp: String): Long { 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") dateFormat.timeZone = TimeZone.getTimeZone("UTC")
val parsedDate = dateFormat.parse(timestamp) val parsedDate = dateFormat.parse(timestamp)
return parsedDate?.time ?: 0 return parsedDate?.time ?: 0

View file

@ -523,11 +523,10 @@ class CommentsFragment : Fragment() {
} }
@SuppressLint("SetTextI18n")
fun replyTo(comment: CommentItem, username: String) { fun replyTo(comment: CommentItem, username: String) {
if (comment.isReplying) { if (comment.isReplying) {
activity.binding.commentReplyToContainer.visibility = View.VISIBLE 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 { activity.binding.commentReplyToCancel.setOnClickListener {
comment.replying(false) comment.replying(false)
replyCallback(comment) replyCallback(comment)

View file

@ -2,7 +2,6 @@ package ani.dantotsu.media.manga
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
@ -10,8 +9,8 @@ import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.util.LruCache import android.util.LruCache
import ani.dantotsu.util.Logger
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -25,15 +24,13 @@ data class ImageData(
) { ) {
suspend fun fetchAndProcessImage( suspend fun fetchAndProcessImage(
page: Page, page: Page,
httpSource: HttpSource, httpSource: HttpSource
context: Context
): Bitmap? { ): Bitmap? {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
// Fetch the image // Fetch the image
val response = httpSource.getImage(page) val response = httpSource.getImage(page)
Logger.log("Response: ${response.code}") Logger.log("Response: ${response.code} - ${response.message}")
Logger.log("Response: ${response.message}")
// Convert the Response to an InputStream // Convert the Response to an InputStream
val inputStream = response.body.byteStream() val inputStream = response.body.byteStream()

View file

@ -262,7 +262,6 @@ class MangaChapterAdapter(
} }
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) { when (holder) {
is ChapterCompactViewHolder -> { is ChapterCompactViewHolder -> {

View file

@ -5,8 +5,8 @@ import java.util.regex.Pattern
class MangaNameAdapter { class MangaNameAdapter {
companion object { companion object {
const val chapterRegex = "(chapter|chap|ch|c)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*" private const val chapterRegex = "(chapter|chap|ch|c)[\\s:.\\-]*(\\d+\\.?\\d*)[\\s:.\\-]*"
const val filedChapterNumberRegex = "(?<!part\\s)\\b(\\d+)\\b" private const val filedChapterNumberRegex = "(?<!part\\s)\\b(\\d+)\\b"
fun findChapterNumber(text: String): Float? { fun findChapterNumber(text: String): Float? {
val pattern: Pattern = Pattern.compile(chapterRegex, Pattern.CASE_INSENSITIVE) val pattern: Pattern = Pattern.compile(chapterRegex, Pattern.CASE_INSENSITIVE)
val matcher: Matcher = pattern.matcher(text) val matcher: Matcher = pattern.matcher(text)

View file

@ -13,6 +13,8 @@ import android.widget.LinearLayout
import android.widget.NumberPicker import android.widget.NumberPicker
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.*
@ -58,7 +60,6 @@ class MangaReadAdapter(
private var nestedDialog: AlertDialog? = null private var nestedDialog: AlertDialog? = null
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
_binding = binding _binding = binding
@ -77,14 +78,12 @@ class MangaReadAdapter(
null null
) )
} }
val offline = val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
if (!isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
) View.GONE else View.VISIBLE
binding.animeSourceNameContainer.visibility = offline binding.animeSourceNameContainer.isGone = offline
binding.animeSourceSettings.visibility = offline binding.animeSourceSettings.isGone = offline
binding.animeSourceSearch.visibility = offline binding.animeSourceSearch.isGone = offline
binding.animeSourceTitle.visibility = offline binding.animeSourceTitle.isGone = offline
//Source Selection //Source Selection
var source = var source =
media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it } media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it }
@ -188,8 +187,8 @@ class MangaReadAdapter(
else -> dialogBinding.animeSourceList else -> dialogBinding.animeSourceList
} }
when (style) { when (style) {
0 -> dialogBinding.layoutText.text = "List" 0 -> dialogBinding.layoutText.setText(R.string.list)
1 -> dialogBinding.layoutText.text = "Compact" 1 -> dialogBinding.layoutText.setText(R.string.compact)
else -> dialogBinding.animeSourceList else -> dialogBinding.animeSourceList
} }
selected.alpha = 1f selected.alpha = 1f
@ -201,18 +200,18 @@ class MangaReadAdapter(
dialogBinding.animeSourceList.setOnClickListener { dialogBinding.animeSourceList.setOnClickListener {
selected(it as ImageButton) selected(it as ImageButton)
style = 0 style = 0
dialogBinding.layoutText.text = "List" dialogBinding.layoutText.setText(R.string.list)
run = true run = true
} }
dialogBinding.animeSourceCompact.setOnClickListener { dialogBinding.animeSourceCompact.setOnClickListener {
selected(it as ImageButton) selected(it as ImageButton)
style = 1 style = 1
dialogBinding.layoutText.text = "Compact" dialogBinding.layoutText.setText(R.string.compact)
run = true run = true
} }
dialogBinding.animeWebviewContainer.setOnClickListener { dialogBinding.animeWebviewContainer.setOnClickListener {
if (!WebViewUtil.supportsWebView(fragment.requireContext())) { if (!WebViewUtil.supportsWebView(fragment.requireContext())) {
toast("WebView not installed") toast(R.string.webview_not_installed)
} }
//start CookieCatcher activity //start CookieCatcher activity
if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) {
@ -249,8 +248,7 @@ class MangaReadAdapter(
} }
//Scanlator //Scanlator
dialogBinding.animeScanlatorContainer.visibility = dialogBinding.animeScanlatorContainer.isVisible = options.count() > 1
if (options.count() > 1) View.VISIBLE else View.GONE
dialogBinding.scanlatorNo.text = "${options.count()}" dialogBinding.scanlatorNo.text = "${options.count()}"
dialogBinding.animeScanlatorTop.setOnClickListener { dialogBinding.animeScanlatorTop.setOnClickListener {
val dialogView2 = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null) val dialogView2 = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
@ -359,7 +357,6 @@ class MangaReadAdapter(
} }
//Chips //Chips
@SuppressLint("SetTextI18n")
fun updateChips(limit: Int, names: Array<String>, arr: Array<Int>, selected: Int = 0) { fun updateChips(limit: Int, names: Array<String>, arr: Array<Int>, selected: Int = 0) {
val binding = _binding val binding = _binding
if (binding != null) { if (binding != null) {
@ -395,7 +392,8 @@ class MangaReadAdapter(
names[last - 1] names[last - 1]
} }
//chip.text = "${names[limit * (position)]} - ${names[last - 1]}" //chip.text = "${names[limit * (position)]} - ${names[last - 1]}"
chip.text = "$startChapterString - $endChapterString" val chipText = "$startChapterString - $endChapterString"
chip.text = chipText
chip.setTextColor( chip.setTextColor(
ContextCompat.getColorStateList( ContextCompat.getColorStateList(
fragment.requireContext(), fragment.requireContext(),
@ -429,7 +427,6 @@ class MangaReadAdapter(
_binding?.animeSourceChipGroup?.removeAllViews() _binding?.animeSourceChipGroup?.removeAllViews()
} }
@SuppressLint("SetTextI18n")
fun handleChapters() { fun handleChapters() {
val binding = _binding val binding = _binding
@ -466,7 +463,7 @@ class MangaReadAdapter(
val ep = media.manga.chapters!![continueEp]!! val ep = media.manga.chapters!![continueEp]!!
binding.itemEpisodeImage.loadImage(media.banner ?: media.cover) binding.itemEpisodeImage.loadImage(media.banner ?: media.cover)
binding.animeSourceContinueText.text = 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 { binding.animeSourceContinue.setOnClickListener {
fragment.onMangaChapterClick(continueEp) fragment.onMangaChapterClick(continueEp)
} }
@ -481,13 +478,9 @@ class MangaReadAdapter(
binding.animeSourceContinue.visibility = View.GONE binding.animeSourceContinue.visibility = View.GONE
} }
binding.animeSourceProgressBar.visibility = View.GONE binding.animeSourceProgressBar.visibility = View.GONE
if (media.manga.chapters!!.isNotEmpty()) { val sourceFound = media.manga.chapters!!.isNotEmpty()
binding.animeSourceNotFound.visibility = View.GONE binding.animeSourceNotFound.isGone = sourceFound
binding.faqbutton.visibility = View.GONE binding.faqbutton.isGone = sourceFound
} else {
binding.animeSourceNotFound.visibility = View.VISIBLE
binding.faqbutton.visibility = View.VISIBLE
}
} else { } else {
binding.animeSourceContinue.visibility = View.GONE binding.animeSourceContinue.visibility = View.GONE
binding.animeSourceNotFound.visibility = View.GONE binding.animeSourceNotFound.visibility = View.GONE
@ -519,8 +512,7 @@ class MangaReadAdapter(
parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) } parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) }
) )
val items = adapter.count val items = adapter.count
binding?.animeSourceLanguageContainer?.visibility = binding?.animeSourceLanguageContainer?.isVisible = items > 1
if (items > 1) View.VISIBLE else View.GONE
binding?.animeSourceLanguage?.setAdapter(adapter) binding?.animeSourceLanguage?.setAdapter(adapter)

View file

@ -20,6 +20,8 @@ import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils.clamp import androidx.core.math.MathUtils.clamp
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@ -28,21 +30,25 @@ import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.* import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.databinding.FragmentAnimeWatchBinding
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.download.manga.MangaDownloaderService import ani.dantotsu.download.manga.MangaDownloaderService
import ani.dantotsu.download.manga.MangaServiceDataSingleton import ani.dantotsu.download.manga.MangaServiceDataSingleton
import ani.dantotsu.dp
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.LanguageMapper import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.DynamicMangaParser import ani.dantotsu.parsers.DynamicMangaParser
import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.HMangaSources
import ani.dantotsu.parsers.MangaParser import ani.dantotsu.parsers.MangaParser
import ani.dantotsu.parsers.MangaSources import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.setNavigationTheme
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
@ -354,14 +360,12 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
val changeUIVisibility: (Boolean) -> Unit = { show -> val changeUIVisibility: (Boolean) -> Unit = { show ->
val activity = activity val activity = activity
if (activity is MediaDetailsActivity && isAdded) { if (activity is MediaDetailsActivity && isAdded) {
val visibility = if (show) View.VISIBLE else View.GONE activity.findViewById<AppBarLayout>(R.id.mediaAppBar).isVisible = show
activity.findViewById<AppBarLayout>(R.id.mediaAppBar).visibility = visibility activity.findViewById<ViewPager2>(R.id.mediaViewPager).isVisible = show
activity.findViewById<ViewPager2>(R.id.mediaViewPager).visibility = visibility activity.findViewById<CardView>(R.id.mediaCover).isVisible = show
activity.findViewById<CardView>(R.id.mediaCover).visibility = visibility activity.findViewById<CardView>(R.id.mediaClose).isVisible = show
activity.findViewById<CardView>(R.id.mediaClose).visibility = visibility activity.navBar.isVisible = show
activity.tabLayout.setVisibility(visibility) activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).isGone = show
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility =
if (show) View.GONE else View.VISIBLE
} }
} }
var itemSelected = false var itemSelected = false
@ -492,7 +496,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
DownloadedType( DownloadedType(
media.mainName(), media.mainName(),
i, i,
DownloadedType.Type.MANGA MediaType.MANGA
) )
) )
chapterAdapter.deleteDownload(i) chapterAdapter.deleteDownload(i)
@ -510,7 +514,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
DownloadedType( DownloadedType(
media.mainName(), media.mainName(),
i, i,
DownloadedType.Type.MANGA MediaType.MANGA
) )
) )
chapterAdapter.purgeDownload(i) chapterAdapter.purgeDownload(i)

View file

@ -13,10 +13,14 @@ import androidx.core.view.GestureDetectorCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView 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.MangaCache
import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.px
import ani.dantotsu.settings.CurrentReaderSettings import ani.dantotsu.settings.CurrentReaderSettings
import ani.dantotsu.tryWithSuspend
import com.alexvasilkov.gestures.views.GestureFrameLayout import com.alexvasilkov.gestures.views.GestureFrameLayout
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
@ -118,13 +122,13 @@ abstract class BaseImageAdapter(
abstract suspend fun loadImage(position: Int, parent: View): Boolean abstract suspend fun loadImage(position: Int, parent: View): Boolean
companion object { companion object {
suspend fun Context.loadBitmap_old( suspend fun Context.loadBitmapOld(
link: FileUrl, link: FileUrl,
transforms: List<BitmapTransformation> transforms: List<BitmapTransformation>
): Bitmap? { //still used in some places ): Bitmap? { //still used in some places
return tryWithSuspend { return tryWithSuspend {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Glide.with(this@loadBitmap_old) Glide.with(this@loadBitmapOld)
.asBitmap() .asBitmap()
.let { .let {
if (link.url.startsWith("file://")) { if (link.url.startsWith("file://")) {
@ -168,8 +172,7 @@ abstract class BaseImageAdapter(
mangaCache.get(link.url)?.let { imageData -> mangaCache.get(link.url)?.let { imageData ->
val bitmap = imageData.fetchAndProcessImage( val bitmap = imageData.fetchAndProcessImage(
imageData.page, imageData.page,
imageData.source, imageData.source
context = this@loadBitmap
) )
it.load(bitmap) it.load(bitmap)
.skipMemoryCache(true) .skipMemoryCache(true)

View file

@ -10,8 +10,19 @@ import android.content.res.Resources
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.HapticFeedbackConstants
import android.view.KeyEvent.* 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.view.animation.OvershootInterpolator
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.CheckBox import android.widget.CheckBox
@ -20,13 +31,16 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.math.MathUtils.clamp import androidx.core.math.MathUtils.clamp
import androidx.core.view.GestureDetectorCompat import androidx.core.view.GestureDetectorCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 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.anilist.Anilist
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.connections.discord.Discord 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.DiscordServiceRunningSingleton
import ani.dantotsu.connections.discord.RPC import ani.dantotsu.connections.discord.RPC
import ani.dantotsu.connections.updateProgress import ani.dantotsu.connections.updateProgress
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ActivityMangaReaderBinding 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.Media
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaSingleton import ani.dantotsu.media.MediaSingleton
@ -45,14 +64,25 @@ import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.HMangaSources
import ani.dantotsu.parsers.MangaImage import ani.dantotsu.parsers.MangaImage
import ani.dantotsu.parsers.MangaSources import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.px
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.CurrentReaderSettings import ani.dantotsu.settings.CurrentReaderSettings
import ani.dantotsu.settings.CurrentReaderSettings.Companion.applyWebtoon import ani.dantotsu.settings.CurrentReaderSettings.Companion.applyWebtoon
import ani.dantotsu.settings.CurrentReaderSettings.Directions.* import ani.dantotsu.settings.CurrentReaderSettings.Directions.BOTTOM_TO_TOP
import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.* import ani.dantotsu.settings.CurrentReaderSettings.Directions.LEFT_TO_RIGHT
import ani.dantotsu.settings.CurrentReaderSettings.Layouts.* 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.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.showSystemBarsRetractView
import ani.dantotsu.snackString
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.tryWith
import com.alexvasilkov.gestures.views.GestureFrameLayout import com.alexvasilkov.gestures.views.GestureFrameLayout
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
@ -65,11 +95,11 @@ import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.ObjectInputStream import java.io.ObjectInputStream
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
import java.util.* import java.util.Timer
import java.util.TimerTask
import kotlin.math.min import kotlin.math.min
import kotlin.properties.Delegates import kotlin.properties.Delegates
@SuppressLint("SetTextI18n")
class MangaReaderActivity : AppCompatActivity() { class MangaReaderActivity : AppCompatActivity() {
private val mangaCache = Injekt.get<MangaCache>() private val mangaCache = Injekt.get<MangaCache>()
@ -88,7 +118,6 @@ class MangaReaderActivity : AppCompatActivity() {
private var isContVisible = false private var isContVisible = false
private var showProgressDialog = true private var showProgressDialog = true
private var hidescrollbar = false
private var maxChapterPage = 0L private var maxChapterPage = 0L
private var currentChapterPage = 0L private var currentChapterPage = 0L
@ -123,7 +152,7 @@ class MangaReaderActivity : AppCompatActivity() {
} }
private fun hideSystemBars() { private fun hideSystemBars() {
if (PrefManager.getVal<Boolean>(PrefName.ShowSystemBars)) if (PrefManager.getVal(PrefName.ShowSystemBars))
showSystemBarsRetractView() showSystemBarsRetractView()
else else
hideSystemBarsExtendView() hideSystemBarsExtendView()
@ -223,8 +252,7 @@ class MangaReaderActivity : AppCompatActivity() {
chapter = chapters[media.manga!!.selectedChapter] ?: return chapter = chapters[media.manga!!.selectedChapter] ?: return
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
binding.mangaReaderSource.visibility = binding.mangaReaderSource.isVisible = PrefManager.getVal(PrefName.ShowSource)
if (PrefManager.getVal(PrefName.ShowSource)) View.VISIBLE else View.GONE
if (model.mangaReadSources!!.names.isEmpty()) { if (model.mangaReadSources!!.names.isEmpty()) {
//try to reload sources //try to reload sources
try { try {
@ -369,7 +397,7 @@ class MangaReaderActivity : AppCompatActivity() {
RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""), RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""),
RPC.Link( RPC.Link(
"Stream on Dantotsu", "Stream on Dantotsu",
"https://github.com/rebelonion/Dantotsu/" getString(R.string.github)
) )
) )
) )
@ -741,12 +769,12 @@ class MangaReaderActivity : AppCompatActivity() {
goneTimer.schedule(timerTask, controllerDuration) goneTimer.schedule(timerTask, controllerDuration)
} }
enum class pressPos { enum class PressPos {
LEFT, RIGHT, CENTER LEFT, RIGHT, CENTER
} }
fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) { fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) {
var pressLocation = pressPos.CENTER var pressLocation = PressPos.CENTER
if (!sliding) { if (!sliding) {
if (event != null && defaultSettings.layout == PAGED) { if (event != null && defaultSettings.layout == PAGED) {
if (event.action != MotionEvent.ACTION_UP) return 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 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) { if (screenWidth / 5 in x + 1..<y) {
pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) { pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
pressPos.RIGHT PressPos.RIGHT
} else { } 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 //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) { else if (x > screenWidth - screenWidth / 5 && y > screenWidth / 5) {
pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) { pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) {
pressPos.LEFT PressPos.LEFT
} else { } else {
pressPos.RIGHT PressPos.RIGHT
} }
} }
} }
// if pressLocation is left or right go to previous or next page (paged mode only) // 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 (binding.mangaReaderPager.currentItem > 0) {
//if the current images zoomed in, go back to normal before going to previous page //if the current images zoomed in, go back to normal before going to previous page
@ -783,7 +811,7 @@ class MangaReaderActivity : AppCompatActivity() {
return return
} }
} else if (pressLocation == pressPos.RIGHT) { } else if (pressLocation == PressPos.RIGHT) {
if (binding.mangaReaderPager.currentItem < maxChapterPage - 1) { if (binding.mangaReaderPager.currentItem < maxChapterPage - 1) {
//if the current images zoomed in, go back to normal before going to next page //if the current images zoomed in, go back to normal before going to next page
if (imageAdapter?.isZoomed() == true) { if (imageAdapter?.isZoomed() == true) {
@ -961,7 +989,7 @@ class MangaReaderActivity : AppCompatActivity() {
if (!incognito && PrefManager.getCustomVal( if (!incognito && PrefManager.getCustomVal(
"${media.id}_save_progress", "${media.id}_save_progress",
true true
) && if (media.isAdult) PrefManager.getVal<Boolean>(PrefName.UpdateForHReader) else true ) && if (media.isAdult) PrefManager.getVal(PrefName.UpdateForHReader) else true
) )
updateProgress( updateProgress(
media, media,

View file

@ -9,7 +9,6 @@ import android.os.Environment
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Parcelable import android.os.Parcelable
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -28,6 +27,7 @@ import ani.dantotsu.download.novel.NovelDownloaderService
import ani.dantotsu.download.novel.NovelServiceDataSingleton import ani.dantotsu.download.novel.NovelServiceDataSingleton
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.novel.novelreader.NovelReaderActivity import ani.dantotsu.media.novel.novelreader.NovelReaderActivity
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.parsers.ShowResponse
@ -90,7 +90,7 @@ class NovelReadFragment : Fragment(),
DownloadedType( DownloadedType(
media.mainName(), media.mainName(),
novel.name, novel.name,
DownloadedType.Type.NOVEL MediaType.NOVEL
) )
) )
) { ) {
@ -122,7 +122,7 @@ class NovelReadFragment : Fragment(),
DownloadedType( DownloadedType(
media.mainName(), media.mainName(),
novel.name, novel.name,
DownloadedType.Type.NOVEL MediaType.NOVEL
) )
) )
} }
@ -133,7 +133,7 @@ class NovelReadFragment : Fragment(),
DownloadedType( DownloadedType(
media.mainName(), media.mainName(),
novel.name, novel.name,
DownloadedType.Type.NOVEL MediaType.NOVEL
) )
) )
} }

View file

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.databinding.ItemNovelResponseBinding import ani.dantotsu.databinding.ItemNovelResponseBinding
@ -71,8 +72,7 @@ class NovelResponseAdapter(
} }
binding.itemEpisodeDesc2.text = novel.extra?.get("1") ?: "" binding.itemEpisodeDesc2.text = novel.extra?.get("1") ?: ""
val desc = novel.extra?.get("2") val desc = novel.extra?.get("2")
binding.itemEpisodeDesc.visibility = binding.itemEpisodeDesc.isVisible = !desc.isNullOrBlank()
if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE
binding.itemEpisodeDesc.text = desc ?: "" binding.itemEpisodeDesc.text = desc ?: ""
binding.root.setOnClickListener { binding.root.setOnClickListener {

View file

@ -31,7 +31,6 @@ class UrlAdapter(
) )
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: UrlViewHolder, position: Int) { override fun onBindViewHolder(holder: UrlViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
val url = urls[position] val url = urls[position]

View file

@ -6,7 +6,6 @@ import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.view.WindowManager
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
@ -17,7 +16,7 @@ import androidx.lifecycle.lifecycleScope
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.Refresh import ani.dantotsu.Refresh
import ani.dantotsu.databinding.ActivityListBinding import ani.dantotsu.databinding.ActivityListBinding
import ani.dantotsu.navBarHeight import ani.dantotsu.hideSystemBarsExtendView
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
@ -33,7 +32,6 @@ class ListActivity : AppCompatActivity() {
private val scope = lifecycleScope private val scope = lifecycleScope
private var selectedTabIdx = 0 private var selectedTabIdx = 0
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -72,10 +70,7 @@ class ListActivity : AppCompatActivity() {
} else { } else {
binding.root.fitsSystemWindows = false binding.root.fitsSystemWindows = false
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
window.setFlags( hideSystemBarsExtendView()
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.settingsContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight topMargin = statusBarHeight
} }
@ -83,8 +78,8 @@ class ListActivity : AppCompatActivity() {
setContentView(binding.root) setContentView(binding.root)
val anime = intent.getBooleanExtra("anime", true) val anime = intent.getBooleanExtra("anime", true)
binding.listTitle.text = binding.listTitle.text = getString(R.string.user_list, intent.getStringExtra("username"),
intent.getStringExtra("username") + "'s " + (if (anime) "Anime" else "Manga") + " List" if (anime) getString(R.string.anime) else getString(R.string.manga))
binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) { override fun onTabSelected(tab: TabLayout.Tab?) {
this@ListActivity.selectedTabIdx = tab?.position ?: 0 this@ListActivity.selectedTabIdx = tab?.position ?: 0

View file

@ -11,7 +11,7 @@ class AndroidBug5497Workaround private constructor(activity: Activity, private v
private val frameLayoutParams: FrameLayout.LayoutParams private val frameLayoutParams: FrameLayout.LayoutParams
init { 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 = content.getChildAt(0)
mChildOfContent.viewTreeObserver.addOnGlobalLayoutListener { possiblyResizeChildOfContent() } mChildOfContent.viewTreeObserver.addOnGlobalLayoutListener { possiblyResizeChildOfContent() }
frameLayoutParams = mChildOfContent.layoutParams as FrameLayout.LayoutParams frameLayoutParams = mChildOfContent.layoutParams as FrameLayout.LayoutParams
@ -42,9 +42,15 @@ class AndroidBug5497Workaround private constructor(activity: Activity, private v
return r.bottom 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 { 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) { fun assistActivity(activity: Activity, callback: (Boolean) -> Unit) {
AndroidBug5497Workaround(activity, callback) AndroidBug5497Workaround(activity, callback)
} }

View file

@ -14,7 +14,7 @@ import ani.dantotsu.R
import ani.dantotsu.databinding.BottomSheetImageBinding import ani.dantotsu.databinding.BottomSheetImageBinding
import ani.dantotsu.downloadsPermission import ani.dantotsu.downloadsPermission
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmapOld
import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap
import ani.dantotsu.openLinkInBrowser import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.saveImageToDownloads import ani.dantotsu.saveImageToDownloads
@ -84,9 +84,9 @@ class ImageViewDialog : BottomSheetDialogFragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
val binding = _binding ?: return@launch val binding = _binding ?: return@launch
var bitmap = context.loadBitmap_old(image, trans1 ?: listOf()) var bitmap = context.loadBitmapOld(image, trans1 ?: listOf())
var bitmap2 = 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) { if (bitmap == null) {
bitmap = context.loadBitmap(image, trans1 ?: listOf()) bitmap = context.loadBitmap(image, trans1 ?: listOf())
bitmap2 = bitmap2 =

View file

@ -1,9 +1,11 @@
package ani.dantotsu.others package ani.dantotsu.others
import android.content.Context import android.content.Context
import android.content.res.Resources
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import ani.dantotsu.R import ani.dantotsu.R
@ -54,14 +56,14 @@ class OutlineTextView : AppCompatTextView {
setStrokeWidth(strokeWidth) setStrokeWidth(strokeWidth)
} }
private val Float.toPx get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics
)
private fun setStrokeWidth(width: Float) { 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() { override fun invalidate() {
if (isDrawing) return if (isDrawing) return
super.invalidate() super.invalidate()

View file

@ -12,19 +12,19 @@ import androidx.appcompat.app.AppCompatActivity
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class CookieCatcher : AppCompatActivity() { class CookieCatcher : AppCompatActivity() {
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
@Suppress("UNCHECKED_CAST")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
//get url from intent //get url from intent
val url = intent.getStringExtra("url") ?: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" val url = intent.getStringExtra("url") ?: getString(R.string.cursed_yt)
val headers: Map<String, String> = intent.getSerializableExtra("headers") as? Map<String, String> ?: emptyMap() val headers: Map<String, String> = intent.getSerializableExtraCompat("headers") as? Map<String, String> ?: emptyMap()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = Application.getProcessName() val process = Application.getProcessName()

View file

@ -11,11 +11,11 @@ import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import ani.dantotsu.FileUrl import ani.dantotsu.FileUrl
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.util.Logger
import ani.dantotsu.media.anime.AnimeNameAdapter import ani.dantotsu.media.anime.AnimeNameAdapter
import ani.dantotsu.media.manga.ImageData import ani.dantotsu.media.manga.ImageData
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimesPage import eu.kanade.tachiyomi.animesource.model.AnimesPage
@ -59,8 +59,6 @@ class AniyomiAdapter {
fun aniyomiToAnimeParser(extension: AnimeExtension.Installed): DynamicAnimeParser { fun aniyomiToAnimeParser(extension: AnimeExtension.Installed): DynamicAnimeParser {
return DynamicAnimeParser(extension) return DynamicAnimeParser(extension)
} }
} }
class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { 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 // Group by season, sort within each season, and then renumber while keeping episode number 0 as is
val seasonGroups = val seasonGroups =
res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 } res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 }
seasonGroups.keys.sortedBy { it.toInt() } seasonGroups.keys.sortedBy { it }
.flatMap { season -> .flatMap { season ->
seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode -> seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode ->
if (episode.episode_number != 0f) { // Skip renumbering for episode number 0 if (episode.episode_number != 0f) { // Skip renumbering for episode number 0
@ -209,7 +207,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
} ?: emptyList() } ?: emptyList()
} }
} }
return sortedEpisodes.map { SEpisodeToEpisode(it) } return sortedEpisodes.map { sEpisodeToEpisode(it) }
} catch (e: Exception) { } catch (e: Exception) {
Logger.log("Exception: $e") Logger.log("Exception: $e")
} }
@ -244,7 +242,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
return try { return try {
val videos = source.getVideoList(sEpisode) val videos = source.getVideoList(sEpisode)
videos.map { VideoToVideoServer(it) } videos.map { videoToVideoServer(it) }
} catch (e: Exception) { } catch (e: Exception) {
Logger.log("Exception occurred: ${e.message}") Logger.log("Exception occurred: ${e.message}")
emptyList() 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 //if the float episode number is a whole number, convert it to an int
val episodeNumberInt = val episodeNumberInt =
if (sEpisode.episode_number % 1 == 0f) { 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( return VideoServer(
video.quality, video.quality,
video.url, video.url,
@ -363,7 +361,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
return try { return try {
val res = source.getChapterList(sManga) val res = source.getChapterList(sManga)
val reversedRes = res.reversed() 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 size: ${chapterList.size}")
Logger.log("chapterList: ${chapterList[1].title}") Logger.log("chapterList: ${chapterList[1].title}")
Logger.log("chapterList: ${chapterList[1].description}") Logger.log("chapterList: ${chapterList[1].description}")
@ -382,7 +380,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
sourceLanguage = 0 sourceLanguage = 0
extension.sources[sourceLanguage] extension.sources[sourceLanguage]
} as? HttpSource ?: return emptyList() } as? HttpSource ?: return emptyList()
var imageDataList: List<ImageData> = listOf() val imageDataList: MutableList<ImageData> = mutableListOf()
val ret = coroutineScope { val ret = coroutineScope {
try { try {
Logger.log("source.name " + source.name) 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( return MangaChapter(
sChapter.name, sChapter.name,
sChapter.url, sChapter.url,
@ -676,8 +674,8 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() {
get() = videoServer get() = videoServer
override suspend fun extract(): VideoContainer { override suspend fun extract(): VideoContainer {
val vidList = listOfNotNull(videoServer.video?.let { AniVideoToSaiVideo(it) }) val vidList = listOfNotNull(videoServer.video?.let { aniVideoToSaiVideo(it) })
val subList = videoServer.video?.subtitleTracks?.map { TrackToSubtitle(it) } ?: emptyList() val subList = videoServer.video?.subtitleTracks?.map { trackToSubtitle(it) } ?: emptyList()
return if (vidList.isNotEmpty()) { return if (vidList.isNotEmpty()) {
VideoContainer(vidList, subList) 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 // Find the number value from the .quality string
val number = Regex("""\d+""").find(aniVideo.quality)?.value?.toInt() ?: 0 val number = Regex("""\d+""").find(aniVideo.quality)?.value?.toInt() ?: 0
@ -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 //use Dispatchers.IO to make a HTTP request to determine the subtitle type
var type: SubtitleType? = null var type: SubtitleType?
runBlocking { runBlocking {
type = findSubtitleType(track.url) type = findSubtitleType(track.url)
} }

View file

@ -4,6 +4,7 @@ import android.net.Uri
import android.os.Environment import android.os.Environment
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.anime.AnimeNameAdapter import ani.dantotsu.media.anime.AnimeNameAdapter
import ani.dantotsu.tryWithSuspend import ani.dantotsu.tryWithSuspend
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
@ -132,16 +133,16 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() {
currContext()?.let { currContext()?.let {
DownloadsManager.getDirectory( DownloadsManager.getDirectory(
it, it,
ani.dantotsu.download.DownloadedType.Type.ANIME, MediaType.ANIME,
title, title,
episode episode
).listFiles()?.forEach { ).listFiles()?.forEach { file ->
if (it.name.contains("subtitle")) { if (file.name.contains("subtitle")) {
return listOf( return listOf(
Subtitle( Subtitle(
"Downloaded Subtitle", "Downloaded Subtitle",
Uri.fromFile(it).toString(), Uri.fromFile(file).toString(),
determineSubtitletype(it.absolutePath) determineSubtitletype(file.absolutePath)
) )
) )
} }

View file

@ -1,7 +1,6 @@
package ani.dantotsu.parsers.novel package ani.dantotsu.parsers.novel
import android.os.FileObserver import android.os.FileObserver
import android.util.Log
import ani.dantotsu.parsers.novel.FileObserver.fileObserver import ani.dantotsu.parsers.novel.FileObserver.fileObserver
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import java.io.File import java.io.File

View file

@ -10,7 +10,6 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.net.toUri import androidx.core.net.toUri
@ -63,7 +62,7 @@ internal class NovelExtensionInstaller(private val context: Context) {
* @param url The url of the apk. * @param url The url of the apk.
* @param extension The extension to install. * @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 pkgName = extension.pkgName
val oldDownload = activeDownloads[pkgName] val oldDownload = activeDownloads[pkgName]

View file

@ -5,11 +5,10 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager.GET_SIGNATURES import android.content.pm.PackageManager.GET_SIGNATURES
import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
import android.os.Build import android.os.Build
import android.util.Log
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.util.Logger
import ani.dantotsu.parsers.NovelInterface import ani.dantotsu.parsers.NovelInterface
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import dalvik.system.PathClassLoader import dalvik.system.PathClassLoader
import eu.kanade.tachiyomi.util.lang.Hash import eu.kanade.tachiyomi.util.lang.Hash
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -134,10 +133,10 @@ internal object NovelExtensionLoader {
} }
Logger.log("isFileWritable: ${file.canWrite()}") Logger.log("isFileWritable: ${file.canWrite()}")
val classLoader = PathClassLoader(file.absolutePath, null, context.classLoader) val classLoader = PathClassLoader(file.absolutePath, null, context.classLoader)
val className = val extensionClassName =
"some.random.novelextensions.${className.lowercase(Locale.getDefault())}.$className" "some.random.novelextensions.${className.lowercase(Locale.getDefault())}.$className"
val loadedClass = classLoader.loadClass(className) val loadedClass = classLoader.loadClass(extensionClassName)
val instance = loadedClass.newInstance() val instance = loadedClass.getDeclaredConstructor().newInstance()
val novelInterfaceInstance = instance as? NovelInterface val novelInterfaceInstance = instance as? NovelInterface
listOfNotNull(novelInterfaceInstance) listOfNotNull(novelInterfaceInstance)
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -3,6 +3,7 @@ package ani.dantotsu.profile
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
@ -11,6 +12,7 @@ import android.widget.PopupMenu
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -47,7 +49,6 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
private var selected: Int = 0 private var selected: Int = 0
private lateinit var navBar: AnimatedBottomBar private lateinit var navBar: AnimatedBottomBar
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
@ -56,8 +57,14 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
setContentView(binding.root) setContentView(binding.root)
screenWidth = resources.displayMetrics.widthPixels.toFloat() screenWidth = resources.displayMetrics.widthPixels.toFloat()
navBar = binding.profileNavBar 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 feedTab = navBar.createTab(R.drawable.ic_round_filter_24, "Feed")
val profileTab = navBar.createTab(R.drawable.ic_round_person_24, "Profile") val profileTab = navBar.createTab(R.drawable.ic_round_person_24, "Profile")
val statsTab = navBar.createTab(R.drawable.ic_stats_24, "Stats") val statsTab = navBar.createTab(R.drawable.ic_stats_24, "Stats")
@ -105,20 +112,30 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
val userLevel = intent.getStringExtra("userLVL") ?: "" val userLevel = intent.getStringExtra("userLVL") ?: ""
binding.followButton.visibility = binding.followButton.visibility =
if (user.id == Anilist.userid || Anilist.userid == null) View.GONE else View.VISIBLE if (user.id == Anilist.userid || Anilist.userid == null) View.GONE else View.VISIBLE
binding.followButton.text = binding.followButton.text = getString(
if (user.isFollowing) "Unfollow" else if (user.isFollower) "Follows you" else "Follow" when {
if (user.isFollowing && user.isFollower) binding.followButton.text = "Mutual" 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 { binding.followButton.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val res = Anilist.query.toggleFollow(user.id) val res = Anilist.query.toggleFollow(user.id)
if (res?.data?.toggleFollow != null) { if (res?.data?.toggleFollow != null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
snackString("Success") snackString(R.string.success)
user.isFollowing = res.data.toggleFollow.isFollowing user.isFollowing = res.data.toggleFollow.isFollowing
binding.followButton.text = binding.followButton.text = getString(
if (user.isFollowing) "Unfollow" else if (user.isFollower) "Follows you" else "Follow" when {
if (user.isFollowing && user.isFollower) binding.followButton.text = user.isFollowing -> R.string.unfollow
"Mutual" 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() if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.profileBannerImage.pause()
blurImage(binding.profileBannerImage, user.bannerImage ?: user.avatar?.medium) blurImage(binding.profileBannerImage, user.bannerImage ?: user.avatar?.medium)
binding.profileBannerImage.updateLayoutParams { height += statusBarHeight } binding.profileBannerImage.updateLayoutParams { height += statusBarHeight }
@ -196,7 +214,7 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
ContextCompat.startActivity( ContextCompat.startActivity(
this@ProfileActivity, this@ProfileActivity,
Intent(this@ProfileActivity, FollowActivity::class.java) Intent(this@ProfileActivity, FollowActivity::class.java)
.putExtra("title", "Followers") .putExtra("title", getString(R.string.followers))
.putExtra("userId", user.id), .putExtra("userId", user.id),
null 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() { override fun onResume() {
if (this::navBar.isInitialized) { if (this::navBar.isInitialized) {
navBar.selectTabAt(selected) navBar.selectTabAt(selected)

View file

@ -11,6 +11,7 @@ import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
@ -33,6 +34,7 @@ import ani.dantotsu.setSlideIn
import ani.dantotsu.setSlideUp import ani.dantotsu.setSlideUp
import ani.dantotsu.util.AniMarkdown.Companion.getFullAniHTML import ani.dantotsu.util.AniMarkdown.Companion.getFullAniHTML
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.util.system.getSerializableCompat
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -57,7 +59,7 @@ class ProfileFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity = requireActivity() as ProfileActivity 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) { viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
model.setData(user.id) model.setData(user.id)
} }
@ -101,8 +103,7 @@ class ProfileFragment : Fragment() {
} }
} }
binding.userInfoContainer.visibility = binding.userInfoContainer.isVisible = user.about != null
if (user.about != null) View.VISIBLE else View.GONE
binding.statsEpisodesWatched.text = user.statistics.anime.episodesWatched.toString() binding.statsEpisodesWatched.text = user.statistics.anime.episodesWatched.toString()

View file

@ -20,6 +20,7 @@ import ani.dantotsu.profile.ChartBuilder.Companion.StatType
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType
import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.GroupieAdapter
import eu.kanade.tachiyomi.util.system.getSerializableCompat
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -48,7 +49,7 @@ class StatsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity = requireActivity() as ProfileActivity 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.adapter = adapter
binding.statisticList.recycledViewPool.setMaxRecycledViews(0, 0) binding.statisticList.recycledViewPool.setMaxRecycledViews(0, 0)
@ -95,7 +96,7 @@ class StatsFragment :
} }
} else { } else {
stats.removeAll( stats.removeAll(
stats.filter { it?.id == Anilist.userid } stats.filter { it?.id == Anilist.userid }.toSet()
) )
loadStats(type == MediaType.ANIME) loadStats(type == MediaType.ANIME)
} }
@ -445,6 +446,7 @@ class StatsFragment :
}.toMutableList() }.toMutableList()
chartPackets.clear() chartPackets.clear()
chartPackets.addAll(standardizedPackets) chartPackets.addAll(standardizedPackets)
@Suppress("UNCHECKED_CAST")
val genreChart = ChartBuilder.buildChart( val genreChart = ChartBuilder.buildChart(
activity, activity,
ChartType.TwoDimensional, ChartType.TwoDimensional,
@ -499,6 +501,7 @@ class StatsFragment :
}.toMutableList() }.toMutableList()
chartPackets.clear() chartPackets.clear()
chartPackets.addAll(standardizedPackets) chartPackets.addAll(standardizedPackets)
@Suppress("UNCHECKED_CAST")
val tagChart = ChartBuilder.buildChart( val tagChart = ChartBuilder.buildChart(
activity, activity,
ChartType.TwoDimensional, ChartType.TwoDimensional,
@ -553,6 +556,7 @@ class StatsFragment :
}.toMutableList() }.toMutableList()
chartPackets.clear() chartPackets.clear()
chartPackets.addAll(standardizedPackets) chartPackets.addAll(standardizedPackets)
@Suppress("UNCHECKED_CAST")
val countryChart = ChartBuilder.buildChart( val countryChart = ChartBuilder.buildChart(
activity, activity,
ChartType.OneDimensional, ChartType.OneDimensional,
@ -609,6 +613,7 @@ class StatsFragment :
}.toMutableList() }.toMutableList()
chartPackets.clear() chartPackets.clear()
chartPackets.addAll(standardizedPackets) chartPackets.addAll(standardizedPackets)
@Suppress("UNCHECKED_CAST")
val voiceActorsChart = ChartBuilder.buildChart( val voiceActorsChart = ChartBuilder.buildChart(
activity, activity,
ChartType.TwoDimensional, ChartType.TwoDimensional,
@ -663,6 +668,7 @@ class StatsFragment :
}.toMutableList() }.toMutableList()
chartPackets.clear() chartPackets.clear()
chartPackets.addAll(standardizedPackets) chartPackets.addAll(standardizedPackets)
@Suppress("UNCHECKED_CAST")
val studioChart = ChartBuilder.buildChart( val studioChart = ChartBuilder.buildChart(
activity, activity,
ChartType.TwoDimensional, ChartType.TwoDimensional,
@ -720,6 +726,7 @@ class StatsFragment :
}.toMutableList() }.toMutableList()
chartPackets.clear() chartPackets.clear()
chartPackets.addAll(standardizedPackets) chartPackets.addAll(standardizedPackets)
@Suppress("UNCHECKED_CAST")
val staffChart = ChartBuilder.buildChart( val staffChart = ChartBuilder.buildChart(
activity, activity,
ChartType.TwoDimensional, ChartType.TwoDimensional,

View file

@ -3,6 +3,7 @@ package ani.dantotsu.profile.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R import ani.dantotsu.R
@ -38,7 +39,6 @@ class ActivityItem(
private lateinit var binding: ItemActivityBinding private lateinit var binding: ItemActivityBinding
private lateinit var repliesAdapter: GroupieAdapter private lateinit var repliesAdapter: GroupieAdapter
@SuppressLint("SetTextI18n")
override fun bind(viewBinding: ItemActivityBinding, position: Int) { override fun bind(viewBinding: ItemActivityBinding, position: Int) {
binding = viewBinding binding = viewBinding
setAnimation(binding.root.context, binding.root) setAnimation(binding.root.context, binding.root)
@ -58,8 +58,7 @@ class ActivityItem(
val likeColor = ContextCompat.getColor(binding.root.context, R.color.yt_red) val likeColor = ContextCompat.getColor(binding.root.context, R.color.yt_red)
val notLikeColor = ContextCompat.getColor(binding.root.context, R.color.bg_opp) val notLikeColor = ContextCompat.getColor(binding.root.context, R.color.bg_opp)
binding.activityLike.setColorFilter(if (activity.isLiked == true) likeColor else notLikeColor) binding.activityLike.setColorFilter(if (activity.isLiked == true) likeColor else notLikeColor)
binding.commentRepliesContainer.visibility = binding.commentRepliesContainer.isVisible = activity.replyCount > 0
if (activity.replyCount > 0) View.VISIBLE else View.GONE
binding.commentRepliesContainer.setOnClickListener { binding.commentRepliesContainer.setOnClickListener {
when (binding.activityReplies.visibility) { when (binding.activityReplies.visibility) {
View.GONE -> { View.GONE -> {
@ -73,13 +72,13 @@ class ActivityItem(
} ?: emptyList() } ?: emptyList()
repliesAdapter.addAll(replyItems) repliesAdapter.addAll(replyItems)
binding.activityReplies.visibility = View.VISIBLE binding.activityReplies.visibility = View.VISIBLE
binding.commentTotalReplies.text = "Hide replies" binding.commentTotalReplies.setText(R.string.hide_replies)
} }
else -> { else -> {
repliesAdapter.clear() repliesAdapter.clear()
binding.activityReplies.visibility = View.GONE 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.activityContent.visibility = View.GONE
binding.activityBannerContainer.visibility = View.VISIBLE binding.activityBannerContainer.visibility = View.VISIBLE
binding.activityMediaName.text = activity.media?.title?.userPreferred 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) binding.activityCover.loadImage(cover)
blurImage(binding.activityBannerImage, banner ?: cover) blurImage(binding.activityBannerImage, banner ?: cover)
binding.activityAvatarContainer.setOnClickListener { binding.activityAvatarContainer.setOnClickListener {

View file

@ -1,9 +1,11 @@
package ani.dantotsu.profile.activity package ani.dantotsu.profile.activity
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -28,14 +30,16 @@ class FeedActivity : AppCompatActivity() {
binding = ActivityFeedBinding.inflate(layoutInflater) binding = ActivityFeedBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
navBar = binding.feedNavBar 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 personalTab = navBar.createTab(R.drawable.ic_round_person_24, "Following")
val globalTab = navBar.createTab(R.drawable.ic_globe_24, "Global") val globalTab = navBar.createTab(R.drawable.ic_globe_24, "Global")
navBar.addTab(personalTab) navBar.addTab(personalTab)
navBar.addTab(globalTab) navBar.addTab(globalTab)
binding.listTitle.text = getString(R.string.activities) binding.listTitle.text = getString(R.string.activities)
binding.feedViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.feedViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin += navBarHeight bottomMargin = navBarMargin
topMargin += statusBarHeight topMargin += statusBarHeight
} }
binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight } binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
@ -57,10 +61,20 @@ class FeedActivity : AppCompatActivity() {
} }
}) })
binding.listBack.setOnClickListener { 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() { override fun onResume() {
super.onResume() super.onResume()
navBar.selectTabAt(selected) navBar.selectTabAt(selected)

View file

@ -11,6 +11,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.Notification import ani.dantotsu.connections.anilist.api.Notification
import ani.dantotsu.databinding.ActivityFollowBinding import ani.dantotsu.databinding.ActivityFollowBinding
@ -37,14 +38,14 @@ class NotificationActivity : AppCompatActivity() {
private var currentPage: Int = 1 private var currentPage: Int = 1
private var hasNextPage: Boolean = true private var hasNextPage: Boolean = true
@SuppressLint("SetTextI18n", "ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
initActivity(this) initActivity(this)
binding = ActivityFollowBinding.inflate(layoutInflater) binding = ActivityFollowBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
binding.listTitle.text = "Notifications" binding.listTitle.text = getString(R.string.notifications)
binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight topMargin = statusBarHeight
} }
@ -57,7 +58,7 @@ class NotificationActivity : AppCompatActivity() {
binding.followerGrid.visibility = ViewGroup.GONE binding.followerGrid.visibility = ViewGroup.GONE
binding.followerList.visibility = ViewGroup.GONE binding.followerList.visibility = ViewGroup.GONE
binding.listBack.setOnClickListener { binding.listBack.setOnClickListener {
onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
binding.listProgressBar.visibility = ViewGroup.VISIBLE binding.listProgressBar.visibility = ViewGroup.VISIBLE
val activityId = intent.getIntExtra("activityId", -1) val activityId = intent.getIntExtra("activityId", -1)

View file

@ -12,6 +12,7 @@ import ani.dantotsu.databinding.ItemNotificationBinding
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.profile.activity.NotificationActivity.Companion.NotificationClickType import ani.dantotsu.profile.activity.NotificationActivity.Companion.NotificationClickType
import ani.dantotsu.setAnimation import ani.dantotsu.setAnimation
import ani.dantotsu.toPx
import com.xwray.groupie.viewbinding.BindableItem import com.xwray.groupie.viewbinding.BindableItem
class NotificationItem( class NotificationItem(
@ -40,23 +41,11 @@ class NotificationItem(
?: notification.media?.coverImage?.large ?: notification.media?.coverImage?.large
blurImage(binding.notificationBannerImage, cover) blurImage(binding.notificationBannerImage, cover)
val defaultHeight = TypedValue.applyDimension( val defaultHeight = 153.toPx
TypedValue.COMPLEX_UNIT_DIP,
153f,
binding.root.context.resources.displayMetrics
).toInt()
val userHeight = TypedValue.applyDimension( val userHeight = 90.toPx
TypedValue.COMPLEX_UNIT_DIP,
90f,
binding.root.context.resources.displayMetrics
).toInt()
val textMarginStart = TypedValue.applyDimension( val textMarginStart = 125.toPx
TypedValue.COMPLEX_UNIT_DIP,
125f,
binding.root.context.resources.displayMetrics
).toInt()
if (user) { if (user) {
binding.notificationCover.visibility = View.GONE binding.notificationCover.visibility = View.GONE

View file

@ -29,7 +29,6 @@ import com.google.android.material.tabs.TabLayoutMediator
class ExtensionsActivity : AppCompatActivity() { class ExtensionsActivity : AppCompatActivity() {
lateinit var binding: ActivityExtensionsBinding lateinit var binding: ActivityExtensionsBinding
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -14,6 +14,8 @@ import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -59,15 +61,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
val name = pkg.name val name = pkg.name
val changeUIVisibility: (Boolean) -> Unit = { show -> val changeUIVisibility: (Boolean) -> Unit = { show ->
val activity = requireActivity() as ExtensionsActivity val activity = requireActivity() as ExtensionsActivity
val visibility = if (show) View.VISIBLE else View.GONE activity.findViewById<ViewPager2>(R.id.viewPager).isVisible = show
activity.findViewById<ViewPager2>(R.id.viewPager).visibility = visibility activity.findViewById<TabLayout>(R.id.tabLayout).isVisible = show
activity.findViewById<TabLayout>(R.id.tabLayout).visibility = visibility activity.findViewById<TextInputLayout>(R.id.searchView).isVisible = show
activity.findViewById<TextInputLayout>(R.id.searchView).visibility = visibility activity.findViewById<ImageView>(R.id.languageselect).isVisible = show
activity.findViewById<ImageView>(R.id.languageselect).visibility = visibility
activity.findViewById<TextView>(R.id.extensions).text = activity.findViewById<TextView>(R.id.extensions).text =
if (show) getString(R.string.extensions) else name if (show) getString(R.string.extensions) else name
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility = activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).isGone = show
if (show) View.GONE else View.VISIBLE
} }
var itemSelected = false var itemSelected = false
val allSettings = pkg.sources.filterIsInstance<ConfigurableAnimeSource>() val allSettings = pkg.sources.filterIsInstance<ConfigurableAnimeSource>()
@ -294,13 +294,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler {
return ViewHolder(view) return ViewHolder(view)
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = getItem(position) val extension = getItem(position)
val nsfw = if (extension.isNsfw) "(18+)" else "" val nsfw = if (extension.isNsfw) "(18+)" else ""
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" val versionText = "$lang ${extension.versionName} $nsfw"
holder.extensionVersionTextView.text = versionText
if (!skipIcons) { if (!skipIcons) {
holder.extensionIconImageView.setImageDrawable(extension.icon) holder.extensionIconImageView.setImageDrawable(extension.icon)
} }

View file

@ -15,6 +15,8 @@ import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -57,15 +59,13 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
val name = pkg.name val name = pkg.name
val changeUIVisibility: (Boolean) -> Unit = { show -> val changeUIVisibility: (Boolean) -> Unit = { show ->
val activity = requireActivity() as ExtensionsActivity val activity = requireActivity() as ExtensionsActivity
val visibility = if (show) View.VISIBLE else View.GONE activity.findViewById<ViewPager2>(R.id.viewPager).isVisible = show
activity.findViewById<ViewPager2>(R.id.viewPager).visibility = visibility activity.findViewById<TabLayout>(R.id.tabLayout).isVisible = show
activity.findViewById<TabLayout>(R.id.tabLayout).visibility = visibility activity.findViewById<TextInputLayout>(R.id.searchView).isVisible = show
activity.findViewById<TextInputLayout>(R.id.searchView).visibility = visibility activity.findViewById<ImageView>(R.id.languageselect).isVisible = show
activity.findViewById<ImageView>(R.id.languageselect).visibility = visibility
activity.findViewById<TextView>(R.id.extensions).text = activity.findViewById<TextView>(R.id.extensions).text =
if (show) getString(R.string.extensions) else name if (show) getString(R.string.extensions) else name
activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).visibility = activity.findViewById<FrameLayout>(R.id.fragmentExtensionsContainer).isGone = show
if (show) View.GONE else View.VISIBLE
} }
var itemSelected = false var itemSelected = false
val allSettings = pkg.sources.filterIsInstance<ConfigurableSource>() val allSettings = pkg.sources.filterIsInstance<ConfigurableSource>()
@ -290,13 +290,14 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
MangaSources.performReorderMangaSources() MangaSources.performReorderMangaSources()
} }
@SuppressLint("SetTextI18n", "ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = getItem(position) // Use getItem() from ListAdapter val extension = getItem(position) // Use getItem() from ListAdapter
val nsfw = if (extension.isNsfw) "(18+)" else "" val nsfw = if (extension.isNsfw) "(18+)" else ""
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" val versionText = "$lang ${extension.versionName} $nsfw"
holder.extensionVersionTextView.text = versionText
if (!skipIcons) { if (!skipIcons) {
holder.extensionIconImageView.setImageDrawable(extension.icon) holder.extensionIconImageView.setImageDrawable(extension.icon)
} }

View file

@ -221,13 +221,13 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler {
return ViewHolder(view) return ViewHolder(view)
} }
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = getItem(position) // Use getItem() from ListAdapter val extension = getItem(position) // Use getItem() from ListAdapter
val nsfw = "" val nsfw = ""
val lang = LanguageMapper.mapLanguageCodeToName("all") val lang = LanguageMapper.mapLanguageCodeToName("all")
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" val versionText = "$lang ${extension.versionName} $nsfw"
holder.extensionVersionTextView.text = versionText
if (!skipIcons) { if (!skipIcons) {
holder.extensionIconImageView.setImageDrawable(extension.icon) holder.extensionIconImageView.setImageDrawable(extension.icon)
} }

View file

@ -127,6 +127,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
binding.playerSettingsTimeStamps.isChecked = PrefManager.getVal(PrefName.TimeStampsEnabled) binding.playerSettingsTimeStamps.isChecked = PrefManager.getVal(PrefName.TimeStampsEnabled)
binding.playerSettingsTimeStamps.setOnCheckedChangeListener { _, isChecked -> binding.playerSettingsTimeStamps.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.TimeStampsEnabled, isChecked) PrefManager.setVal(PrefName.TimeStampsEnabled, isChecked)
binding.playerSettingsAutoSkipOpEd.isEnabled = isChecked
} }
binding.playerSettingsTimeStampsAutoHide.isChecked = PrefManager.getVal(PrefName.AutoHideTimeStamps) binding.playerSettingsTimeStampsAutoHide.isChecked = PrefManager.getVal(PrefName.AutoHideTimeStamps)
@ -148,6 +149,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
// Auto // Auto
binding.playerSettingsAutoSkipOpEd.isChecked = PrefManager.getVal(PrefName.AutoSkipOPED) binding.playerSettingsAutoSkipOpEd.isChecked = PrefManager.getVal(PrefName.AutoSkipOPED)
binding.playerSettingsAutoSkipOpEd.isEnabled = binding.playerSettingsTimeStamps.isChecked
binding.playerSettingsAutoSkipOpEd.setOnCheckedChangeListener { _, isChecked -> binding.playerSettingsAutoSkipOpEd.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.AutoSkipOPED, isChecked) PrefManager.setVal(PrefName.AutoSkipOPED, isChecked)
} }
@ -172,7 +174,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
binding.playerSettingsAskChapterZero.isChecked = binding.playerSettingsAskChapterZero.isChecked =
PrefManager.getVal(PrefName.ChapterZeroPlayer) PrefManager.getVal(PrefName.ChapterZeroPlayer)
binding.playerSettingsAskChapterZero.isEnabled = binding.playerSettingsAskChapterZero.isEnabled =
!PrefManager.getVal<Boolean>(PrefName.AskIndividualPlayer) !binding.playerSettingsAskUpdateProgress.isChecked
binding.playerSettingsAskChapterZero.setOnCheckedChangeListener { _, isChecked -> binding.playerSettingsAskChapterZero.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.ChapterZeroPlayer, isChecked) PrefManager.setVal(PrefName.ChapterZeroPlayer, isChecked)
} }
@ -411,7 +413,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
"Magenta" "Magenta"
) )
val subBackgroundDialog = AlertDialog.Builder(this, R.style.MyPopup) 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 { binding.videoSubColorBackground.setOnClickListener {
val dialog = subBackgroundDialog.setSingleChoiceItems( val dialog = subBackgroundDialog.setSingleChoiceItems(
colorsSubBackground, colorsSubBackground,
@ -438,7 +440,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
"Magenta" "Magenta"
) )
val subWindowDialog = AlertDialog.Builder(this, R.style.MyPopup) 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 { binding.videoSubColorWindow.setOnClickListener {
val dialog = subWindowDialog.setSingleChoiceItems( val dialog = subWindowDialog.setSingleChoiceItems(
colorsSubWindow, colorsSubWindow,

File diff suppressed because it is too large Load diff

View file

@ -9,9 +9,9 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.MainActivity import ani.dantotsu.MainActivity
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.databinding.BottomSheetSettingsBinding import ani.dantotsu.databinding.BottomSheetSettingsBinding
@ -24,13 +24,15 @@ import ani.dantotsu.home.MangaFragment
import ani.dantotsu.home.NoInternet import ani.dantotsu.home.NoInternet
import ani.dantotsu.incognitoNotification import ani.dantotsu.incognitoNotification
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.profile.activity.NotificationActivity
import ani.dantotsu.offline.OfflineFragment import ani.dantotsu.offline.OfflineFragment
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.activity.FeedActivity import ani.dantotsu.profile.activity.FeedActivity
import ani.dantotsu.profile.activity.NotificationActivity
import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.startMainActivity import ani.dantotsu.startMainActivity
import eu.kanade.tachiyomi.util.system.getSerializableCompat
import java.util.Timer import java.util.Timer
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
@ -41,7 +43,7 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
private lateinit var pageType: PageType private lateinit var pageType: PageType
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
pageType = arguments?.getSerializable("pageType") as? PageType ?: PageType.HOME pageType = arguments?.getSerializableCompat("pageType") as? PageType ?: PageType.HOME
} }
override fun onCreateView( override fun onCreateView(
@ -94,7 +96,7 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
Anilist.loginIntent(requireActivity()) 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.settingsNotificationCount.text = Anilist.unreadNotificationCount.toString()
binding.settingsUserAvatar.setOnClickListener{ binding.settingsUserAvatar.setOnClickListener{
ContextCompat.startActivity( ContextCompat.startActivity(

View file

@ -202,12 +202,12 @@ class AnimeExtensionAdapter(private val clickListener: OnAnimeInstallClickListen
val extensionIconImageView: ImageView = binding.extensionIconImageView val extensionIconImageView: ImageView = binding.extensionIconImageView
@SuppressLint("SetTextI18n")
fun bind(extension: AnimeExtension.Available) { fun bind(extension: AnimeExtension.Available) {
val nsfw = if (extension.isNsfw) "(18+)" else "" val nsfw = if (extension.isNsfw) "(18+)" else ""
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
binding.extensionNameTextView.text = extension.name binding.extensionNameTextView.text = extension.name
binding.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" val versionText = "$lang ${extension.versionName} $nsfw"
binding.extensionVersionTextView.text = versionText
} }
fun clear() { fun clear() {

View file

@ -199,12 +199,12 @@ class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListen
val extensionIconImageView: ImageView = binding.extensionIconImageView val extensionIconImageView: ImageView = binding.extensionIconImageView
@SuppressLint("SetTextI18n")
fun bind(extension: MangaExtension.Available) { fun bind(extension: MangaExtension.Available) {
val nsfw = if (extension.isNsfw) "(18+)" else "" val nsfw = if (extension.isNsfw) "(18+)" else ""
val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) val lang = LanguageMapper.mapLanguageCodeToName(extension.lang)
binding.extensionNameTextView.text = extension.name binding.extensionNameTextView.text = extension.name
binding.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" val versionText = "$lang ${extension.versionName} $nsfw"
binding.extensionVersionTextView.text = versionText
} }
fun clear() { fun clear() {

View file

@ -41,13 +41,14 @@ import kotlinx.coroutines.withContext
class NovelExtensionsViewModelFactory( class NovelExtensionsViewModelFactory(
private val novelExtensionManager: NovelExtensionManager private val novelExtensionManager: NovelExtensionManager
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return NovelExtensionsViewModel(novelExtensionManager) as T return NovelExtensionsViewModel(novelExtensionManager) as T
} }
} }
class NovelExtensionsViewModel( class NovelExtensionsViewModel(
private val novelExtensionManager: NovelExtensionManager novelExtensionManager: NovelExtensionManager
) : ViewModel() { ) : ViewModel() {
private val searchQuery = MutableStateFlow("") private val searchQuery = MutableStateFlow("")
private var currentPagingSource: NovelExtensionPagingSource? = null private var currentPagingSource: NovelExtensionPagingSource? = null
@ -102,21 +103,20 @@ class NovelExtensionPagingSource(
} else { } else {
availableExtensions.filter { it.name.contains(query, ignoreCase = true) } availableExtensions.filter { it.name.contains(query, ignoreCase = true) }
} }
val filternfsw = filteredExtensions
/*val filternfsw = if(isNsfwEnabled) { currently not implemented /*val filternfsw = if(isNsfwEnabled) { currently not implemented
filteredExtensions filteredExtensions
} else { } else {
filteredExtensions.filterNot { it.isNsfw } filteredExtensions.filterNot { it.isNsfw }
}*/ }*/
return try { return try {
val sublist = filternfsw.subList( val sublist = filteredExtensions.subList(
fromIndex = position, fromIndex = position,
toIndex = (position + params.loadSize).coerceAtMost(filternfsw.size) toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size)
) )
LoadResult.Page( LoadResult.Page(
data = sublist, data = sublist,
prevKey = if (position == 0) null else position - params.loadSize, prevKey = if (position == 0) null else position - params.loadSize,
nextKey = if (position + params.loadSize >= filternfsw.size) null else position + params.loadSize nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize
) )
} catch (e: Exception) { } catch (e: Exception) {
LoadResult.Error(e) LoadResult.Error(e)

View file

@ -6,7 +6,7 @@ import ani.dantotsu.util.ColorEditor.Companion.toHexColor
class AniMarkdown { //istg anilist has the worst api class AniMarkdown { //istg anilist has the worst api
companion object { companion object {
private fun convertNestedImageToHtml(markdown: String): String { private fun convertNestedImageToHtml(markdown: String): String {
val regex = """\[\!\[(.*?)\]\((.*?)\)\]\((.*?)\)""".toRegex() val regex = """\[!\[(.*?)]\((.*?)\)]\((.*?)\)""".toRegex()
return regex.replace(markdown) { matchResult -> return regex.replace(markdown) { matchResult ->
val altText = matchResult.groupValues[1] val altText = matchResult.groupValues[1]
val imageUrl = matchResult.groupValues[2] val imageUrl = matchResult.groupValues[2]
@ -16,7 +16,7 @@ class AniMarkdown { //istg anilist has the worst api
} }
private fun convertImageToHtml(markdown: String): String { private fun convertImageToHtml(markdown: String): String {
val regex = """\!\[(.*?)\]\((.*?)\)""".toRegex() val regex = """!\[(.*?)]\((.*?)\)""".toRegex()
return regex.replace(markdown) { matchResult -> return regex.replace(markdown) { matchResult ->
val altText = matchResult.groupValues[1] val altText = matchResult.groupValues[1]
val imageUrl = matchResult.groupValues[2] val imageUrl = matchResult.groupValues[2]
@ -25,7 +25,7 @@ class AniMarkdown { //istg anilist has the worst api
} }
private fun convertLinkToHtml(markdown: String): String { private fun convertLinkToHtml(markdown: String): String {
val regex = """\[(.*?)\]\((.*?)\)""".toRegex() val regex = """\[(.*?)]\((.*?)\)""".toRegex()
return regex.replace(markdown) { matchResult -> return regex.replace(markdown) { matchResult ->
val linkText = matchResult.groupValues[1] val linkText = matchResult.groupValues[1]
val linkUrl = matchResult.groupValues[2] val linkUrl = matchResult.groupValues[2]
@ -50,7 +50,7 @@ class AniMarkdown { //istg anilist has the worst api
private fun underlineToHtml(html: String): String { private fun underlineToHtml(html: String): String {
return html.replace("(?s)___(.*?)___".toRegex(), "<br><em><strong>$1</strong></em><br>") return html.replace("(?s)___(.*?)___".toRegex(), "<br><em><strong>$1</strong></em><br>")
.replace("(?s)__(.*?)__".toRegex(), "<br><strong>$1</strong><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 { fun getBasicAniHTML(html: String): String {

View file

@ -1,7 +1,6 @@
package ani.dantotsu.widgets package ani.dantotsu.widgets
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.widget.RemoteViews import android.widget.RemoteViews
@ -12,7 +11,7 @@ import java.io.InputStream
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
class CurrentlyAiringRemoteViewsFactory(private val context: Context, intent: Intent) : class CurrentlyAiringRemoteViewsFactory(private val context: Context) :
RemoteViewsService.RemoteViewsFactory { RemoteViewsService.RemoteViewsFactory {
private var widgetItems = mutableListOf<WidgetItem>() private var widgetItems = mutableListOf<WidgetItem>()

View file

@ -7,6 +7,6 @@ import ani.dantotsu.util.Logger
class CurrentlyAiringRemoteViewsService : RemoteViewsService() { class CurrentlyAiringRemoteViewsService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
Logger.log("CurrentlyAiringRemoteViewsFactory onGetViewFactory") Logger.log("CurrentlyAiringRemoteViewsFactory onGetViewFactory")
return CurrentlyAiringRemoteViewsFactory(applicationContext, intent) return CurrentlyAiringRemoteViewsFactory(applicationContext)
} }
} }

View file

@ -20,7 +20,7 @@ class ExtensionInstallerPreference(
val entries val entries
get() = ExtensionInstaller.values().run { get() = ExtensionInstaller.entries.toTypedArray().run {
if (context.hasMiuiPackageInstaller) { if (context.hasMiuiPackageInstaller) {
filter { it != ExtensionInstaller.PACKAGEINSTALLER } filter { it != ExtensionInstaller.PACKAGEINSTALLER }
} else { } else {

View file

@ -58,8 +58,7 @@ interface AnimeSource {
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
suspend fun getVideoList(episode: SEpisode): List<Video> { suspend fun getVideoList(episode: SEpisode): List<Video> {
val list = fetchVideoList(episode).awaitSingle() return fetchVideoList(episode).awaitSingle()
return list
} }
@Deprecated( @Deprecated(

View file

@ -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.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallReceiver import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.util.preference.plusAssign import eu.kanade.tachiyomi.util.preference.plusAssign
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -51,7 +52,7 @@ class AnimeExtensionManager(
/** /**
* The installer which installs, updates and uninstalls the anime extensions. * 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>() private val iconMap = mutableMapOf<String, Drawable>()
@ -92,14 +93,14 @@ class AnimeExtensionManager(
init { init {
initAnimeExtensions() initAnimeExtensions()
AnimeExtensionInstallReceiver(AnimeInstallationListener()).register(context) ExtensionInstallReceiver().setAnimeListener(InstallationListener()).register(context)
} }
/** /**
* Loads and registers the installed animeextensions. * Loads and registers the installed animeextensions.
*/ */
private fun initAnimeExtensions() { private fun initAnimeExtensions() {
val animeextensions = AnimeExtensionLoader.loadExtensions(context) val animeextensions = ExtensionLoader.loadAnimeExtensions(context)
_installedAnimeExtensionsFlow.value = animeextensions _installedAnimeExtensionsFlow.value = animeextensions
.filterIsInstance<AnimeLoadResult.Success>() .filterIsInstance<AnimeLoadResult.Success>()
@ -254,12 +255,13 @@ class AnimeExtensionManager(
* *
* @param signature The signature to whitelist. * @param signature The signature to whitelist.
*/ */
@OptIn(DelicateCoroutinesApi::class)
fun trustSignature(signature: String) { fun trustSignature(signature: String) {
val untrustedSignatures = val untrustedSignatures =
_untrustedAnimeExtensionsFlow.value.map { it.signatureHash }.toSet() _untrustedAnimeExtensionsFlow.value.map { it.signatureHash }.toSet()
if (signature !in untrustedSignatures) return if (signature !in untrustedSignatures) return
AnimeExtensionLoader.trustedSignatures += signature ExtensionLoader.trustedSignaturesAnime += signature
preferences.trustedSignatures() += signature preferences.trustedSignatures() += signature
val nowTrustedAnimeExtensions = val nowTrustedAnimeExtensions =
@ -271,7 +273,7 @@ class AnimeExtensionManager(
nowTrustedAnimeExtensions nowTrustedAnimeExtensions
.map { animeextension -> .map { animeextension ->
async { async {
AnimeExtensionLoader.loadExtensionFromPkgName( ExtensionLoader.loadAnimeExtensionFromPkgName(
ctx, ctx,
animeextension.pkgName animeextension.pkgName
) )
@ -333,7 +335,7 @@ class AnimeExtensionManager(
/** /**
* Listener which receives events of the anime extensions being installed, updated or removed. * 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) { override fun onExtensionInstalled(extension: AnimeExtension.Installed) {
registerNewExtension(extension.withUpdateCheck()) registerNewExtension(extension.withUpdateCheck())

View file

@ -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.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources 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.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
@ -87,7 +87,7 @@ internal class AnimeExtensionGithubApi {
findExtensions().also { lastExtCheck.set(Date().time) } findExtensions().also { lastExtCheck.set(Date().time) }
} }
val installedExtensions = AnimeExtensionLoader.loadExtensions(context) val installedExtensions = ExtensionLoader.loadAnimeExtensions(context)
.filterIsInstance<AnimeLoadResult.Success>() .filterIsInstance<AnimeLoadResult.Success>()
.map { it.extension } .map { it.extension }
@ -115,7 +115,7 @@ internal class AnimeExtensionGithubApi {
return this return this
.filter { .filter {
val libVersion = it.extractLibVersion() 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 { .map {
AnimeExtension.Available( AnimeExtension.Available(

View file

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

View file

@ -3,5 +3,5 @@ package eu.kanade.tachiyomi.extension.anime.model
sealed class AnimeLoadResult { sealed class AnimeLoadResult {
class Success(val extension: AnimeExtension.Installed) : AnimeLoadResult() class Success(val extension: AnimeExtension.Installed) : AnimeLoadResult()
class Untrusted(val extension: AnimeExtension.Untrusted) : AnimeLoadResult() class Untrusted(val extension: AnimeExtension.Untrusted) : AnimeLoadResult()
object Error : AnimeLoadResult() data object Error : AnimeLoadResult()
} }

View file

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

View file

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

View file

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