Compare commits

..

No commits in common. "a93b4f5b1102f781fe2b1b1e51adacf2d83fdb71" and "f606bef2a5199d0af8ae0d26b6299defe8946091" have entirely different histories.

19 changed files with 1324 additions and 1553 deletions

View file

@ -14,7 +14,7 @@ Dantotsu is an [Anilist](https://anilist.co/) only client.
> **Dantotsu (断トツ; Dan-totsu)** literally means "the best of the best" in Japanese. Try it out for yourself and be the judge! > **Dantotsu (断トツ; Dan-totsu)** literally means "the best of the best" in Japanese. Try it out for yourself and be the judge!
<a href="https://www.buymeacoffee.com/rebelonion"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=rebelonion&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff" /></a> <a href="https://www.buymeacoffee.com/rebelonion"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=rebelonion&button_colour=FFDD00&font_colour=030201&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff" /></a>
## Terms of Use ## Terms of Use
By downloading, installing, or using this application, you agree to: By downloading, installing, or using this application, you agree to:

View file

@ -17,8 +17,9 @@ android {
applicationId "ani.dantotsu" applicationId "ani.dantotsu"
minSdk 21 minSdk 21
targetSdk 35 targetSdk 35
versionName "3.2.2" versionCode((System.currentTimeMillis() / 60000).toInteger())
versionCode 300200200 versionName "3.2.0"
versionCode 300200000
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> `<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">

View file

@ -253,7 +253,7 @@ data class MediaStreamingEpisode(
// The site location of the streaming episode // The site location of the streaming episode
@SerialName("site") var site: String?, @SerialName("site") var site: String?,
) : java.io.Serializable )
@Serializable @Serializable
data class MediaCoverImage( data class MediaCoverImage(

View file

@ -50,9 +50,8 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
val assetApi = RPCExternalAsset(data.applicationId, token!!, client, json) val assetApi = RPCExternalAsset(data.applicationId, token!!, client, json)
suspend fun String.discordUrl() = assetApi.getDiscordUri(this) suspend fun String.discordUrl() = assetApi.getDiscordUri(this)
return json.encodeToString( return json.encodeToString(Presence.Response(
Presence.Response( 3,
3,
Presence( Presence(
activities = listOf( activities = listOf(
Activity( Activity(

View file

@ -232,18 +232,12 @@ class MangaDownloaderService : Service() {
image.page, image.page,
image.source image.source
) )
if (bitmap == null) {
snackString("${task.chapter} - Retrying to download page ${index.ofLength(3)}, attempt ${retryCount + 1}.")
}
retryCount++ retryCount++
} }
if (bitmap == null) { if (bitmap != null) {
outputDir.deleteRecursively(this@MangaDownloaderService, false) saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap)
throw Exception("${task.chapter} - Unable to download all pages after $retryCount attempts. Try again.")
} }
saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap)
farthest++ farthest++
builder.setProgress(task.imageData.size, farthest, false) builder.setProgress(task.imageData.size, farthest, false)

File diff suppressed because it is too large Load diff

View file

@ -7,11 +7,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.EditText
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getString import androidx.core.content.ContextCompat.getString
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
@ -267,22 +265,19 @@ class MangaReadAdapter(
} }
// Multi download // Multi download
//downloadNo.text = "0" downloadNo.text = "0"
mediaDownloadTop.setOnClickListener { mediaDownloadTop.setOnClickListener {
// Alert dialog asking for the number of chapters to download
fragment.requireContext().customAlertDialog().apply { fragment.requireContext().customAlertDialog().apply {
setTitle("Multi Chapter Downloader") setTitle("Multi Chapter Downloader")
setMessage("Enter the number of chapters to download") setMessage("Enter the number of chapters to download")
val input = View.inflate(currContext(), R.layout.dialog_layout, null) val input = NumberPicker(currContext())
val editText = input.findViewById<EditText>(R.id.downloadNo) input.minValue = 1
input.maxValue = 20
input.value = 1
setCustomView(input) setCustomView(input)
setPosButton(R.string.ok) { setPosButton(R.string.ok) {
val value = editText.text.toString().toIntOrNull() downloadNo.text = "${input.value}"
if (value != null && value > 0) {
downloadNo.setText(value.toString(), TextView.BufferType.EDITABLE)
fragment.multiDownload(value)
} else {
toast("Please enter a valid number")
}
} }
setNegButton(R.string.cancel) setNegButton(R.string.cancel)
show() show()
@ -387,9 +382,8 @@ class MangaReadAdapter(
setCustomView(root) setCustomView(root)
setPosButton("OK") { setPosButton("OK") {
if (run) fragment.onIconPressed(style, reversed) if (run) fragment.onIconPressed(style, reversed)
val value = downloadNo.text.toString().toIntOrNull() if (downloadNo.text != "0") {
if (value != null && value > 0) { fragment.multiDownload(downloadNo.text.toString().toInt())
fragment.multiDownload(value)
} }
if (refresh) fragment.loadChapters(source, true) if (refresh) fragment.loadChapters(source, true)
} }

View file

@ -474,7 +474,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
scanlator = chapter.scanlator ?: "Unknown", scanlator = chapter.scanlator ?: "Unknown",
imageData = images, imageData = images,
sourceMedia = media, sourceMedia = media,
retries = 25, retries = 2,
simultaneousDownloads = 2 simultaneousDownloads = 2
) )

View file

@ -24,11 +24,11 @@ class CrashActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
initActivity(this) initActivity(this)
binding = ActivityCrashBinding.inflate(layoutInflater)
window.setFlags( window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE WindowManager.LayoutParams.FLAG_SECURE
) )
binding = ActivityCrashBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight topMargin = statusBarHeight

View file

@ -19,7 +19,6 @@ 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
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.util.customAlertDialog
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -58,16 +57,23 @@ class SettingsAnimeActivity : AppCompatActivity() {
desc = getString(R.string.purge_anime_downloads_desc), desc = getString(R.string.purge_anime_downloads_desc),
icon = R.drawable.ic_round_delete_24, icon = R.drawable.ic_round_delete_24,
onClick = { onClick = {
context.customAlertDialog().apply { val dialog = AlertDialog.Builder(context, R.style.MyPopup)
setTitle(R.string.purge_anime_downloads) .setTitle(R.string.purge_anime_downloads)
setMessage(R.string.purge_confirm, getString(R.string.anime)) .setMessage(
setPosButton(R.string.yes, onClick = { getString(
R.string.purge_confirm,
getString(R.string.anime)
)
)
.setPositiveButton(R.string.yes) { dialog, _ ->
val downloadsManager = Injekt.get<DownloadsManager>() val downloadsManager = Injekt.get<DownloadsManager>()
downloadsManager.purgeDownloads(MediaType.ANIME) downloadsManager.purgeDownloads(MediaType.ANIME)
}) dialog.dismiss()
setNegButton(R.string.no) }.setNegativeButton(R.string.no) { dialog, _ ->
show() dialog.dismiss()
} }.create()
dialog.window?.setDimAmount(0.8f)
dialog.show()
} }
), ),
@ -137,4 +143,4 @@ class SettingsAnimeActivity : AppCompatActivity() {
} }
} }
} }

View file

@ -45,6 +45,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.UUID import java.util.UUID
class SettingsCommonActivity : AppCompatActivity() { class SettingsCommonActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsCommonBinding private lateinit var binding: ActivitySettingsCommonBinding
private lateinit var launcher: LauncherWrapper private lateinit var launcher: LauncherWrapper
@ -61,27 +62,23 @@ class SettingsCommonActivity : AppCompatActivity() {
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri != null) { if (uri != null) {
try { try {
val jsonString = val jsonString = contentResolver.openInputStream(uri)?.readBytes()
contentResolver.openInputStream(uri)?.readBytes() ?: throw Exception("Error reading file")
?: throw Exception("Error reading file")
val name = DocumentFile.fromSingleUri(this, uri)?.name ?: "settings" val name = DocumentFile.fromSingleUri(this, uri)?.name ?: "settings"
// .sani is encrypted, .ani is not //.sani is encrypted, .ani is not
if (name.endsWith(".sani")) { if (name.endsWith(".sani")) {
passwordAlertDialog(false) { password -> passwordAlertDialog(false) { password ->
if (password != null) { if (password != null) {
val salt = jsonString.copyOfRange(0, 16) val salt = jsonString.copyOfRange(0, 16)
val encrypted = jsonString.copyOfRange(16, jsonString.size) val encrypted = jsonString.copyOfRange(16, jsonString.size)
val decryptedJson = val decryptedJson = try {
try { PreferenceKeystore.decryptWithPassword(
PreferenceKeystore.decryptWithPassword( password, encrypted, salt
password, )
encrypted, } catch (e: Exception) {
salt, toast(getString(R.string.incorrect_password))
) return@passwordAlertDialog
} catch (e: Exception) { }
toast(getString(R.string.incorrect_password))
return@passwordAlertDialog
}
if (PreferencePackager.unpack(decryptedJson)) restartApp() if (PreferencePackager.unpack(decryptedJson)) restartApp()
} else { } else {
toast(getString(R.string.password_cannot_be_empty)) toast(getString(R.string.password_cannot_be_empty))
@ -103,6 +100,7 @@ class SettingsCommonActivity : AppCompatActivity() {
launcher = LauncherWrapper(this, contract) launcher = LauncherWrapper(this, contract)
binding.apply { binding.apply {
settingsCommonLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> { settingsCommonLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight topMargin = statusBarHeight
bottomMargin = navBarHeight bottomMargin = navBarHeight
@ -110,30 +108,27 @@ class SettingsCommonActivity : AppCompatActivity() {
commonSettingsBack.setOnClickListener { commonSettingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
val exDns = val exDns = listOf(
listOf( "None",
"None", "Cloudflare",
"Cloudflare", "Google",
"Google", "AdGuard",
"AdGuard", "Quad9",
"Quad9", "AliDNS",
"AliDNS", "DNSPod",
"DNSPod", "360",
"360", "Quad101",
"Quad101", "Mullvad",
"Mullvad", "Controld",
"Controld", "Njalla",
"Njalla", "Shecan",
"Shecan", "Libre"
"Libre", )
)
settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)]) settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)])
settingsExtensionDns.setAdapter( settingsExtensionDns.setAdapter(
ArrayAdapter( ArrayAdapter(
context, context, R.layout.item_dropdown, exDns
R.layout.item_dropdown, )
exDns,
),
) )
settingsExtensionDns.setOnItemClickListener { _, _, i, _ -> settingsExtensionDns.setOnItemClickListener { _, _, i, _ ->
PrefManager.setVal(PrefName.DohProvider, i) PrefManager.setVal(PrefName.DohProvider, i)
@ -141,287 +136,283 @@ class SettingsCommonActivity : AppCompatActivity() {
restartApp() restartApp()
} }
settingsRecyclerView.adapter = settingsRecyclerView.adapter = SettingsAdapter(
SettingsAdapter( arrayListOf(
arrayListOf( Settings(
Settings( type = 1,
type = 1, name = getString(R.string.ui_settings),
name = getString(R.string.ui_settings), desc = getString(R.string.ui_settings_desc),
desc = getString(R.string.ui_settings_desc), icon = R.drawable.ic_round_auto_awesome_24,
icon = R.drawable.ic_round_auto_awesome_24, onClick = {
onClick = { startActivity(
startActivity( Intent(
Intent( context,
context, UserInterfaceSettingsActivity::class.java
UserInterfaceSettingsActivity::class.java,
),
) )
}, )
isActivity = true, },
), isActivity = true
Settings( ),
type = 1, Settings(
name = getString(R.string.download_manager_select), type = 2,
desc = getString(R.string.download_manager_select_desc), name = getString(R.string.open_animanga_directly),
icon = R.drawable.ic_download_24, desc = getString(R.string.open_animanga_directly_info),
onClick = { icon = R.drawable.ic_round_search_24,
val managers = arrayOf("Default", "1DM", "ADM") isChecked = PrefManager.getVal(PrefName.AniMangaSearchDirect),
customAlertDialog().apply { switch = { isChecked, _ ->
setTitle(getString(R.string.download_manager)) PrefManager.setVal(PrefName.AniMangaSearchDirect, isChecked)
singleChoiceItems( }
managers, ),
PrefManager.getVal(PrefName.DownloadManager), Settings(
) { count -> type = 1,
PrefManager.setVal(PrefName.DownloadManager, count) name = getString(R.string.download_manager_select),
} desc = getString(R.string.download_manager_select_desc),
show() icon = R.drawable.ic_download_24,
onClick = {
val managers = arrayOf("Default", "1DM", "ADM")
customAlertDialog().apply {
setTitle(getString(R.string.download_manager))
singleChoiceItems(
managers,
PrefManager.getVal(PrefName.DownloadManager)
) { count ->
PrefManager.setVal(PrefName.DownloadManager, count)
} }
}, show()
), }
Settings( }
type = 1, ),
name = getString(R.string.app_lock), Settings(
desc = getString(R.string.app_lock_desc), type = 1,
icon = R.drawable.ic_round_lock_open_24, name = getString(R.string.app_lock),
onClick = { desc = getString(R.string.app_lock_desc),
customAlertDialog().apply { icon = R.drawable.ic_round_lock_open_24,
val view = DialogSetPasswordBinding.inflate(layoutInflater) onClick = {
setTitle(R.string.app_lock) customAlertDialog().apply {
setCustomView(view.root) val view = DialogSetPasswordBinding.inflate(layoutInflater)
setPosButton(R.string.ok) { setTitle(R.string.app_lock)
if (view.forgotPasswordCheckbox.isChecked) { setCustomView(view.root)
PrefManager.setVal(PrefName.OverridePassword, true) setPosButton(R.string.ok) {
} if (view.forgotPasswordCheckbox.isChecked) {
val password = view.passwordInput.text.toString() PrefManager.setVal(PrefName.OverridePassword, true)
val confirmPassword = }
view.confirmPasswordInput.text.toString() val password = view.passwordInput.text.toString()
if (password == confirmPassword && password.isNotEmpty()) { val confirmPassword = view.confirmPasswordInput.text.toString()
PrefManager.setVal(PrefName.AppPassword, password) if (password == confirmPassword && password.isNotEmpty()) {
if (view.biometricCheckbox.isChecked) { PrefManager.setVal(PrefName.AppPassword, password)
val canBiometricPrompt = if (view.biometricCheckbox.isChecked) {
BiometricManager val canBiometricPrompt =
.from(applicationContext) BiometricManager.from(applicationContext)
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS
BiometricManager.BIOMETRIC_SUCCESS
if (canBiometricPrompt) { if (canBiometricPrompt) {
val biometricPrompt = val biometricPrompt =
BiometricPromptUtils.createBiometricPrompt( BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ ->
this@SettingsCommonActivity val token = UUID.randomUUID().toString()
) { _ -> PrefManager.setVal(
val token = UUID.randomUUID().toString() PrefName.BiometricToken,
PrefManager.setVal( token
PrefName.BiometricToken, )
token, toast(R.string.success)
) }
toast(R.string.success) val promptInfo =
} BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity)
val promptInfo = biometricPrompt.authenticate(promptInfo)
BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity) }
biometricPrompt.authenticate(promptInfo)
} } else {
PrefManager.setVal(PrefName.BiometricToken, "")
toast(R.string.success)
}
} else {
toast(R.string.password_mismatch)
}
}
setNegButton(R.string.cancel)
setNeutralButton(R.string.remove) {
PrefManager.setVal(PrefName.AppPassword, "")
PrefManager.setVal(PrefName.BiometricToken, "")
PrefManager.setVal(PrefName.OverridePassword, false)
toast(R.string.success)
}
setOnShowListener {
view.passwordInput.requestFocus()
val canAuthenticate =
BiometricManager.from(applicationContext).canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_WEAK
) == BiometricManager.BIOMETRIC_SUCCESS
view.biometricCheckbox.isVisible = canAuthenticate
view.biometricCheckbox.isChecked =
PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty()
view.forgotPasswordCheckbox.isChecked =
PrefManager.getVal(PrefName.OverridePassword)
}
show()
}
}
),
Settings(
type = 1,
name = getString(R.string.backup_restore),
desc = getString(R.string.backup_restore_desc),
icon = R.drawable.backup_restore,
onClick = {
StoragePermissions.downloadsPermission(context)
val selectedArray = mutableListOf(false)
val filteredLocations = Location.entries.filter { it.exportable }
selectedArray.addAll(List(filteredLocations.size - 1) { false })
val dialog = AlertDialog.Builder(context, R.style.MyPopup)
.setTitle(R.string.backup_restore).setMultiChoiceItems(
filteredLocations.map { it.name }.toTypedArray(),
selectedArray.toBooleanArray()
) { _, which, isChecked ->
selectedArray[which] = isChecked
}.setPositiveButton(R.string.button_restore) { dialog, _ ->
openDocumentLauncher.launch(arrayOf("*/*"))
dialog.dismiss()
}.setNegativeButton(R.string.button_backup) { dialog, _ ->
if (!selectedArray.contains(true)) {
toast(R.string.no_location_selected)
return@setNegativeButton
}
dialog.dismiss()
val selected =
filteredLocations.filterIndexed { index, _ -> selectedArray[index] }
if (selected.contains(Location.Protected)) {
passwordAlertDialog(true) { password ->
if (password != null) {
savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
password
)
} else { } else {
PrefManager.setVal(PrefName.BiometricToken, "") toast(R.string.password_cannot_be_empty)
toast(R.string.success)
} }
} else {
toast(R.string.password_mismatch)
} }
} else {
savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
null
)
} }
setNegButton(R.string.cancel) }.setNeutralButton(R.string.cancel) { dialog, _ ->
setNeutralButton(R.string.remove) { dialog.dismiss()
PrefManager.setVal(PrefName.AppPassword, "") }.create()
PrefManager.setVal(PrefName.BiometricToken, "") dialog.window?.setDimAmount(0.8f)
PrefManager.setVal(PrefName.OverridePassword, false) dialog.show()
toast(R.string.success) },
} ),
setOnShowListener { Settings(
view.passwordInput.requestFocus() type = 1,
val canAuthenticate = name = getString(R.string.change_download_location),
BiometricManager.from(applicationContext) desc = getString(R.string.change_download_location_desc),
.canAuthenticate( icon = R.drawable.ic_round_source_24,
BiometricManager.Authenticators.BIOMETRIC_WEAK, onClick = {
) == BiometricManager.BIOMETRIC_SUCCESS context.customAlertDialog().apply {
view.biometricCheckbox.isVisible = canAuthenticate setTitle(R.string.change_download_location)
view.biometricCheckbox.isChecked = setMessage(R.string.download_location_msg)
PrefManager.getVal(PrefName.BiometricToken, "") setPosButton(R.string.ok) {
.isNotEmpty() val oldUri = PrefManager.getVal<String>(PrefName.DownloadsDir)
view.forgotPasswordCheckbox.isChecked = launcher.registerForCallback { success ->
PrefManager.getVal(PrefName.OverridePassword) if (success) {
} toast(getString(R.string.please_wait))
show() val newUri =
} PrefManager.getVal<String>(PrefName.DownloadsDir)
}, GlobalScope.launch(Dispatchers.IO) {
), Injekt.get<DownloadsManager>().moveDownloadsDir(
Settings( context, Uri.parse(oldUri), Uri.parse(newUri)
type = 1, ) { finished, message ->
name = getString(R.string.backup_restore), if (finished) {
desc = getString(R.string.backup_restore_desc), toast(getString(R.string.success))
icon = R.drawable.backup_restore, } else {
onClick = { toast(message)
StoragePermissions.downloadsPermission(context)
val filteredLocations = Location.entries.filter { it.exportable }
val selectedArray = BooleanArray(filteredLocations.size) { false }
context.customAlertDialog().apply {
setTitle(R.string.backup_restore)
multiChoiceItems(
filteredLocations.map { it.name }.toTypedArray(),
selectedArray,
) { updatedSelection ->
for (i in updatedSelection.indices) {
selectedArray[i] = updatedSelection[i]
}
}
setPosButton(R.string.button_restore) {
openDocumentLauncher.launch(arrayOf("*/*"))
}
setNegButton(R.string.button_backup) {
if (!selectedArray.contains(true)) {
toast(R.string.no_location_selected)
return@setNegButton
}
val selected =
filteredLocations.filterIndexed { index, _ -> selectedArray[index] }
if (selected.contains(Location.Protected)) {
passwordAlertDialog(true) { password ->
if (password != null) {
savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
password,
)
} else {
toast(R.string.password_cannot_be_empty)
}
}
} else {
savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
null,
)
}
}
setNeutralButton(R.string.cancel) {}
show()
}
},
),
Settings(
type = 1,
name = getString(R.string.change_download_location),
desc = getString(R.string.change_download_location_desc),
icon = R.drawable.ic_round_source_24,
onClick = {
context.customAlertDialog().apply {
setTitle(R.string.change_download_location)
setMessage(R.string.download_location_msg)
setPosButton(R.string.ok) {
val oldUri =
PrefManager.getVal<String>(PrefName.DownloadsDir)
launcher.registerForCallback { success ->
if (success) {
toast(getString(R.string.please_wait))
val newUri =
PrefManager.getVal<String>(PrefName.DownloadsDir)
GlobalScope.launch(Dispatchers.IO) {
Injekt.get<DownloadsManager>().moveDownloadsDir(
context,
Uri.parse(oldUri),
Uri.parse(newUri),
) { finished, message ->
if (finished) {
toast(getString(R.string.success))
} else {
toast(message)
}
} }
} }
} else {
toast(getString(R.string.error))
} }
} else {
toast(getString(R.string.error))
} }
launcher.launch()
} }
setNegButton(R.string.cancel) launcher.launch()
show()
} }
}, setNegButton(R.string.cancel)
), show()
Settings( }
type = 2, }
name = getString(R.string.always_continue_content), ),
desc = getString(R.string.always_continue_content_desc), Settings(
icon = R.drawable.ic_round_delete_24, type = 2,
isChecked = PrefManager.getVal(PrefName.ContinueMedia), name = getString(R.string.always_continue_content),
switch = { isChecked, _ -> desc = getString(R.string.always_continue_content_desc),
PrefManager.setVal(PrefName.ContinueMedia, isChecked) icon = R.drawable.ic_round_delete_24,
}, isChecked = PrefManager.getVal(PrefName.ContinueMedia),
), switch = { isChecked, _ ->
Settings( PrefManager.setVal(PrefName.ContinueMedia, isChecked)
type = 2, }
name = getString(R.string.hide_private), ),
desc = getString(R.string.hide_private_desc), Settings(
icon = R.drawable.ic_round_remove_red_eye_24, type = 2,
isChecked = PrefManager.getVal(PrefName.HidePrivate), name = getString(R.string.hide_private),
switch = { isChecked, _ -> desc = getString(R.string.hide_private_desc),
PrefManager.setVal(PrefName.HidePrivate, isChecked) icon = R.drawable.ic_round_remove_red_eye_24,
restartApp() isChecked = PrefManager.getVal(PrefName.HidePrivate),
}, switch = { isChecked, _ ->
), PrefManager.setVal(PrefName.HidePrivate, isChecked)
Settings( restartApp()
type = 2, }
name = getString(R.string.search_source_list), ),
desc = getString(R.string.search_source_list_desc), Settings(
icon = R.drawable.ic_round_search_sources_24, type = 2,
isChecked = PrefManager.getVal(PrefName.SearchSources), name = getString(R.string.search_source_list),
switch = { isChecked, _ -> desc = getString(R.string.search_source_list_desc),
PrefManager.setVal(PrefName.SearchSources, isChecked) icon = R.drawable.ic_round_search_sources_24,
}, isChecked = PrefManager.getVal(PrefName.SearchSources),
), switch = { isChecked, _ ->
Settings( PrefManager.setVal(PrefName.SearchSources, isChecked)
type = 2, }
name = getString(R.string.recentlyListOnly), ),
desc = getString(R.string.recentlyListOnly_desc), Settings(
icon = R.drawable.ic_round_new_releases_24, type = 2,
isChecked = PrefManager.getVal(PrefName.RecentlyListOnly), name = getString(R.string.recentlyListOnly),
switch = { isChecked, _ -> desc = getString(R.string.recentlyListOnly_desc),
PrefManager.setVal(PrefName.RecentlyListOnly, isChecked) icon = R.drawable.ic_round_new_releases_24,
}, isChecked = PrefManager.getVal(PrefName.RecentlyListOnly),
), switch = { isChecked, _ ->
Settings( PrefManager.setVal(PrefName.RecentlyListOnly, isChecked)
type = 2, }
name = getString(R.string.adult_only_content), ),
desc = getString(R.string.adult_only_content_desc), Settings(
icon = R.drawable.ic_round_nsfw_24, type = 2,
isChecked = PrefManager.getVal(PrefName.AdultOnly), name = getString(R.string.adult_only_content),
switch = { isChecked, _ -> desc = getString(R.string.adult_only_content_desc),
PrefManager.setVal(PrefName.AdultOnly, isChecked) icon = R.drawable.ic_round_nsfw_24,
restartApp() isChecked = PrefManager.getVal(PrefName.AdultOnly),
}, switch = { isChecked, _ ->
isVisible = Anilist.adult, PrefManager.setVal(PrefName.AdultOnly, isChecked)
), restartApp()
},
isVisible = Anilist.adult
), ),
) )
)
settingsRecyclerView.apply { settingsRecyclerView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
setHasFixedSize(true) setHasFixedSize(true)
} }
var previousStart: View = var previousStart: View = when (PrefManager.getVal<Int>(PrefName.DefaultStartUpTab)) {
when (PrefManager.getVal<Int>(PrefName.DefaultStartUpTab)) { 0 -> uiSettingsAnime
0 -> uiSettingsAnime 1 -> uiSettingsHome
1 -> uiSettingsHome 2 -> uiSettingsManga
2 -> uiSettingsManga else -> uiSettingsHome
else -> uiSettingsHome }
}
previousStart.alpha = 1f previousStart.alpha = 1f
fun uiDefault(mode: Int, current: View) {
fun uiDefault(
mode: Int,
current: View,
) {
previousStart.alpha = 0.33f previousStart.alpha = 0.33f
previousStart = current previousStart = current
current.alpha = 1f current.alpha = 1f
@ -440,13 +431,11 @@ class SettingsCommonActivity : AppCompatActivity() {
uiSettingsManga.setOnClickListener { uiSettingsManga.setOnClickListener {
uiDefault(2, it) uiDefault(2, it)
} }
} }
} }
private fun passwordAlertDialog( private fun passwordAlertDialog(isExporting: Boolean, callback: (CharArray?) -> Unit) {
isExporting: Boolean,
callback: (CharArray?) -> Unit,
) {
val password = CharArray(16).apply { fill('0') } val password = CharArray(16).apply { fill('0') }
// Inflate the dialog layout // Inflate the dialog layout
@ -456,9 +445,7 @@ class SettingsCommonActivity : AppCompatActivity() {
box.setSingleLine() box.setSingleLine()
val dialog = val dialog =
AlertDialog AlertDialog.Builder(this, R.style.MyPopup).setTitle(getString(R.string.enter_password))
.Builder(this, R.style.MyPopup)
.setTitle(getString(R.string.enter_password))
.setView(dialogView.root) .setView(dialogView.root)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel) { dialog, _ -> .setNegativeButton(R.string.cancel) { dialog, _ ->
@ -470,10 +457,7 @@ class SettingsCommonActivity : AppCompatActivity() {
fun handleOkAction() { fun handleOkAction() {
val editText = dialogView.userAgentTextBox val editText = dialogView.userAgentTextBox
if (editText.text?.isNotBlank() == true) { if (editText.text?.isNotBlank() == true) {
editText.text editText.text?.toString()?.trim()?.toCharArray(password)
?.toString()
?.trim()
?.toCharArray(password)
dialog.dismiss() dialog.dismiss()
callback(password) callback(password)
} else { } else {
@ -489,20 +473,18 @@ class SettingsCommonActivity : AppCompatActivity() {
} }
} }
dialogView.subtitle.visibility = View.VISIBLE dialogView.subtitle.visibility = View.VISIBLE
if (!isExporting) { if (!isExporting) dialogView.subtitle.text =
dialogView.subtitle.text = getString(R.string.enter_password_to_decrypt_file)
getString(R.string.enter_password_to_decrypt_file)
}
dialog.window?.apply {
setDimAmount(0.8f) dialog.window?.setDimAmount(0.8f)
attributes.windowAnimations = android.R.style.Animation_Dialog
}
dialog.show() dialog.show()
// Override the positive button here // Override the positive button here
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
handleOkAction() handleOkAction()
} }
} }
}
}

View file

@ -128,27 +128,26 @@ class SettingsNotificationActivity : AppCompatActivity() {
PrefManager.getVal<Set<String>>(PrefName.AnilistFilteredTypes) PrefManager.getVal<Set<String>>(PrefName.AnilistFilteredTypes)
.toMutableSet() .toMutableSet()
val selected = types.map { filteredTypes.contains(it) }.toBooleanArray() val selected = types.map { filteredTypes.contains(it) }.toBooleanArray()
context.customAlertDialog().apply { val dialog = AlertDialog.Builder(context, R.style.MyPopup)
setTitle(R.string.anilist_notification_filters) .setTitle(R.string.anilist_notification_filters)
multiChoiceItems( .setMultiChoiceItems(
types.map { name -> types.map { name ->
name.replace("_", " ").lowercase().replaceFirstChar { name.replace("_", " ").lowercase().replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
} }
}.toTypedArray(), }.toTypedArray(),
selected selected
) { updatedSelected -> ) { _, which, isChecked ->
types.forEachIndexed { index, type -> val type = types[which]
if (updatedSelected[index]) { if (isChecked) {
filteredTypes.add(type) filteredTypes.add(type)
} else { } else {
filteredTypes.remove(type) filteredTypes.remove(type)
}
} }
PrefManager.setVal(PrefName.AnilistFilteredTypes, filteredTypes) PrefManager.setVal(PrefName.AnilistFilteredTypes, filteredTypes)
} }.create()
show() dialog.window?.setDimAmount(0.8f)
} dialog.show()
} }
), ),
@ -161,24 +160,27 @@ class SettingsNotificationActivity : AppCompatActivity() {
desc = getString(R.string.anilist_notifications_checking_time_desc), desc = getString(R.string.anilist_notifications_checking_time_desc),
icon = R.drawable.ic_round_notifications_none_24, icon = R.drawable.ic_round_notifications_none_24,
onClick = { onClick = {
context.customAlertDialog().apply { val selected =
setTitle(R.string.subscriptions_checking_time) PrefManager.getVal<Int>(PrefName.AnilistNotificationInterval)
singleChoiceItems( val dialog = AlertDialog.Builder(context, R.style.MyPopup)
.setTitle(R.string.subscriptions_checking_time)
.setSingleChoiceItems(
aItems.toTypedArray(), aItems.toTypedArray(),
PrefManager.getVal<Int>(PrefName.AnilistNotificationInterval) selected
) { i -> ) { dialog, i ->
PrefManager.setVal(PrefName.AnilistNotificationInterval, i) PrefManager.setVal(PrefName.AnilistNotificationInterval, i)
it.settingsTitle.text = it.settingsTitle.text =
getString( getString(
R.string.anilist_notifications_checking_time, R.string.anilist_notifications_checking_time,
aItems[i] aItems[i]
) )
dialog.dismiss()
TaskScheduler.create( TaskScheduler.create(
context, PrefManager.getVal(PrefName.UseAlarmManager) context, PrefManager.getVal(PrefName.UseAlarmManager)
).scheduleAllTasks(context) ).scheduleAllTasks(context)
} }.create()
show() dialog.window?.setDimAmount(0.8f)
} dialog.show()
} }
), ),
Settings( Settings(
@ -190,24 +192,27 @@ class SettingsNotificationActivity : AppCompatActivity() {
desc = getString(R.string.comment_notification_checking_time_desc), desc = getString(R.string.comment_notification_checking_time_desc),
icon = R.drawable.ic_round_notifications_none_24, icon = R.drawable.ic_round_notifications_none_24,
onClick = { onClick = {
context.customAlertDialog().apply { val selected =
setTitle(R.string.subscriptions_checking_time) PrefManager.getVal<Int>(PrefName.CommentNotificationInterval)
singleChoiceItems( val dialog = AlertDialog.Builder(context, R.style.MyPopup)
.setTitle(R.string.subscriptions_checking_time)
.setSingleChoiceItems(
cItems.toTypedArray(), cItems.toTypedArray(),
PrefManager.getVal<Int>(PrefName.CommentNotificationInterval) selected
) { i -> ) { dialog, i ->
PrefManager.setVal(PrefName.CommentNotificationInterval, i) PrefManager.setVal(PrefName.CommentNotificationInterval, i)
it.settingsTitle.text = it.settingsTitle.text =
getString( getString(
R.string.comment_notification_checking_time, R.string.comment_notification_checking_time,
cItems[i] cItems[i]
) )
dialog.dismiss()
TaskScheduler.create( TaskScheduler.create(
context, PrefManager.getVal(PrefName.UseAlarmManager) context, PrefManager.getVal(PrefName.UseAlarmManager)
).scheduleAllTasks(context) ).scheduleAllTasks(context)
} }.create()
show() dialog.window?.setDimAmount(0.8f)
} dialog.show()
} }
), ),
Settings( Settings(
@ -234,10 +239,10 @@ class SettingsNotificationActivity : AppCompatActivity() {
isChecked = PrefManager.getVal(PrefName.UseAlarmManager), isChecked = PrefManager.getVal(PrefName.UseAlarmManager),
switch = { isChecked, view -> switch = { isChecked, view ->
if (isChecked) { if (isChecked) {
context.customAlertDialog().apply { val alertDialog = AlertDialog.Builder(context, R.style.MyPopup)
setTitle(R.string.use_alarm_manager) .setTitle(R.string.use_alarm_manager)
setMessage(R.string.use_alarm_manager_confirm) .setMessage(R.string.use_alarm_manager_confirm)
setPosButton(R.string.use) { .setPositiveButton(R.string.use) { dialog, _ ->
PrefManager.setVal(PrefName.UseAlarmManager, true) PrefManager.setVal(PrefName.UseAlarmManager, true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!(getSystemService(Context.ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()) { if (!(getSystemService(Context.ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()) {
@ -247,13 +252,15 @@ class SettingsNotificationActivity : AppCompatActivity() {
view.settingsButton.isChecked = true view.settingsButton.isChecked = true
} }
} }
} dialog.dismiss()
setNegButton(R.string.cancel) { }.setNegativeButton(R.string.cancel) { dialog, _ ->
view.settingsButton.isChecked = false view.settingsButton.isChecked = false
PrefManager.setVal(PrefName.UseAlarmManager, false) PrefManager.setVal(PrefName.UseAlarmManager, false)
}
show() dialog.dismiss()
} }.create()
alertDialog.window?.setDimAmount(0.8f)
alertDialog.show()
} else { } else {
PrefManager.setVal(PrefName.UseAlarmManager, false) PrefManager.setVal(PrefName.UseAlarmManager, false)
TaskScheduler.create(context, true).cancelAllTasks() TaskScheduler.create(context, true).cancelAllTasks()
@ -270,4 +277,4 @@ class SettingsNotificationActivity : AppCompatActivity() {
} }
} }
} }
} }

View file

@ -96,8 +96,7 @@ class SettingsThemeActivity : AppCompatActivity(), SimpleDialog.OnDialogResultLi
themeSwitcher.apply { themeSwitcher.apply {
setText(themeText) setText(themeText)
setAdapter( setAdapter(
ArrayAdapter( ArrayAdapter(context,
context,
R.layout.item_dropdown, R.layout.item_dropdown,
ThemeManager.Companion.Theme.entries.map { ThemeManager.Companion.Theme.entries.map {
it.theme.substring( it.theme.substring(

View file

@ -52,15 +52,14 @@ class SubscriptionsBottomDialog : BottomSheetDialogFragment() {
} }
groupedSubscriptions.forEach { (parserName, mediaList) -> groupedSubscriptions.forEach { (parserName, mediaList) ->
adapter.add( adapter.add(SubscriptionSource(
SubscriptionSource( parserName,
parserName, mediaList.toMutableList(),
mediaList.toMutableList(), adapter,
adapter, getParserIcon(parserName)
getParserIcon(parserName) ) { group ->
) { group -> adapter.remove(group)
adapter.remove(group) })
})
} }
} }

View file

@ -8,7 +8,7 @@
android:padding="16dp"> android:padding="16dp">
<LinearLayout <LinearLayout
android:layout_width="326dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
@ -160,8 +160,8 @@
android:orientation="horizontal"> android:orientation="horizontal">
<LinearLayout <LinearLayout
android:layout_width="263dp" android:layout_width="265dp"
android:layout_height="60dp" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -171,23 +171,14 @@
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:text="@string/download" /> android:text="@string/download" />
<EditText <TextView
android:id="@+id/downloadNo" android:id="@+id/downloadNo"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:textColor="?attr/colorSecondary" android:textColor="?attr/colorSecondary"
android:textSize="12dp"
tools:ignore="TextContrastCheck" tools:ignore="TextContrastCheck"
tools:text="Number" /> tools:text="number" />
<!-- <TextView-->
<!-- android:id="@+id/downloadNo"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:fontFamily="@font/poppins_bold"-->
<!-- android:textColor="?attr/colorSecondary"-->
<!-- tools:ignore="TextContrastCheck"-->
<!-- tools:text="number" />-->
</LinearLayout> </LinearLayout>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
@ -200,7 +191,7 @@
<ImageButton <ImageButton
android:id="@+id/mediaDownloadTop" android:id="@+id/mediaDownloadTop"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="60dp" android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
app:srcCompat="@drawable/ic_download_24" app:srcCompat="@drawable/ic_download_24"
app:tint="?attr/colorOnBackground" app:tint="?attr/colorOnBackground"
@ -322,9 +313,9 @@
android:text="@string/reset" /> android:text="@string/reset" />
<TextView <TextView
android:id="@+id/reset_progress_def"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/reset_progress_def"
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:text="" android:text=""
android:textColor="?attr/colorSecondary" android:textColor="?attr/colorSecondary"

View file

@ -12,7 +12,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.9.0' classpath 'com.android.tools.build:gradle:8.7.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.google.devtools.ksp:symbol-processing-api:$ksp_version" classpath "com.google.devtools.ksp:symbol-processing-api:$ksp_version"

View file

@ -1,6 +1,6 @@
#Wed Aug 30 19:57:04 IST 2023 #Wed Aug 30 19:57:04 IST 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -1,4 +1,53 @@
# 3.2.1 # 3.1.0
- **New Features:**
- Addons
- Torrent support addon
- Anime downloading addon (mkv files pog)
- Available in app settings
- Anilist reviews in app
- Media subscriptions added to notification tab
- Notification filtering
- Ability to post activitys
- Ability to reply to activities
- Extension tester
- Media subscription Viewer
- Instagram-style stories
- More audio options for some extensions
- Ability to hide items on the home screen
- Ability to set a downloads directory
- 2 functioning widgets
- App lock ( ͡° ͜ʖ ͡°)
- More manga and anime feeds on the home page
- Settings page redesign
- New app crash notifier
- Voice actors
- Additional repo support
- Various UI uplifts
- **Bugfixes:** - **Bugfixes:**
- Fix a crash after watching a video - Scanlator/language not saving after leaving app
- notification red dot not hiding on home pages
- comment/activity scrolling not working on some parts of the screen
- comment notifications falling to the bottom of the list
- Fixed some sources without audio
- Initial app loading time reduced
- activity text more visible
- novel extensions not installing
- Many sources not working
- Subscription notifications not using the correct source
- Notification red dot showing with no new notifications
- Various bug/crash fixes
- General theme tweaks
- Fixed some network-related crashes
- Subscription notifications not working for some people
- Fix for file permissions on older Android versions
- Search list view not working
- Media page opening twice on notification click
- A Special Thanks to all those who contributed :heart:
- **Like what you see?**
- Consider supporting me on [Github](https://github.com/sponsors/rebelonion) or [Buy Me a Coffee](https://www.buymeacoffee.com/rebelonion)!
![alt text](https://media1.tenor.com/m/P7hCyZlzDH4AAAAC/wink-anime.gif)