Merge pull request #565 from rebelonion/dev

Dev
This commit is contained in:
rebel onion 2025-01-16 00:15:34 -06:00 committed by GitHub
commit 1c156053d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1479 additions and 1302 deletions

View file

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

View file

@ -253,7 +253,7 @@ data class MediaStreamingEpisode(
// The site location of the streaming episode
@SerialName("site") var site: String?,
)
) : java.io.Serializable
@Serializable
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)
ThemeManager(this).applyTheme()
initActivity(this)
binding = ActivityCrashBinding.inflate(layoutInflater)
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
binding = ActivityCrashBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight

View file

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

View file

@ -45,7 +45,6 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.UUID
class SettingsCommonActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsCommonBinding
private lateinit var launcher: LauncherWrapper
@ -62,7 +61,8 @@ class SettingsCommonActivity : AppCompatActivity() {
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri != null) {
try {
val jsonString = contentResolver.openInputStream(uri)?.readBytes()
val jsonString =
contentResolver.openInputStream(uri)?.readBytes()
?: throw Exception("Error reading file")
val name = DocumentFile.fromSingleUri(this, uri)?.name ?: "settings"
// .sani is encrypted, .ani is not
@ -71,9 +71,12 @@ class SettingsCommonActivity : AppCompatActivity() {
if (password != null) {
val salt = jsonString.copyOfRange(0, 16)
val encrypted = jsonString.copyOfRange(16, jsonString.size)
val decryptedJson = try {
val decryptedJson =
try {
PreferenceKeystore.decryptWithPassword(
password, encrypted, salt
password,
encrypted,
salt,
)
} catch (e: Exception) {
toast(getString(R.string.incorrect_password))
@ -100,7 +103,6 @@ class SettingsCommonActivity : AppCompatActivity() {
launcher = LauncherWrapper(this, contract)
binding.apply {
settingsCommonLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight
bottomMargin = navBarHeight
@ -108,7 +110,8 @@ class SettingsCommonActivity : AppCompatActivity() {
commonSettingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
val exDns = listOf(
val exDns =
listOf(
"None",
"Cloudflare",
"Google",
@ -122,13 +125,15 @@ class SettingsCommonActivity : AppCompatActivity() {
"Controld",
"Njalla",
"Shecan",
"Libre"
"Libre",
)
settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)])
settingsExtensionDns.setAdapter(
ArrayAdapter(
context, R.layout.item_dropdown, exDns
)
context,
R.layout.item_dropdown,
exDns,
),
)
settingsExtensionDns.setOnItemClickListener { _, _, i, _ ->
PrefManager.setVal(PrefName.DohProvider, i)
@ -136,7 +141,8 @@ class SettingsCommonActivity : AppCompatActivity() {
restartApp()
}
settingsRecyclerView.adapter = SettingsAdapter(
settingsRecyclerView.adapter =
SettingsAdapter(
arrayListOf(
Settings(
type = 1,
@ -147,21 +153,11 @@ class SettingsCommonActivity : AppCompatActivity() {
startActivity(
Intent(
context,
UserInterfaceSettingsActivity::class.java
)
UserInterfaceSettingsActivity::class.java,
),
)
},
isActivity = true
),
Settings(
type = 2,
name = getString(R.string.open_animanga_directly),
desc = getString(R.string.open_animanga_directly_info),
icon = R.drawable.ic_round_search_24,
isChecked = PrefManager.getVal(PrefName.AniMangaSearchDirect),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.AniMangaSearchDirect, isChecked)
}
isActivity = true,
),
Settings(
type = 1,
@ -174,13 +170,13 @@ class SettingsCommonActivity : AppCompatActivity() {
setTitle(getString(R.string.download_manager))
singleChoiceItems(
managers,
PrefManager.getVal(PrefName.DownloadManager)
PrefManager.getVal(PrefName.DownloadManager),
) { count ->
PrefManager.setVal(PrefName.DownloadManager, count)
}
show()
}
}
},
),
Settings(
type = 1,
@ -202,8 +198,10 @@ class SettingsCommonActivity : AppCompatActivity() {
PrefManager.setVal(PrefName.AppPassword, password)
if (view.biometricCheckbox.isChecked) {
val canBiometricPrompt =
BiometricManager.from(applicationContext)
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS
BiometricManager
.from(applicationContext)
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ==
BiometricManager.BIOMETRIC_SUCCESS
if (canBiometricPrompt) {
val biometricPrompt =
@ -211,7 +209,7 @@ class SettingsCommonActivity : AppCompatActivity() {
val token = UUID.randomUUID().toString()
PrefManager.setVal(
PrefName.BiometricToken,
token
token,
)
toast(R.string.success)
}
@ -219,7 +217,6 @@ class SettingsCommonActivity : AppCompatActivity() {
BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity)
biometricPrompt.authenticate(promptInfo)
}
} else {
PrefManager.setVal(PrefName.BiometricToken, "")
toast(R.string.success)
@ -239,7 +236,7 @@ class SettingsCommonActivity : AppCompatActivity() {
view.passwordInput.requestFocus()
val canAuthenticate =
BiometricManager.from(applicationContext).canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_WEAK
BiometricManager.Authenticators.BIOMETRIC_WEAK,
) == BiometricManager.BIOMETRIC_SUCCESS
view.biometricCheckbox.isVisible = canAuthenticate
view.biometricCheckbox.isChecked =
@ -249,8 +246,7 @@ class SettingsCommonActivity : AppCompatActivity() {
}
show()
}
}
},
),
Settings(
type = 1,
@ -259,24 +255,26 @@ class SettingsCommonActivity : AppCompatActivity() {
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(
val selectedArray = BooleanArray(filteredLocations.size) { false }
context.customAlertDialog().apply {
setTitle(R.string.backup_restore)
multiChoiceItems(
filteredLocations.map { it.name }.toTypedArray(),
selectedArray.toBooleanArray()
) { _, which, isChecked ->
selectedArray[which] = isChecked
}.setPositiveButton(R.string.button_restore) { dialog, _ ->
selectedArray,
) { updatedSelection ->
for (i in updatedSelection.indices) {
selectedArray[i] = updatedSelection[i]
}
}
setPosButton(R.string.button_restore) {
openDocumentLauncher.launch(arrayOf("*/*"))
dialog.dismiss()
}.setNegativeButton(R.string.button_backup) { dialog, _ ->
}
setNegButton(R.string.button_backup) {
if (!selectedArray.contains(true)) {
toast(R.string.no_location_selected)
return@setNegativeButton
return@setNegButton
}
dialog.dismiss()
val selected =
filteredLocations.filterIndexed { index, _ -> selectedArray[index] }
if (selected.contains(Location.Protected)) {
@ -286,7 +284,7 @@ class SettingsCommonActivity : AppCompatActivity() {
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
password
password,
)
} else {
toast(R.string.password_cannot_be_empty)
@ -297,14 +295,13 @@ class SettingsCommonActivity : AppCompatActivity() {
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
null
null,
)
}
}.setNeutralButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
}.create()
dialog.window?.setDimAmount(0.8f)
dialog.show()
}
setNeutralButton(R.string.cancel) {}
show()
}
},
),
Settings(
@ -325,7 +322,9 @@ class SettingsCommonActivity : AppCompatActivity() {
PrefManager.getVal<String>(PrefName.DownloadsDir)
GlobalScope.launch(Dispatchers.IO) {
Injekt.get<DownloadsManager>().moveDownloadsDir(
context, Uri.parse(oldUri), Uri.parse(newUri)
context,
Uri.parse(oldUri),
Uri.parse(newUri),
) { finished, message ->
if (finished) {
toast(getString(R.string.success))
@ -343,7 +342,7 @@ class SettingsCommonActivity : AppCompatActivity() {
setNegButton(R.string.cancel)
show()
}
}
},
),
Settings(
type = 2,
@ -353,7 +352,7 @@ class SettingsCommonActivity : AppCompatActivity() {
isChecked = PrefManager.getVal(PrefName.ContinueMedia),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.ContinueMedia, isChecked)
}
},
),
Settings(
type = 2,
@ -364,7 +363,7 @@ class SettingsCommonActivity : AppCompatActivity() {
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.HidePrivate, isChecked)
restartApp()
}
},
),
Settings(
type = 2,
@ -374,7 +373,7 @@ class SettingsCommonActivity : AppCompatActivity() {
isChecked = PrefManager.getVal(PrefName.SearchSources),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.SearchSources, isChecked)
}
},
),
Settings(
type = 2,
@ -384,7 +383,7 @@ class SettingsCommonActivity : AppCompatActivity() {
isChecked = PrefManager.getVal(PrefName.RecentlyListOnly),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.RecentlyListOnly, isChecked)
}
},
),
Settings(
type = 2,
@ -396,23 +395,27 @@ class SettingsCommonActivity : AppCompatActivity() {
PrefManager.setVal(PrefName.AdultOnly, isChecked)
restartApp()
},
isVisible = Anilist.adult
isVisible = Anilist.adult,
),
),
)
)
settingsRecyclerView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
setHasFixedSize(true)
}
var previousStart: View = when (PrefManager.getVal<Int>(PrefName.DefaultStartUpTab)) {
var previousStart: View =
when (PrefManager.getVal<Int>(PrefName.DefaultStartUpTab)) {
0 -> uiSettingsAnime
1 -> uiSettingsHome
2 -> uiSettingsManga
else -> uiSettingsHome
}
previousStart.alpha = 1f
fun uiDefault(mode: Int, current: View) {
fun uiDefault(
mode: Int,
current: View,
) {
previousStart.alpha = 0.33f
previousStart = current
current.alpha = 1f
@ -431,11 +434,13 @@ class SettingsCommonActivity : AppCompatActivity() {
uiSettingsManga.setOnClickListener {
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') }
// Inflate the dialog layout
@ -445,7 +450,9 @@ class SettingsCommonActivity : AppCompatActivity() {
box.setSingleLine()
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)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel) { dialog, _ ->
@ -457,7 +464,10 @@ class SettingsCommonActivity : AppCompatActivity() {
fun handleOkAction() {
val editText = dialogView.userAgentTextBox
if (editText.text?.isNotBlank() == true) {
editText.text?.toString()?.trim()?.toCharArray(password)
editText.text
?.toString()
?.trim()
?.toCharArray(password)
dialog.dismiss()
callback(password)
} else {
@ -473,18 +483,20 @@ class SettingsCommonActivity : AppCompatActivity() {
}
}
dialogView.subtitle.visibility = View.VISIBLE
if (!isExporting) dialogView.subtitle.text =
if (!isExporting) {
dialogView.subtitle.text =
getString(R.string.enter_password_to_decrypt_file)
}
dialog.window?.setDimAmount(0.8f)
dialog.window?.apply {
setDimAmount(0.8f)
attributes.windowAnimations = android.R.style.Animation_Dialog
}
dialog.show()
// Override the positive button here
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
handleOkAction()
}
}
}

View file

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

View file

@ -1,47 +1,4 @@
# 3.2.0
# 3.2.1
Features:
1. Searching Users, characters, studios and staff (rebelonion)
2. Better repo adding (rebelonion)
3. Adding repos by online button (rebelonion)
4. More AniList settings (ibo)
5. Copy username and better profile dropdown menu (ibo)
6. More staff info (rebelonion)
7. Progress bar (aayush262)
8. Toggleable RPC (Sadwhy)
9. Smooth theme transitions (Sadwhy)
10. Toggleable Comments (Sadwhy)
11. Added button to view rules (Ankit Grai)
12. Added additional codec support (Sadwhy)
13. Socks5 proxy support (Sadwhy)
14. Added anime clear progress (Ankit Grai)
15. Custom subtitle view (Sadwhy)
16. Color picker for subtitles (Sadwhy)
17. Added option to select preferred language for subs (tutel)
18. Animated dantotsu icon (aayush262)
Fixes:
1. Performance improvements (rebelonion)
2. Start keyboard expanded in some views (rebelonion)
3. many scanlators not showing (rebelonion)
4. StaffNameLanguage crash (ibo)
5. Remove unneeded logs (Toby Bridle)
6. Thumbnails (aayush262)
7. No images in RPC (aayush262)
8. Scroll to next chapter for single page manga (Ankit Grai)
9. Local dev problem with injekt dependency (Ankit Grai)
10. Swipy (Dawn-used-yeet)
11. Fix crash for EbookReaderView (Vipul Tyagi)
12. Markdown fixes (rebelonion)
13. Deletion queries (Toby Bridle)
14. Manga not reordering automatically (aayush262)
15. Manga crash (aayush262)
16. Kotlin issues with SDK 35 (Sadwhy)
17. Null story list for some users (aayush262)
18. Manga RTL (Sadwhy)
19. Markdown options hidden behind keyboard (rebelonion)
20. Move cursor to end after selecting history item (rebelonion)
21. Search history not in correct order (rebelonion)
22. book files not being opened (rebelonion)
A special thanks to all those who helped contribute!
- **Bugfixes:**
- Fix a crash after watching a video