Compare commits

...

7 Commits

Author SHA1 Message Date
rebel onion
7053a7b4b2 Update README.md 2025-01-16 20:27:24 -06:00
rebel onion
1c156053d0 Merge pull request #565 from rebelonion/dev
Dev
2025-01-16 00:15:34 -06:00
rebel onion
6fa2f11db2 Merge branch 'main' into dev 2025-01-16 00:15:21 -06:00
rebel onion
a5babea27c chore: version bump 2025-01-16 00:14:25 -06:00
rebelonion
8a9b8cca7e fix: Serializable 2025-01-13 14:23:02 -06:00
rebel onion
7479f5f43b Update stable.md 2025-01-09 19:58:00 -06:00
Sadwhy
3ac9307329 Use custom alert builder for all dialogs [skip ci] 2025-01-09 18:04:22 +05:30
10 changed files with 1479 additions and 1308 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=030201&font_family=Poppins&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=000000&font_family=Cookie&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

@@ -18,8 +18,8 @@ android {
minSdk 21 minSdk 21
targetSdk 35 targetSdk 35
versionCode((System.currentTimeMillis() / 60000).toInteger()) versionCode((System.currentTimeMillis() / 60000).toInteger())
versionName "3.2.0" versionName "3.2.1"
versionCode 300200000 versionCode 300200100
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(

File diff suppressed because it is too large Load Diff

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,6 +19,7 @@ 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
@@ -57,23 +58,16 @@ 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 = {
val dialog = AlertDialog.Builder(context, R.style.MyPopup) context.customAlertDialog().apply {
.setTitle(R.string.purge_anime_downloads) setTitle(R.string.purge_anime_downloads)
.setMessage( setMessage(R.string.purge_confirm, getString(R.string.anime))
getString( setPosButton(R.string.yes, onClick = {
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() })
}.setNegativeButton(R.string.no) { dialog, _ -> setNegButton(R.string.no)
dialog.dismiss() show()
}.create() }
dialog.window?.setDimAmount(0.8f)
dialog.show()
} }
), ),
@@ -143,4 +137,4 @@ class SettingsAnimeActivity : AppCompatActivity() {
} }
} }
} }

View File

