diff --git a/README.md b/README.md index 76bece4b..6b713a2a 100644 --- a/README.md +++ b/README.md @@ -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! - + ## Terms of Use By downloading, installing, or using this application, you agree to: diff --git a/app/build.gradle b/app/build.gradle index f7aab7ca..0604d87e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,9 +17,8 @@ android { applicationId "ani.dantotsu" minSdk 21 targetSdk 35 - versionCode((System.currentTimeMillis() / 60000).toInteger()) - versionName "3.2.1" - versionCode 300200100 + versionName "3.2.2" + versionCode 300200200 signingConfig signingConfigs.debug } diff --git a/app/src/main/java/ani/dantotsu/connections/discord/RPC.kt b/app/src/main/java/ani/dantotsu/connections/discord/RPC.kt index 20bafb39..eec99b96 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/RPC.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/RPC.kt @@ -50,8 +50,9 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) { val assetApi = RPCExternalAsset(data.applicationId, token!!, client, json) suspend fun String.discordUrl() = assetApi.getDiscordUri(this) - return json.encodeToString(Presence.Response( - 3, + return json.encodeToString( + Presence.Response( + 3, Presence( activities = listOf( Activity( diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt index 58e7de38..2ea82108 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -232,12 +232,18 @@ class MangaDownloaderService : Service() { image.page, image.source ) + if (bitmap == null) { + snackString("${task.chapter} - Retrying to download page ${index.ofLength(3)}, attempt ${retryCount + 1}.") + } retryCount++ } - if (bitmap != null) { - saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap) + if (bitmap == null) { + outputDir.deleteRecursively(this@MangaDownloaderService, false) + throw Exception("${task.chapter} - Unable to download all pages after $retryCount attempts. Try again.") } + + saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap) farthest++ builder.setProgress(task.imageData.size, farthest, false) diff --git a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt index 465949a4..4de053dd 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt @@ -427,7 +427,8 @@ class ExoplayerView : false -> 0f } - val textElevation = PrefManager.getVal(PrefName.SubBottomMargin) / 50 * resources.displayMetrics.heightPixels + val textElevation = + PrefManager.getVal(PrefName.SubBottomMargin) / 50 * resources.displayMetrics.heightPixels textView.translationY = -textElevation } @@ -606,9 +607,9 @@ class ExoplayerView : if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { pipEnabled = packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && - PrefManager.getVal( - PrefName.Pip, - ) + PrefManager.getVal( + PrefName.Pip, + ) if (pipEnabled) { exoPip.visibility = View.VISIBLE exoPip.setOnClickListener { @@ -1044,7 +1045,8 @@ class ExoplayerView : } } - override fun onSingleClick(event: MotionEvent) = if (isSeeking) doubleTap(false, event) else handleController() + override fun onSingleClick(event: MotionEvent) = + if (isSeeking) doubleTap(false, event) else handleController() }, ) val rewindArea = playerView.findViewById(R.id.exo_rewind_area) @@ -1079,7 +1081,8 @@ class ExoplayerView : } } - override fun onSingleClick(event: MotionEvent) = if (isSeeking) doubleTap(true, event) else handleController() + override fun onSingleClick(event: MotionEvent) = + if (isSeeking) doubleTap(true, event) else handleController() }, ) val forwardArea = playerView.findViewById(R.id.exo_forward_area) @@ -1449,7 +1452,8 @@ class ExoplayerView : else -> mutableListOf() } val startTimestamp = Calendar.getInstance() - val durationInSeconds = if (exoPlayer.duration != C.TIME_UNSET) (exoPlayer.duration / 1000).toInt() else 1440 + val durationInSeconds = + if (exoPlayer.duration != C.TIME_UNSET) (exoPlayer.duration / 1000).toInt() else 1440 val endTimestamp = Calendar.getInstance().apply { @@ -1502,12 +1506,12 @@ class ExoplayerView : @Suppress("UNCHECKED_CAST") val list = ( - PrefManager.getNullableCustomVal( - "continueAnimeList", - listOf(), - List::class.java, - ) as List - ).toMutableList() + PrefManager.getNullableCustomVal( + "continueAnimeList", + listOf(), + List::class.java, + ) as List + ).toMutableList() if (list.contains(media.id)) list.remove(media.id) list.add(media.id) PrefManager.setCustomVal("continueAnimeList", list) @@ -1567,7 +1571,11 @@ class ExoplayerView : subtitle = intent.getSerialized("subtitle") ?: when ( val subLang: String? = - PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java) + PrefManager.getNullableCustomVal( + "subLang_${media.id}", + null, + String::class.java + ) ) { null -> { when (episode.selectedSubtitle) { @@ -1575,8 +1583,12 @@ class ExoplayerView : -1 -> ext.subtitles.find { it.language.contains(lang, ignoreCase = true) || - it.language.contains(getLanguageCode(lang), ignoreCase = true) + it.language.contains( + getLanguageCode(lang), + ignoreCase = true + ) } + else -> ext.subtitles.getOrNull(episode.selectedSubtitle!!) } } @@ -1651,7 +1663,8 @@ class ExoplayerView : }.build() val dataSourceFactory = DataSource.Factory { - val dataSource: HttpDataSource = OkHttpDataSource.Factory(httpClient).createDataSource() + val dataSource: HttpDataSource = + OkHttpDataSource.Factory(httpClient).createDataSource() defaultHeaders.forEach { dataSource.setRequestProperty(it.key, it.value) } @@ -1717,16 +1730,18 @@ class ExoplayerView : val docFile = directory.listFiles().firstOrNull { it.name?.endsWith(".mp4") == true || - it.name?.endsWith(".mkv") == true || - it.name?.endsWith( - ".${Injekt - .get() - .extension - ?.extension - ?.getFileExtension() - ?.first ?: "ts"}", - ) == - true + it.name?.endsWith(".mkv") == true || + it.name?.endsWith( + ".${ + Injekt + .get() + .extension + ?.extension + ?.getFileExtension() + ?.first ?: "ts" + }", + ) == + true } if (docFile != null) { val uri = docFile.uri @@ -1840,30 +1855,30 @@ class ExoplayerView : "%02d:%02d:%02d", TimeUnit.MILLISECONDS.toHours(playbackPosition), TimeUnit.MILLISECONDS.toMinutes(playbackPosition) - - TimeUnit.HOURS.toMinutes( - TimeUnit.MILLISECONDS.toHours( - playbackPosition, + TimeUnit.HOURS.toMinutes( + TimeUnit.MILLISECONDS.toHours( + playbackPosition, + ), ), - ), TimeUnit.MILLISECONDS.toSeconds(playbackPosition) - - TimeUnit.MINUTES.toSeconds( - TimeUnit.MILLISECONDS.toMinutes( - playbackPosition, + TimeUnit.MINUTES.toSeconds( + TimeUnit.MILLISECONDS.toMinutes( + playbackPosition, + ), ), - ), ) - customAlertDialog().apply { - setTitle(getString(R.string.continue_from, time)) - setCancelable(false) - setPosButton(getString(R.string.yes)) { - buildExoplayer() - } - setNegButton(getString(R.string.no)) { - playbackPosition = 0L - buildExoplayer() - } - show() + customAlertDialog().apply { + setTitle(getString(R.string.continue_from, time)) + setCancelable(false) + setPosButton(getString(R.string.yes)) { + buildExoplayer() } + setNegButton(getString(R.string.no)) { + playbackPosition = 0L + buildExoplayer() + } + show() + } } else { buildExoplayer() } @@ -1928,7 +1943,7 @@ class ExoplayerView : if (PrefManager.getVal(PrefName.TextviewSubtitles)) { exoSubtitleView.visibility = View.GONE customSubtitleView.visibility = View.VISIBLE - val newCues = cueGroup.cues.map { it.text.toString() ?: "" } + val newCues = cueGroup.cues.map { it.text.toString() } if (newCues.isEmpty()) { customSubtitleView.text = "" @@ -1940,7 +1955,9 @@ class ExoplayerView : val currentPosition = exoPlayer.currentPosition - if ((lastSubtitle?.length ?: 0) < 20 || (lastPosition != 0L && currentPosition - lastPosition > 1500)) { + if ((lastSubtitle?.length + ?: 0) < 20 || (lastPosition != 0L && currentPosition - lastPosition > 1500) + ) { activeSubtitles.clear() } @@ -2187,7 +2204,7 @@ class ExoplayerView : currentTimeStamp = model.timeStamps.value?.find { timestamp -> timestamp.interval.startTime < playerCurrentTime && - playerCurrentTime < (timestamp.interval.endTime - 1) + playerCurrentTime < (timestamp.interval.endTime - 1) } val new = currentTimeStamp @@ -2213,7 +2230,8 @@ class ExoplayerView : override fun onTick(millisUntilFinished: Long) { if (new == null) { skipTimeButton.visibility = View.GONE - exoSkip.isVisible = PrefManager.getVal(PrefName.SkipTime) > 0 + exoSkip.isVisible = + PrefManager.getVal(PrefName.SkipTime) > 0 disappeared = false functionstarted = false cancelTimer() @@ -2222,7 +2240,8 @@ class ExoplayerView : override fun onFinish() { skipTimeButton.visibility = View.GONE - exoSkip.isVisible = PrefManager.getVal(PrefName.SkipTime) > 0 + exoSkip.isVisible = + PrefManager.getVal(PrefName.SkipTime) > 0 disappeared = true functionstarted = false cancelTimer() @@ -2310,7 +2329,7 @@ class ExoplayerView : tracks.groups.forEach { println( "Track__: $it\nTrack__: ${it.length}\nTrack__: ${it.isSelected}\n" + - "Track__: ${it.type}\nTrack__: ${it.mediaTrackGroup.id}", + "Track__: ${it.type}\nTrack__: ${it.mediaTrackGroup.id}", ) when (it.type) { TRACK_TYPE_AUDIO -> { @@ -2365,7 +2384,7 @@ class ExoplayerView : when (error.errorCode) { PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, - -> { + -> { toast("Source Exception : ${error.message}") isPlayerPlaying = true sourceClick() @@ -2403,9 +2422,9 @@ class ExoplayerView : val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) val episodeEnd = exoPlayer.currentPosition / episodeLength > - PrefManager.getVal( - PrefName.WatchPercentage, - ) + PrefManager.getVal( + PrefName.WatchPercentage, + ) val episode0 = currentEpisodeIndex == 0 && PrefManager.getVal(PrefName.ChapterZeroPlayer) if (!incognito && (episodeEnd || episode0) && Anilist.userid != null ) { diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index a76d44a4..ddf98974 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -7,9 +7,11 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.CheckBox +import android.widget.EditText import android.widget.ImageButton import android.widget.LinearLayout import android.widget.NumberPicker +import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getString import androidx.core.content.ContextCompat.startActivity @@ -265,19 +267,22 @@ class MangaReadAdapter( } // Multi download - downloadNo.text = "0" + //downloadNo.text = "0" mediaDownloadTop.setOnClickListener { - // Alert dialog asking for the number of chapters to download fragment.requireContext().customAlertDialog().apply { setTitle("Multi Chapter Downloader") setMessage("Enter the number of chapters to download") - val input = NumberPicker(currContext()) - input.minValue = 1 - input.maxValue = 20 - input.value = 1 + val input = View.inflate(currContext(), R.layout.dialog_layout, null) + val editText = input.findViewById(R.id.downloadNo) setCustomView(input) setPosButton(R.string.ok) { - downloadNo.text = "${input.value}" + val value = editText.text.toString().toIntOrNull() + 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) show() @@ -382,8 +387,9 @@ class MangaReadAdapter( setCustomView(root) setPosButton("OK") { if (run) fragment.onIconPressed(style, reversed) - if (downloadNo.text != "0") { - fragment.multiDownload(downloadNo.text.toString().toInt()) + val value = downloadNo.text.toString().toIntOrNull() + if (value != null && value > 0) { + fragment.multiDownload(value) } if (refresh) fragment.loadChapters(source, true) } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index 6b7eb6bc..63ef7408 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -474,7 +474,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { scanlator = chapter.scanlator ?: "Unknown", imageData = images, sourceMedia = media, - retries = 2, + retries = 25, simultaneousDownloads = 2 ) diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt index 38230a86..f0bf91d0 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt @@ -193,7 +193,8 @@ class SettingsCommonActivity : AppCompatActivity() { PrefManager.setVal(PrefName.OverridePassword, true) } val password = view.passwordInput.text.toString() - val confirmPassword = view.confirmPasswordInput.text.toString() + val confirmPassword = + view.confirmPasswordInput.text.toString() if (password == confirmPassword && password.isNotEmpty()) { PrefManager.setVal(PrefName.AppPassword, password) if (view.biometricCheckbox.isChecked) { @@ -201,11 +202,13 @@ class SettingsCommonActivity : AppCompatActivity() { BiometricManager .from(applicationContext) .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == - BiometricManager.BIOMETRIC_SUCCESS + BiometricManager.BIOMETRIC_SUCCESS if (canBiometricPrompt) { val biometricPrompt = - BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ -> + BiometricPromptUtils.createBiometricPrompt( + this@SettingsCommonActivity + ) { _ -> val token = UUID.randomUUID().toString() PrefManager.setVal( PrefName.BiometricToken, @@ -235,12 +238,14 @@ class SettingsCommonActivity : AppCompatActivity() { setOnShowListener { view.passwordInput.requestFocus() val canAuthenticate = - BiometricManager.from(applicationContext).canAuthenticate( - BiometricManager.Authenticators.BIOMETRIC_WEAK, - ) == BiometricManager.BIOMETRIC_SUCCESS + BiometricManager.from(applicationContext) + .canAuthenticate( + BiometricManager.Authenticators.BIOMETRIC_WEAK, + ) == BiometricManager.BIOMETRIC_SUCCESS view.biometricCheckbox.isVisible = canAuthenticate view.biometricCheckbox.isChecked = - PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty() + PrefManager.getVal(PrefName.BiometricToken, "") + .isNotEmpty() view.forgotPasswordCheckbox.isChecked = PrefManager.getVal(PrefName.OverridePassword) } @@ -314,7 +319,8 @@ class SettingsCommonActivity : AppCompatActivity() { setTitle(R.string.change_download_location) setMessage(R.string.download_location_msg) setPosButton(R.string.ok) { - val oldUri = PrefManager.getVal(PrefName.DownloadsDir) + val oldUri = + PrefManager.getVal(PrefName.DownloadsDir) launcher.registerForCallback { success -> if (success) { toast(getString(R.string.please_wait)) diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt index c5ca440a..f1225ed4 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt @@ -82,9 +82,18 @@ 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() } @@ -120,21 +129,22 @@ class SettingsNotificationActivity : AppCompatActivity() { .toMutableSet() val selected = types.map { filteredTypes.contains(it) }.toBooleanArray() context.customAlertDialog().apply { - setTitle(R.string.anilist_notification_filters) - multiChoiceItems( + 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(), + if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() + } + }.toTypedArray(), selected ) { updatedSelected -> - types.forEachIndexed { index, type -> - if (updatedSelected[index]) { - filteredTypes.add(type) - } else { - filteredTypes.remove(type) + types.forEachIndexed { index, type -> + if (updatedSelected[index]) { + filteredTypes.add(type) + } else { + filteredTypes.remove(type) + } } - } PrefManager.setVal(PrefName.AnilistFilteredTypes, filteredTypes) } show() @@ -152,8 +162,8 @@ class SettingsNotificationActivity : AppCompatActivity() { icon = R.drawable.ic_round_notifications_none_24, onClick = { context.customAlertDialog().apply { - setTitle(R.string.subscriptions_checking_time) - singleChoiceItems( + setTitle(R.string.subscriptions_checking_time) + singleChoiceItems( aItems.toTypedArray(), PrefManager.getVal(PrefName.AnilistNotificationInterval) ) { i -> @@ -181,11 +191,11 @@ class SettingsNotificationActivity : AppCompatActivity() { icon = R.drawable.ic_round_notifications_none_24, onClick = { context.customAlertDialog().apply { - setTitle(R.string.subscriptions_checking_time) - singleChoiceItems( + setTitle(R.string.subscriptions_checking_time) + singleChoiceItems( cItems.toTypedArray(), PrefManager.getVal(PrefName.CommentNotificationInterval) - ) { i -> + ) { i -> PrefManager.setVal(PrefName.CommentNotificationInterval, i) it.settingsTitle.text = getString( @@ -225,9 +235,9 @@ class SettingsNotificationActivity : AppCompatActivity() { switch = { isChecked, view -> if (isChecked) { context.customAlertDialog().apply { - setTitle(R.string.use_alarm_manager) - setMessage(R.string.use_alarm_manager_confirm) - setPosButton(R.string.use) { + 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()) { diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsThemeActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsThemeActivity.kt index 8133640a..648c26bd 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsThemeActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsThemeActivity.kt @@ -96,7 +96,8 @@ class SettingsThemeActivity : AppCompatActivity(), SimpleDialog.OnDialogResultLi themeSwitcher.apply { setText(themeText) setAdapter( - ArrayAdapter(context, + ArrayAdapter( + context, R.layout.item_dropdown, ThemeManager.Companion.Theme.entries.map { it.theme.substring( diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt index 9fa7552e..188da9d3 100644 --- a/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt @@ -52,14 +52,15 @@ class SubscriptionsBottomDialog : BottomSheetDialogFragment() { } groupedSubscriptions.forEach { (parserName, mediaList) -> - adapter.add(SubscriptionSource( - parserName, - mediaList.toMutableList(), - adapter, - getParserIcon(parserName) - ) { group -> - adapter.remove(group) - }) + adapter.add( + SubscriptionSource( + parserName, + mediaList.toMutableList(), + adapter, + getParserIcon(parserName) + ) { group -> + adapter.remove(group) + }) } } diff --git a/app/src/main/res/layout/dialog_layout.xml b/app/src/main/res/layout/dialog_layout.xml index 3db06395..3c9df3b8 100644 --- a/app/src/main/res/layout/dialog_layout.xml +++ b/app/src/main/res/layout/dialog_layout.xml @@ -8,7 +8,7 @@ android:padding="16dp"> @@ -160,8 +160,8 @@ android:orientation="horizontal"> - + tools:text="Number" /> + + + + + + + +