Compare commits

...
Sign in to create a new pull request.

6 commits
dev ... main

Author SHA1 Message Date
rebel onion
a93b4f5b11 Merge branch 'main' of https://github.com/rebelonion/Dantotsu 2025-05-14 21:40:08 -05:00
rebel onion
69c44b7d20 chore: formatting changes 2025-05-14 21:40:06 -05:00
Rishvaish
a684aac0b1
To install multiple mangas (#582)
users can enter the value required to install as there is an EditText field instead of the Text View
2025-04-02 10:40:39 +05:30
Daniele Santoru
6c49839f87
Fixed missing manga pages when downloading (#586) 2025-04-02 10:39:33 +05:30
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
14 changed files with 178 additions and 120 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

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

View file

@ -50,7 +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(Presence.Response( return json.encodeToString(
Presence.Response(
3, 3,
Presence( Presence(
activities = listOf( activities = listOf(

View file

@ -232,12 +232,18 @@ 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) {
saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap) 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++ farthest++
builder.setProgress(task.imageData.size, farthest, false) builder.setProgress(task.imageData.size, farthest, false)

View file

@ -427,7 +427,8 @@ class ExoplayerView :
false -> 0f false -> 0f
} }
val textElevation = PrefManager.getVal<Float>(PrefName.SubBottomMargin) / 50 * resources.displayMetrics.heightPixels val textElevation =
PrefManager.getVal<Float>(PrefName.SubBottomMargin) / 50 * resources.displayMetrics.heightPixels
textView.translationY = -textElevation textView.translationY = -textElevation
} }
@ -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<View>(R.id.exo_rewind_area) val rewindArea = playerView.findViewById<View>(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<View>(R.id.exo_forward_area) val forwardArea = playerView.findViewById<View>(R.id.exo_forward_area)
@ -1449,7 +1452,8 @@ class ExoplayerView :
else -> mutableListOf() else -> mutableListOf()
} }
val startTimestamp = Calendar.getInstance() 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 = val endTimestamp =
Calendar.getInstance().apply { Calendar.getInstance().apply {
@ -1567,7 +1571,11 @@ class ExoplayerView :
subtitle = intent.getSerialized("subtitle") subtitle = intent.getSerialized("subtitle")
?: when ( ?: when (
val subLang: String? = val subLang: String? =
PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java) PrefManager.getNullableCustomVal(
"subLang_${media.id}",
null,
String::class.java
)
) { ) {
null -> { null -> {
when (episode.selectedSubtitle) { when (episode.selectedSubtitle) {
@ -1575,8 +1583,12 @@ class ExoplayerView :
-1 -> -1 ->
ext.subtitles.find { ext.subtitles.find {
it.language.contains(lang, ignoreCase = true) || 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!!) else -> ext.subtitles.getOrNull(episode.selectedSubtitle!!)
} }
} }
@ -1651,7 +1663,8 @@ class ExoplayerView :
}.build() }.build()
val dataSourceFactory = val dataSourceFactory =
DataSource.Factory { DataSource.Factory {
val dataSource: HttpDataSource = OkHttpDataSource.Factory(httpClient).createDataSource() val dataSource: HttpDataSource =
OkHttpDataSource.Factory(httpClient).createDataSource()
defaultHeaders.forEach { defaultHeaders.forEach {
dataSource.setRequestProperty(it.key, it.value) dataSource.setRequestProperty(it.key, it.value)
} }
@ -1719,12 +1732,14 @@ class ExoplayerView :
it.name?.endsWith(".mp4") == true || it.name?.endsWith(".mp4") == true ||
it.name?.endsWith(".mkv") == true || it.name?.endsWith(".mkv") == true ||
it.name?.endsWith( it.name?.endsWith(
".${Injekt ".${
Injekt
.get<DownloadAddonManager>() .get<DownloadAddonManager>()
.extension .extension
?.extension ?.extension
?.getFileExtension() ?.getFileExtension()
?.first ?: "ts"}", ?.first ?: "ts"
}",
) == ) ==
true true
} }
@ -1928,7 +1943,7 @@ class ExoplayerView :
if (PrefManager.getVal<Boolean>(PrefName.TextviewSubtitles)) { if (PrefManager.getVal<Boolean>(PrefName.TextviewSubtitles)) {
exoSubtitleView.visibility = View.GONE exoSubtitleView.visibility = View.GONE
customSubtitleView.visibility = View.VISIBLE customSubtitleView.visibility = View.VISIBLE
val newCues = cueGroup.cues.map { it.text.toString() ?: "" } val newCues = cueGroup.cues.map { it.text.toString() }
if (newCues.isEmpty()) { if (newCues.isEmpty()) {
customSubtitleView.text = "" customSubtitleView.text = ""
@ -1940,7 +1955,9 @@ class ExoplayerView :
val currentPosition = exoPlayer.currentPosition 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() activeSubtitles.clear()
} }
@ -2213,7 +2230,8 @@ class ExoplayerView :
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
if (new == null) { if (new == null) {
skipTimeButton.visibility = View.GONE skipTimeButton.visibility = View.GONE
exoSkip.isVisible = PrefManager.getVal<Int>(PrefName.SkipTime) > 0 exoSkip.isVisible =
PrefManager.getVal<Int>(PrefName.SkipTime) > 0
disappeared = false disappeared = false
functionstarted = false functionstarted = false
cancelTimer() cancelTimer()
@ -2222,7 +2240,8 @@ class ExoplayerView :
override fun onFinish() { override fun onFinish() {
skipTimeButton.visibility = View.GONE skipTimeButton.visibility = View.GONE
exoSkip.isVisible = PrefManager.getVal<Int>(PrefName.SkipTime) > 0 exoSkip.isVisible =
PrefManager.getVal<Int>(PrefName.SkipTime) > 0
disappeared = true disappeared = true
functionstarted = false functionstarted = false
cancelTimer() cancelTimer()

View file

@ -7,9 +7,11 @@ 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
@ -265,19 +267,22 @@ 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 = NumberPicker(currContext()) val input = View.inflate(currContext(), R.layout.dialog_layout, null)
input.minValue = 1 val editText = input.findViewById<EditText>(R.id.downloadNo)
input.maxValue = 20
input.value = 1
setCustomView(input) setCustomView(input)
setPosButton(R.string.ok) { 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) setNegButton(R.string.cancel)
show() show()
@ -382,8 +387,9 @@ class MangaReadAdapter(
setCustomView(root) setCustomView(root)
setPosButton("OK") { setPosButton("OK") {
if (run) fragment.onIconPressed(style, reversed) if (run) fragment.onIconPressed(style, reversed)
if (downloadNo.text != "0") { val value = downloadNo.text.toString().toIntOrNull()
fragment.multiDownload(downloadNo.text.toString().toInt()) if (value != null && value > 0) {
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 = 2, retries = 25,
simultaneousDownloads = 2 simultaneousDownloads = 2
) )

View file

@ -193,7 +193,8 @@ class SettingsCommonActivity : AppCompatActivity() {
PrefManager.setVal(PrefName.OverridePassword, true) PrefManager.setVal(PrefName.OverridePassword, true)
} }
val password = view.passwordInput.text.toString() val password = view.passwordInput.text.toString()
val confirmPassword = view.confirmPasswordInput.text.toString() val confirmPassword =
view.confirmPasswordInput.text.toString()
if (password == confirmPassword && password.isNotEmpty()) { if (password == confirmPassword && password.isNotEmpty()) {
PrefManager.setVal(PrefName.AppPassword, password) PrefManager.setVal(PrefName.AppPassword, password)
if (view.biometricCheckbox.isChecked) { if (view.biometricCheckbox.isChecked) {
@ -205,7 +206,9 @@ class SettingsCommonActivity : AppCompatActivity() {
if (canBiometricPrompt) { if (canBiometricPrompt) {
val biometricPrompt = val biometricPrompt =
BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ -> BiometricPromptUtils.createBiometricPrompt(
this@SettingsCommonActivity
) { _ ->
val token = UUID.randomUUID().toString() val token = UUID.randomUUID().toString()
PrefManager.setVal( PrefManager.setVal(
PrefName.BiometricToken, PrefName.BiometricToken,
@ -235,12 +238,14 @@ class SettingsCommonActivity : AppCompatActivity() {
setOnShowListener { setOnShowListener {
view.passwordInput.requestFocus() view.passwordInput.requestFocus()
val canAuthenticate = val canAuthenticate =
BiometricManager.from(applicationContext).canAuthenticate( BiometricManager.from(applicationContext)
.canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_WEAK, BiometricManager.Authenticators.BIOMETRIC_WEAK,
) == BiometricManager.BIOMETRIC_SUCCESS ) == BiometricManager.BIOMETRIC_SUCCESS
view.biometricCheckbox.isVisible = canAuthenticate view.biometricCheckbox.isVisible = canAuthenticate
view.biometricCheckbox.isChecked = view.biometricCheckbox.isChecked =
PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty() PrefManager.getVal(PrefName.BiometricToken, "")
.isNotEmpty()
view.forgotPasswordCheckbox.isChecked = view.forgotPasswordCheckbox.isChecked =
PrefManager.getVal(PrefName.OverridePassword) PrefManager.getVal(PrefName.OverridePassword)
} }
@ -314,7 +319,8 @@ class SettingsCommonActivity : AppCompatActivity() {
setTitle(R.string.change_download_location) setTitle(R.string.change_download_location)
setMessage(R.string.download_location_msg) setMessage(R.string.download_location_msg)
setPosButton(R.string.ok) { setPosButton(R.string.ok) {
val oldUri = PrefManager.getVal<String>(PrefName.DownloadsDir) val oldUri =
PrefManager.getVal<String>(PrefName.DownloadsDir)
launcher.registerForCallback { success -> launcher.registerForCallback { success ->
if (success) { if (success) {
toast(getString(R.string.please_wait)) toast(getString(R.string.please_wait))

View file

@ -82,9 +82,18 @@ 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(R.string.subscriptions_checking_time_s, timeNames[i]) it.settingsTitle.text = getString(
PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime) R.string.subscriptions_checking_time_s,
TaskScheduler.create(context, PrefManager.getVal(PrefName.UseAlarmManager)).scheduleAllTasks(context) timeNames[i]
)
PrefManager.setVal(
PrefName.SubscriptionNotificationInterval,
curTime
)
TaskScheduler.create(
context,
PrefManager.getVal(PrefName.UseAlarmManager)
).scheduleAllTasks(context)
} }
show() show()
} }
@ -125,7 +134,8 @@ class SettingsNotificationActivity : AppCompatActivity() {
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 -> ) { updatedSelected ->
types.forEachIndexed { index, type -> types.forEachIndexed { index, type ->

View file

@ -96,7 +96,8 @@ class SettingsThemeActivity : AppCompatActivity(), SimpleDialog.OnDialogResultLi
themeSwitcher.apply { themeSwitcher.apply {
setText(themeText) setText(themeText)
setAdapter( setAdapter(
ArrayAdapter(context, ArrayAdapter(
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,7 +52,8 @@ class SubscriptionsBottomDialog : BottomSheetDialogFragment() {
} }
groupedSubscriptions.forEach { (parserName, mediaList) -> groupedSubscriptions.forEach { (parserName, mediaList) ->
adapter.add(SubscriptionSource( adapter.add(
SubscriptionSource(
parserName, parserName,
mediaList.toMutableList(), mediaList.toMutableList(),
adapter, adapter,

View file

@ -8,7 +8,7 @@
android:padding="16dp"> android:padding="16dp">
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="326dp"
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="265dp" android:layout_width="263dp"
android:layout_height="match_parent" android:layout_height="60dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -171,14 +171,23 @@
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:text="@string/download" /> android:text="@string/download" />
<TextView <EditText
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
@ -191,7 +200,7 @@
<ImageButton <ImageButton
android:id="@+id/mediaDownloadTop" android:id="@+id/mediaDownloadTop"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="60dp"
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"
@ -313,9 +322,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.7.3' classpath 'com.android.tools.build:gradle:8.9.0'
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.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists