* feat: (wip) torrent

credit to kuukiyomi

* fix: extensions -> addons

* fix: unified loader

* feat: (wip) modularity

* fix: addon ui

* feat: addon install/uninstall

---------

Co-authored-by: aayush262 <aayushthakur262006@gmail.com>
This commit is contained in:
rebel onion 2024-04-19 04:08:20 -05:00 committed by GitHub
parent 3d1040b280
commit 670d16bd8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 1923 additions and 427 deletions

View file

@ -80,48 +80,14 @@ class AnimeExtensionsFragment : Fragment(),
if (isAdded) {
val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val installerSteps = InstallerSteps(notificationManager, context)
// Start the installation process
animeExtensionManager.installExtension(pkg)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ installStep ->
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_round_sync_24)
.setContentTitle(getString(R.string.installing_extension))
.setContentText(getString(R.string.install_step, installStep))
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
},
{ error ->
Injekt.get<CrashlyticsInterface>().logException(error)
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_ERROR
)
.setSmallIcon(R.drawable.ic_round_info_24)
.setContentTitle(getString(R.string.installation_failed, error.message))
.setContentText(getString(R.string.error_message, error.message))
.setPriority(NotificationCompat.PRIORITY_HIGH)
notificationManager.notify(1, builder.build())
snackString(getString(R.string.installation_failed, error.message))
},
{
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_download_24)
.setContentTitle(getString(R.string.installation_complete))
.setContentText(getString(R.string.extension_has_been_installed))
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
viewModel.invalidatePager()
snackString(getString(R.string.extension_installed))
}
{ installStep -> installerSteps.onInstallStep(installStep) {} },
{ error -> installerSteps.onError(error) {} },
{ installerSteps.onComplete { viewModel.invalidatePager() } }
)
}
}

View file

@ -1,7 +1,6 @@
package ani.dantotsu.settings
import android.app.AlertDialog
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@ -31,7 +30,6 @@ import ani.dantotsu.others.AndroidBug5497Workaround
import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import com.google.android.material.tabs.TabLayout

View file

@ -0,0 +1,54 @@
package ani.dantotsu.settings
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationCompat
import ani.dantotsu.R
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.snackString
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.extension.InstallStep
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class InstallerSteps(private val notificationManager: NotificationManager, private val context: Context) {
fun onInstallStep(installStep: InstallStep, extra: () -> Unit) {
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_round_sync_24)
.setContentTitle(context.getString(R.string.installing_extension))
.setContentText(context.getString(R.string.install_step, installStep))
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
}
fun onError(error: Throwable, extra: () -> Unit) {
Injekt.get<CrashlyticsInterface>().logException(error)
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_ERROR
)
.setSmallIcon(R.drawable.ic_round_info_24)
.setContentTitle(context.getString(R.string.installation_failed, error.message))
.setContentText(context.getString(R.string.error_message, error.message))
.setPriority(NotificationCompat.PRIORITY_HIGH)
notificationManager.notify(1, builder.build())
snackString(context.getString(R.string.installation_failed, error.message))
}
fun onComplete(extra: () -> Unit) {
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_download_24)
.setContentTitle(context.getString(R.string.installation_complete))
.setContentText(context.getString(R.string.extension_has_been_installed))
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
snackString(context.getString(R.string.extension_installed))
}
}

View file

@ -81,48 +81,15 @@ class MangaExtensionsFragment : Fragment(),
val context = requireContext()
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val installerSteps = InstallerSteps(notificationManager, context)
// Start the installation process
mangaExtensionManager.installExtension(pkg)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ installStep ->
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_round_sync_24)
.setContentTitle(getString(R.string.installing_extension))
.setContentText(getString(R.string.install_step, installStep))
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
},
{ error ->
Injekt.get<CrashlyticsInterface>().logException(error)
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_ERROR
)
.setSmallIcon(R.drawable.ic_round_info_24)
.setContentTitle(getString(R.string.installation_failed, error.message))
.setContentText(getString(R.string.error_message, error.message))
.setPriority(NotificationCompat.PRIORITY_HIGH)
notificationManager.notify(1, builder.build())
snackString(getString(R.string.installation_failed, error.message))
},
{
val builder = NotificationCompat.Builder(
context,
Notifications.CHANNEL_DOWNLOADER_PROGRESS
)
.setSmallIcon(R.drawable.ic_download_24)
.setContentTitle(getString(R.string.installation_complete))
.setContentText(getString(R.string.extension_has_been_installed))
.setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build())
viewModel.invalidatePager()
snackString(getString(R.string.extension_installed))
}
{ installStep -> installerSteps.onInstallStep(installStep) {} },
{ error -> installerSteps.onError(error) {} },
{ installerSteps.onComplete { viewModel.invalidatePager() } }
)
}
}