@@ -45,7 +45,6 @@ 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
@@ -62,23 +61,27 @@ class SettingsCommonActivity : AppCompatActivity() {
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri != null) { if (uri != null) {
try { try {
val jsonString = contentResolver.openInputStream(uri)?.readBytes() val jsonString =
?: throw Exception("Error reading file") contentResolver.openInputStream(uri)?.readBytes()
?: 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 = try { val decryptedJson =
PreferenceKeystore.decryptWithPassword( try {
password, encrypted, salt PreferenceKeystore.decryptWithPassword(
) password,
} catch (e: Exception) { encrypted,
toast(getString(R.string.incorrect_password)) salt,
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))
@@ -100,7 +103,6 @@ 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
@@ -108,27 +110,30 @@ class SettingsCommonActivity : AppCompatActivity() {
commonSettingsBack.setOnClickListener { commonSettingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
val exDns = listOf( val exDns =
"None", listOf(
"Cloudflare", "None",
"Google", "Cloudflare",
"AdGuard", "Google",
"Quad9", "AdGuard",
"AliDNS", "Quad9",
"DNSPod", "AliDNS",
"360", "DNSPod",
"Quad101", "360",
"Mullvad", "Quad101",
"Controld", "Mullvad",
"Njalla", "Controld",
"Shecan", "Njalla",
"Libre" "Shecan",
) "Libre",
)
settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)]) settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)])
settingsExtensionDns.setAdapter( settingsExtensionDns.setAdapter(
ArrayAdapter( ArrayAdapter(
context, R.layout.item_dropdown, exDns context,
) R.layout.item_dropdown,
exDns,
),
) )
settingsExtensionDns.setOnItemClickListener { _, _, i, _ -> settingsExtensionDns.setOnItemClickListener { _, _, i, _ ->
PrefManager.setVal(PrefName.DohProvider, i) PrefManager.setVal(PrefName.DohProvider, i)
@@ -136,283 +141,281 @@ class SettingsCommonActivity : AppCompatActivity() {
restartApp() restartApp()
} }
settingsRecyclerView.adapter = SettingsAdapter( settingsRecyclerView.adapter =
arrayListOf( SettingsAdapter(
Settings( arrayListOf(
type = 1, Settings(
name = getString(R.string.ui_settings), type = 1,
desc = getString(R.string.ui_settings_desc), name = getString(R.string.ui_settings),
icon = R.drawable.ic_round_auto_awesome_24, desc = getString(R.string.ui_settings_desc),
onClick = { icon = R.drawable.ic_round_auto_awesome_24,
startActivity( onClick = {
Intent( startActivity(
context, Intent(
UserInterfaceSettingsActivity::class.java context,
UserInterfaceSettingsActivity::class.java,
),
) )
) },
}, isActivity = true,
isActivity = true ),
), Settings(
Settings( type = 1,
type = 2, name = getString(R.string.download_manager_select),
name = getString(R.string.open_animanga_directly), desc = getString(R.string.download_manager_select_desc),
desc = getString(R.string.open_animanga_directly_info), icon = R.drawable.ic_download_24,
icon = R.drawable.ic_round_search_24, onClick = {
isChecked = PrefManager.getVal(PrefName.AniMangaSearchDirect), val managers = arrayOf("Default", "1DM", "ADM")
switch = { isChecked, _ -> customAlertDialog().apply {
PrefManager.setVal(PrefName.AniMangaSearchDirect, isChecked) setTitle(getString(R.string.download_manager))
} singleChoiceItems(
), managers,
Settings( PrefManager.getVal(PrefName.DownloadManager),
type = 1, ) { count ->
name = getString(R.string.download_manager_select), PrefManager.setVal(PrefName.DownloadManager, count)
desc = getString(R.string.download_manager_select_desc),
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),
desc = getString(R.string.app_lock_desc),
icon = R.drawable.ic_round_lock_open_24,
onClick = {
customAlertDialog().apply {
val view = DialogSetPasswordBinding.inflate(layoutInflater)
setTitle(R.string.app_lock)
setCustomView(view.root)
setPosButton(R.string.ok) {
if (view.forgotPasswordCheckbox.isChecked) {
PrefManager.setVal(PrefName.OverridePassword, true)
} }
val password = view.passwordInput.text.toString() show()
val confirmPassword = view.confirmPasswordInput.text.toString() }
if (password == confirmPassword && password.isNotEmpty()) { },
PrefManager.setVal(PrefName.AppPassword, password) ),
if (view.biometricCheckbox.isChecked) { Settings(
val canBiometricPrompt = type = 1,
BiometricManager.from(applicationContext) name = getString(R.string.app_lock),
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS desc = getString(R.string.app_lock_desc),
icon = R.drawable.ic_round_lock_open_24,
if (canBiometricPrompt) { onClick = {
val biometricPrompt = customAlertDialog().apply {
BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ -> val view = DialogSetPasswordBinding.inflate(layoutInflater)
val token = UUID.randomUUID().toString() setTitle(R.string.app_lock)
PrefManager.setVal( setCustomView(view.root)
PrefName.BiometricToken, setPosButton(R.string.ok) {
token if (view.forgotPasswordCheckbox.isChecked) {
) PrefManager.setVal(PrefName.OverridePassword, true)
toast(R.string.success)
}
val promptInfo =
BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity)
biometricPrompt.authenticate(promptInfo)
}
} else {
PrefManager.setVal(PrefName.BiometricToken, "")
toast(R.string.success)
} }
} else { val password = view.passwordInput.text.toString()
toast(R.string.password_mismatch) val confirmPassword = view.confirmPasswordInput.text.toString()
} if (password == confirmPassword && password.isNotEmpty()) {
} PrefManager.setVal(PrefName.AppPassword, password)
setNegButton(R.string.cancel) if (view.biometricCheckbox.isChecked) {
setNeutralButton(R.string.remove) { val canBiometricPrompt =
PrefManager.setVal(PrefName.AppPassword, "") BiometricManager
PrefManager.setVal(PrefName.BiometricToken, "") .from(applicationContext)
PrefManager.setVal(PrefName.OverridePassword, false) .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ==
toast(R.string.success) BiometricManager.BIOMETRIC_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()
}
}
), if (canBiometricPrompt) {
Settings( val biometricPrompt =
type = 1, BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ ->
name = getString(R.string.backup_restore), val token = UUID.randomUUID().toString()
desc = getString(R.string.backup_restore_desc), PrefManager.setVal(
icon = R.drawable.backup_restore, PrefName.BiometricToken,
onClick = { token,
StoragePermissions.downloadsPermission(context) )
val selectedArray = mutableListOf(false) toast(R.string.success)
val filteredLocations = Location.entries.filter { it.exportable } }
selectedArray.addAll(List(filteredLocations.size - 1) { false }) val promptInfo =
val dialog = AlertDialog.Builder(context, R.style.MyPopup) BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity)
.setTitle(R.string.backup_restore).setMultiChoiceItems( biometricPrompt.authenticate(promptInfo)
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 {
toast(R.string.password_cannot_be_empty) PrefManager.setVal(PrefName.BiometricToken, "")
toast(R.string.success)
} }
} else {
toast(R.string.password_mismatch)
} }
} else {
savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
null
)
} }
}.setNeutralButton(R.string.cancel) { dialog, _ -> setNegButton(R.string.cancel)
dialog.dismiss() setNeutralButton(R.string.remove) {
}.create() PrefManager.setVal(PrefName.AppPassword, "")
dialog.window?.setDimAmount(0.8f) PrefManager.setVal(PrefName.BiometricToken, "")
dialog.show() PrefManager.setVal(PrefName.OverridePassword, false)
}, toast(R.string.success)
), }
Settings( setOnShowListener {
type = 1, view.passwordInput.requestFocus()
name = getString(R.string.change_download_location), val canAuthenticate =
desc = getString(R.string.change_download_location_desc), BiometricManager.from(applicationContext).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, "").isNotEmpty()
setPosButton(R.string.ok) { view.forgotPasswordCheckbox.isChecked =
val oldUri = PrefManager.getVal<String>(PrefName.DownloadsDir) PrefManager.getVal(PrefName.OverridePassword)
launcher.registerForCallback { success -> }
if (success) { show()
toast(getString(R.string.please_wait)) }
val newUri = },
PrefManager.getVal<String>(PrefName.DownloadsDir) ),
GlobalScope.launch(Dispatchers.IO) { Settings(
Injekt.get<DownloadsManager>().moveDownloadsDir( type = 1,
context, Uri.parse(oldUri), Uri.parse(newUri) name = getString(R.string.backup_restore),
) { finished, message -> desc = getString(R.string.backup_restore_desc),
if (finished) { icon = R.drawable.backup_restore,
toast(getString(R.string.success)) onClick = {
} else { StoragePermissions.downloadsPermission(context)
toast(message) 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 { } else {
toast(getString(R.string.error)) savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
null,
)
} }
} }
launcher.launch() setNeutralButton(R.string.cancel) {}
show()
} }
setNegButton(R.string.cancel) },
show() ),
} Settings(
} type = 1,
), name = getString(R.string.change_download_location),
Settings( desc = getString(R.string.change_download_location_desc),
type = 2, icon = R.drawable.ic_round_source_24,
name = getString(R.string.always_continue_content), onClick = {
desc = getString(R.string.always_continue_content_desc), context.customAlertDialog().apply {
icon = R.drawable.ic_round_delete_24, setTitle(R.string.change_download_location)
isChecked = PrefManager.getVal(PrefName.ContinueMedia), setMessage(R.string.download_location_msg)
switch = { isChecked, _ -> setPosButton(R.string.ok) {
PrefManager.setVal(PrefName.ContinueMedia, isChecked) val oldUri = PrefManager.getVal<String>(PrefName.DownloadsDir)
} launcher.registerForCallback { success ->
), if (success) {
Settings( toast(getString(R.string.please_wait))
type = 2, val newUri =
name = getString(R.string.hide_private), PrefManager.getVal<String>(PrefName.DownloadsDir)
desc = getString(R.string.hide_private_desc), GlobalScope.launch(Dispatchers.IO) {
icon = R.drawable.ic_round_remove_red_eye_24, Injekt.get<DownloadsManager>().moveDownloadsDir(
isChecked = PrefManager.getVal(PrefName.HidePrivate), context,
switch = { isChecked, _ -> Uri.parse(oldUri),
PrefManager.setVal(PrefName.HidePrivate, isChecked) Uri.parse(newUri),
restartApp() ) { finished, message ->
} if (finished) {
), toast(getString(R.string.success))
Settings( } else {
type = 2, toast(message)
name = getString(R.string.search_source_list), }
desc = getString(R.string.search_source_list_desc), }
icon = R.drawable.ic_round_search_sources_24, }
isChecked = PrefManager.getVal(PrefName.SearchSources), } else {
switch = { isChecked, _ -> toast(getString(R.string.error))
PrefManager.setVal(PrefName.SearchSources, isChecked) }
} }
), launcher.launch()
Settings( }
type = 2, setNegButton(R.string.cancel)
name = getString(R.string.recentlyListOnly), show()
desc = getString(R.string.recentlyListOnly_desc), }
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.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.adult_only_content), switch = { isChecked, _ ->
desc = getString(R.string.adult_only_content_desc), PrefManager.setVal(PrefName.ContinueMedia, isChecked)
icon = R.drawable.ic_round_nsfw_24, },
isChecked = PrefManager.getVal(PrefName.AdultOnly), ),
switch = { isChecked, _ -> Settings(
PrefManager.setVal(PrefName.AdultOnly, isChecked) type = 2,
restartApp() name = getString(R.string.hide_private),
}, desc = getString(R.string.hide_private_desc),
isVisible = Anilist.adult icon = R.drawable.ic_round_remove_red_eye_24,
isChecked = PrefManager.getVal(PrefName.HidePrivate),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.HidePrivate, isChecked)
restartApp()
},
),
Settings(
type = 2,
name = getString(R.string.search_source_list),
desc = getString(R.string.search_source_list_desc),
icon = R.drawable.ic_round_search_sources_24,
isChecked = PrefManager.getVal(PrefName.SearchSources),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.SearchSources, isChecked)
},
),
Settings(
type = 2,
name = getString(R.string.recentlyListOnly),
desc = getString(R.string.recentlyListOnly_desc),
icon = R.drawable.ic_round_new_releases_24,
isChecked = PrefManager.getVal(PrefName.RecentlyListOnly),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.RecentlyListOnly, isChecked)
},
),
Settings(
type = 2,
name = getString(R.string.adult_only_content),
desc = getString(R.string.adult_only_content_desc),
icon = R.drawable.ic_round_nsfw_24,
isChecked = PrefManager.getVal(PrefName.AdultOnly),
switch = { isChecked, _ ->
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 = when (PrefManager.getVal<Int>(PrefName.DefaultStartUpTab)) { var previousStart: View =
0 -> uiSettingsAnime when (PrefManager.getVal<Int>(PrefName.DefaultStartUpTab)) {
1 -> uiSettingsHome 0 -> uiSettingsAnime
2 -> uiSettingsManga 1 -> uiSettingsHome
else -> uiSettingsHome 2 -> uiSettingsManga
} 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
@@ -431,11 +434,13 @@ class SettingsCommonActivity : AppCompatActivity() {
uiSettingsManga.setOnClickListener { uiSettingsManga.setOnClickListener {
uiDefault(2, it) uiDefault(2, it)
} }
} }
} }
private fun passwordAlertDialog(isExporting: Boolean, callback: (CharArray?) -> Unit) { private fun passwordAlertDialog(
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
@@ -445,7 +450,9 @@ class SettingsCommonActivity : AppCompatActivity() {
box.setSingleLine() box.setSingleLine()
val dialog = val dialog =
AlertDialog.Builder(this, R.style.MyPopup).setTitle(getString(R.string.enter_password)) AlertDialog
.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, _ ->
@@ -457,7 +464,10 @@ 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?.toString()?.trim()?.toCharArray(password) editText.text
?.toString()
?.trim()
?.toCharArray(password)
dialog.dismiss() dialog.dismiss()
callback(password) callback(password)
} else { } else {
@@ -473,18 +483,20 @@ class SettingsCommonActivity : AppCompatActivity() {
} }
} }
dialogView.subtitle.visibility = View.VISIBLE dialogView.subtitle.visibility = View.VISIBLE
if (!isExporting) dialogView.subtitle.text = if (!isExporting) {
getString(R.string.enter_password_to_decrypt_file) dialogView.subtitle.text =
getString(R.string.enter_password_to_decrypt_file)
}
dialog.window?.apply {
dialog.window?.setDimAmount(0.8f) 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

@@ -82,18 +82,9 @@ class SettingsNotificationActivity : AppCompatActivity() {
setTitle(R.string.subscriptions_checking_time) setTitle(R.string.subscriptions_checking_time)
singleChoiceItems(timeNames, curTime) { i -> singleChoiceItems(timeNames, curTime) { i ->
curTime = i curTime = i
it.settingsTitle.text = getString( it.settingsTitle.text = getString(R.string.subscriptions_checking_time_s, timeNames[i])
R.string.subscriptions_checking_time_s, PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime)
timeNames[i] TaskScheduler.create(context, PrefManager.getVal(PrefName.UseAlarmManager)).scheduleAllTasks(context)
)
PrefManager.setVal(
PrefName.SubscriptionNotificationInterval,
curTime
)
TaskScheduler.create(
context,
PrefManager.getVal(PrefName.UseAlarmManager)
).scheduleAllTasks(context)
} }
show() show()
} }
@@ -128,26 +119,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()
val dialog = AlertDialog.Builder(context, R.style.MyPopup) context.customAlertDialog().apply {
.setTitle(R.string.anilist_notification_filters) setTitle(R.string.anilist_notification_filters)
.setMultiChoiceItems( multiChoiceItems(
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
) { _, which, isChecked -> ) { updatedSelected ->
val type = types[which] types.forEachIndexed { index, type ->
if (isChecked) { if (updatedSelected[index]) {
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() }
dialog.window?.setDimAmount(0.8f) show()
dialog.show() }
} }
), ),
@@ -160,27 +151,24 @@ 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 = {
val selected = context.customAlertDialog().apply {
PrefManager.getVal<Int>(PrefName.AnilistNotificationInterval) setTitle(R.string.subscriptions_checking_time)
val dialog = AlertDialog.Builder(context, R.style.MyPopup) singleChoiceItems(
.setTitle(R.string.subscriptions_checking_time)
.setSingleChoiceItems(
aItems.toTypedArray(), aItems.toTypedArray(),
selected PrefManager.getVal<Int>(PrefName.AnilistNotificationInterval)
) { dialog, i -> ) { 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() }
dialog.window?.setDimAmount(0.8f) show()
dialog.show() }
} }
), ),
Settings( Settings(
@@ -192,27 +180,24 @@ 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 = {
val selected = context.customAlertDialog().apply {
PrefManager.getVal<Int>(PrefName.CommentNotificationInterval) setTitle(R.string.subscriptions_checking_time)
val dialog = AlertDialog.Builder(context, R.style.MyPopup) singleChoiceItems(
.setTitle(R.string.subscriptions_checking_time)
.setSingleChoiceItems(
cItems.toTypedArray(), cItems.toTypedArray(),
selected PrefManager.getVal<Int>(PrefName.CommentNotificationInterval)
) { dialog, i -> ) { 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() }
dialog.window?.setDimAmount(0.8f) show()
dialog.show() }
} }
), ),
Settings( Settings(
@@ -239,10 +224,10 @@ class SettingsNotificationActivity : AppCompatActivity() {
isChecked = PrefManager.getVal(PrefName.UseAlarmManager), isChecked = PrefManager.getVal(PrefName.UseAlarmManager),
switch = { isChecked, view -> switch = { isChecked, view ->
if (isChecked) { if (isChecked) {
val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) context.customAlertDialog().apply {
.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)
.setPositiveButton(R.string.use) { dialog, _ -> setPosButton(R.string.use) {
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()) {
@@ -252,15 +237,13 @@ class SettingsNotificationActivity : AppCompatActivity() {
view.settingsButton.isChecked = true view.settingsButton.isChecked = true
} }
} }
dialog.dismiss() }
}.setNegativeButton(R.string.cancel) { dialog, _ -> setNegButton(R.string.cancel) {
view.settingsButton.isChecked = false view.settingsButton.isChecked = false
PrefManager.setVal(PrefName.UseAlarmManager, false) PrefManager.setVal(PrefName.UseAlarmManager, false)
}
dialog.dismiss() show()
}.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()
@@ -277,4 +260,4 @@ class SettingsNotificationActivity : AppCompatActivity() {
} }
} }
} }
} }

View File

@@ -1,53 +1,4 @@
# 3.1.0 # 3.2.1
- **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:**
- Scanlator/language not saving after leaving app - Fix a crash after watching a video
- 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)