View file

@ -1,6 +1,5 @@
package ani.dantotsu.settings
import android.view.ViewGroup
import ani.dantotsu.databinding.ItemSettingsBinding
import ani.dantotsu.databinding.ItemSettingsSwitchBinding
@ -13,6 +12,7 @@ data class Settings(
val onLongClick: (() -> Unit)? = null,
val switch: ((isChecked:Boolean , view: ItemSettingsSwitchBinding ) -> Unit)? = null,
val attach:((ItemSettingsBinding) -> Unit)? = null,
val attachToSwitch : ((ItemSettingsSwitchBinding) -> Unit)? = null,
val isVisible: Boolean = true,
val isActivity: Boolean = false,
var isChecked : Boolean = false,

View file

@ -2,6 +2,7 @@ package ani.dantotsu.settings
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
@ -82,11 +83,13 @@ class SettingsAboutActivity : AppCompatActivity() {
PrefManager.setVal(PrefName.LogToFile, isChecked)
restartApp()
},
attach = {
it.settingsDesc.setOnLongClickListener {
attachToSwitch = {
it.settingsExtraIcon.visibility = View.VISIBLE
it.settingsExtraIcon.setImageResource(R.drawable.ic_round_share_24)
it.settingsExtraIcon.setOnClickListener {
Logger.shareLog(context)
true
}
}
),
Settings(

View file

@ -144,6 +144,16 @@ class SettingsActivity : AppCompatActivity() {
},
isActivity = true
),
Settings(
type = 1,
name = getString(R.string.addons),
desc = getString(R.string.addons_desc),
icon = R.drawable.ic_round_restaurant_24,
onClick = {
startActivity(Intent(context, SettingsAddonActivity::class.java))
},
isActivity = true
),
Settings(
type = 1,
name = getString(R.string.notifications),

View file

@ -87,6 +87,7 @@ class SettingsAdapter(private val settings: ArrayList<Settings>) :
true
}
b.settingsLayout.visibility = if (settings.isVisible) View.VISIBLE else View.GONE
settings.attachToSwitch?.invoke(b)
}
}
}

View file

@ -0,0 +1,259 @@
package ani.dantotsu.settings
import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.addons.AddonDownloader
import ani.dantotsu.addons.AddonListener
import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.addons.torrent.ServerService
import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.databinding.ActivitySettingsAddonsBinding
import ani.dantotsu.databinding.ItemSettingsBinding
import ani.dantotsu.initActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.toast
import ani.dantotsu.util.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import tachiyomi.core.util.lang.launchIO
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SettingsAddonActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsAddonsBinding
private val downloadAddonManager: DownloadAddonManager = Injekt.get()
private val torrentAddonManager: TorrentAddonManager = Injekt.get()
@OptIn(DelicateCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme()
initActivity(this)
val context = this
binding = ActivitySettingsAddonsBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.apply {
settingsAddonsLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
bottomMargin = navBarHeight
}
binding.addonSettingsBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
binding.settingsRecyclerView.adapter = SettingsAdapter(
arrayListOf(
Settings(
type = 1,
name = getString(R.string.anime_downloader_addon),
desc = getString(R.string.not_installed),
icon = R.drawable.anim_play_to_pause,
isActivity = true,
attach = {
setStatus(
view = it,
context = context,
status = downloadAddonManager.hadError(context),
hasUpdate = downloadAddonManager.hasUpdate
)
var job = Job()
downloadAddonManager.addListenerAction { _ ->
job.cancel()
it.settingsIconRight.animate().cancel()
it.settingsIconRight.rotation = 0f
setStatus(
view = it,
context = context,
status = downloadAddonManager.hadError(context),
hasUpdate = false
)
}
it.settingsIconRight.setOnClickListener { _ ->
if (it.settingsDesc.text == getString(R.string.installed)) {
downloadAddonManager.uninstall()
return@setOnClickListener //uninstall logic here
} else {
job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
it.settingsIconRight.setImageResource(R.drawable.ic_sync)
scope.launch {
while (isActive) {
withContext(Dispatchers.Main) {
it.settingsIconRight.animate()
.rotationBy(360f)
.setDuration(1000)
.setInterpolator(LinearInterpolator())
.start()
}
delay(1000)
}
}
snackString(getString(R.string.downloading))
lifecycleScope.launchIO {
AddonDownloader.update(
activity = context,
downloadAddonManager,
repo = DownloadAddonManager.REPO,
currentVersion = downloadAddonManager.getVersion() ?: ""
)
}
}
}
},
), Settings(
type = 1,
name = getString(R.string.torrent_addon),
desc = getString(R.string.not_installed),
icon = R.drawable.anim_play_to_pause,
isActivity = true,
attach = {
setStatus(
view = it,
context = context,
status = torrentAddonManager.hadError(context),
hasUpdate = torrentAddonManager.hasUpdate
)
var job = Job()
torrentAddonManager.addListenerAction { _ ->
job.cancel()
it.settingsIconRight.animate().cancel()
it.settingsIconRight.rotation = 0f
setStatus(
view = it,
context = context,
status = torrentAddonManager.hadError(context),
hasUpdate = false
)
}
it.settingsIconRight.setOnClickListener { _ ->
if (it.settingsDesc.text == getString(R.string.installed)) {
ServerService.stop()
torrentAddonManager.uninstall()
return@setOnClickListener
} else {
job = Job()
val scope = CoroutineScope(Dispatchers.Main + job)
it.settingsIconRight.setImageResource(R.drawable.ic_sync)
scope.launch {
while (isActive) {
withContext(Dispatchers.Main) {
it.settingsIconRight.animate()
.rotationBy(360f)
.setDuration(1000)
.setInterpolator(LinearInterpolator())
.start()
}
delay(1000)
}
}
snackString(getString(R.string.downloading))
lifecycleScope.launchIO {
AddonDownloader.update(
activity = context,
torrentAddonManager,
repo = TorrentAddonManager.REPO,
currentVersion = torrentAddonManager.getVersion() ?: "",
)
}
}
}
},
),
Settings(
type = 2,
name = getString(R.string.enable_torrent),
desc = getString(R.string.enable_torrent),
icon = R.drawable.ic_round_dns_24,
isChecked = PrefManager.getVal(PrefName.TorrentEnabled),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.TorrentEnabled, isChecked)
Injekt.get<TorrentAddonManager>().extension?.let {
if (isChecked) {
lifecycleScope.launchIO {
if (!ServerService.isRunning()) {
ServerService.start()
}
}
} else {
lifecycleScope.launchIO {
if (ServerService.isRunning()) {
ServerService.stop()
}
}
}
}
}
)
)
)
binding.settingsRecyclerView.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}
}
override fun onDestroy() {
super.onDestroy()
torrentAddonManager.removeListenerAction()
downloadAddonManager.removeListenerAction()
}
private fun setStatus(
view: ItemSettingsBinding,
context: Context,
status: String?,
hasUpdate: Boolean
) {
try {
when (status) {
context.getString(R.string.loaded_successfully) -> {
view.settingsIconRight.setImageResource(R.drawable.ic_round_delete_24)
view.settingsIconRight.rotation = 0f
view.settingsDesc.text = context.getString(R.string.installed)
}
null -> {
view.settingsIconRight.setImageResource(R.drawable.ic_download_24)
view.settingsIconRight.rotation = 0f
view.settingsDesc.text = context.getString(R.string.not_installed)
}
else -> {
view.settingsIconRight.setImageResource(R.drawable.ic_round_new_releases_24)
view.settingsIconRight.rotation = 0f
view.settingsDesc.text = context.getString(R.string.error_msg, status)
}
}
if (hasUpdate) {
view.settingsIconRight.setImageResource(R.drawable.ic_round_sync_24)
view.settingsDesc.text = context.getString(R.string.update_addon)
}
} catch (e: Exception) {
Logger.log(e)
}
}
}

View file

@ -13,22 +13,17 @@ import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.api.NotificationType
import ani.dantotsu.databinding.ActivitySettingsNotificationsBinding
import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.initActivity
import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.TaskScheduler
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
import ani.dantotsu.notifications.comment.CommentNotificationWorker
import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker
import ani.dantotsu.openSettings
import ani.dantotsu.restartApp
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SettingsNotificationActivity: AppCompatActivity(){
private lateinit var binding: ActivitySettingsNotificationsBinding

View file

@ -120,6 +120,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
UseInternalCast(Pref(Location.Player, Boolean::class, false)),
Pip(Pref(Location.Player, Boolean::class, true)),
RotationPlayer(Pref(Location.Player, Boolean::class, true)),
TorrentEnabled(Pref(Location.Player, Boolean::class, false)),
//Reader
ShowSource(Pref(Location.Reader, Boolean::class, true)),