Merge remote-tracking branch 'upstream/master'

This commit is contained in:
jmir1 2021-07-18 19:14:27 +02:00
commit 48c802a294
122 changed files with 1600 additions and 1184 deletions

View file

@ -22,7 +22,6 @@ shortcutHelper.setFilePath("./shortcuts.xml")
android {
compileSdkVersion(AndroidConfig.compileSdk)
buildToolsVersion(AndroidConfig.buildTools)
ndkVersion = AndroidConfig.ndk
defaultConfig {
@ -150,6 +149,7 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.1.0-beta02")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
implementation("androidx.core:core-ktx:1.7.0-alpha01")
implementation("androidx.core:core-splashscreen:1.0.0-alpha01")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.2.1")
@ -247,12 +247,6 @@ dependencies {
implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0")
implementation("dev.chrisbanes.insetter:insetter:0.6.0")
// 3.2.0+ introduces weird UI blinking or cut off issues on some devices
val materialDialogsVersion = "3.1.1"
implementation("com.afollestad.material-dialogs:core:$materialDialogsVersion")
implementation("com.afollestad.material-dialogs:input:$materialDialogsVersion")
implementation("com.afollestad.material-dialogs:datetime:$materialDialogsVersion")
// Conductor
val conductorVersion = "3.0.0"
implementation("com.bluelinelabs:conductor:$conductorVersion")

View file

@ -37,7 +37,7 @@
<activity
android:name=".ui.main.MainActivity"
android:launchMode="singleTop"
android:theme="@style/Theme.Splash">
android:theme="@style/Theme.Tachiyomi.SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View file

@ -74,7 +74,7 @@ class AnimelibUpdateNotifier(private val context: Context) {
.setContentTitle(context.getString(R.string.notification_check_updates))
.setContentText("($current/$total)")
} else {
val updatingText = anime.joinToString("\n") { it.title }
val updatingText = anime.joinToString("\n") { it.title.chop(40) }
progressNotificationBuilder
.setContentTitle(context.getString(R.string.notification_updating, current, total))
.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))

View file

@ -295,7 +295,6 @@ class AnimelibUpdateService(
}
currentlyUpdatingAnime.add(anime)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingAnime,
progressCount.get(),
@ -328,6 +327,7 @@ class AnimelibUpdateService(
}
currentlyUpdatingAnime.remove(anime)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingAnime,
progressCount.get(),
@ -416,7 +416,6 @@ class AnimelibUpdateService(
}
currentlyUpdatingAnime.add(anime)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingAnime,
progressCount.get(),
@ -440,6 +439,7 @@ class AnimelibUpdateService(
}
currentlyUpdatingAnime.remove(anime)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingAnime,
progressCount.get(),

View file

@ -74,7 +74,7 @@ class LibraryUpdateNotifier(private val context: Context) {
.setContentTitle(context.getString(R.string.notification_check_updates))
.setContentText("($current/$total)")
} else {
val updatingText = manga.joinToString("\n") { it.title }
val updatingText = manga.joinToString("\n") { it.title.chop(40) }
progressNotificationBuilder
.setContentTitle(context.getString(R.string.notification_updating, current, total))
.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))

View file

@ -295,7 +295,6 @@ class LibraryUpdateService(
}
currentlyUpdatingManga.add(manga)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingManga,
progressCount.get(),
@ -328,6 +327,7 @@ class LibraryUpdateService(
}
currentlyUpdatingManga.remove(manga)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingManga,
progressCount.get(),
@ -416,7 +416,6 @@ class LibraryUpdateService(
}
currentlyUpdatingManga.add(manga)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingManga,
progressCount.get(),
@ -440,6 +439,7 @@ class LibraryUpdateService(
}
currentlyUpdatingManga.remove(manga)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingManga,
progressCount.get(),

View file

@ -27,8 +27,9 @@ object PreferenceValues {
GREEN_APPLE(R.string.theme_greenapple),
MIDNIGHT_DUSK(R.string.theme_midnightdusk),
STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
YOTSUBA(R.string.theme_yotsuba),
TAKO(R.string.theme_tako),
YINYANG(R.string.theme_yinyang),
YOTSUBA(R.string.theme_yotsuba),
// Deprecated
DARK_BLUE(null),

View file

@ -40,6 +40,14 @@ abstract class TrackService(val id: Int) {
abstract fun getStatus(status: Int): String
abstract fun getReadingStatus(): Int
abstract fun getWatchingStatus(): Int
abstract fun getRereadingStatus(): Int
abstract fun getRewatchingStatus(): Int
abstract fun getCompletionStatus(): Int
abstract fun getScoreList(): List<String>
@ -52,13 +60,13 @@ abstract class TrackService(val id: Int) {
abstract fun displayScore(track: AnimeTrack): String
abstract suspend fun update(track: Track): Track
abstract suspend fun update(track: Track, didReadChapter: Boolean = false): Track
abstract suspend fun update(track: AnimeTrack): AnimeTrack
abstract suspend fun update(track: AnimeTrack, didReadChapter: Boolean = false): AnimeTrack
abstract suspend fun bind(track: Track): Track
abstract suspend fun bind(track: Track, hasReadChapters: Boolean = false): Track
abstract suspend fun bind(track: AnimeTrack): AnimeTrack
abstract suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean = false): AnimeTrack
abstract suspend fun search(query: String): List<TrackSearch>

View file

@ -82,6 +82,14 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun getReadingStatus(): Int = READING
override fun getWatchingStatus(): Int = WATCHING
override fun getRereadingStatus(): Int = REPEATING
override fun getRewatchingStatus(): Int = REPEATING_ANIME
override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> {
@ -166,7 +174,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
return api.addLibAnime(track)
}
override suspend fun update(track: Track): Track {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
// If user was using API v1 fetch library_id
if (track.library_id == null || track.library_id!! == 0L) {
val libManga = api.findLibManga(track, getUsername().toInt())
@ -174,10 +182,16 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
track.library_id = libManga.library_id
}
if (track.status != COMPLETED) {
if (track.status != REPEATING && didReadChapter) {
track.status = READING
}
}
return api.updateLibManga(track)
}
override suspend fun update(track: AnimeTrack): AnimeTrack {
override suspend fun update(track: AnimeTrack, didReadChapter: Boolean): AnimeTrack {
// If user was using API v1 fetch library_id
if (track.library_id == null || track.library_id!! == 0L) {
val libManga = api.findLibAnime(track, getUsername().toInt())
@ -188,21 +202,27 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
return api.updateLibAnime(track)
}
override suspend fun bind(track: Track): Track {
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
val remoteTrack = api.findLibManga(track, getUsername().toInt())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
if (track.status != COMPLETED) {
val isRereading = track.status == REPEATING
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
}
update(track)
} else {
// Set default fields if it's not found in the list
track.status = READING
track.status = if (hasReadChapters) READING else PLANNING
track.score = 0F
add(track)
}
}
override suspend fun bind(track: AnimeTrack): AnimeTrack {
override suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean): AnimeTrack {
val remoteTrack = api.findLibAnime(track, getUsername().toInt())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)

View file

@ -2,9 +2,6 @@ package eu.kanade.tachiyomi.data.track.anilist
import android.net.Uri
import androidx.core.net.toUri
import com.afollestad.date.dayOfMonth
import com.afollestad.date.month
import com.afollestad.date.year
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
@ -531,9 +528,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
val calendar = Calendar.getInstance()
calendar.timeInMillis = dateValue
return buildJsonObject {
put("year", calendar.year)
put("month", calendar.month + 1)
put("day", calendar.dayOfMonth)
put("year", calendar.get(Calendar.YEAR))
put("month", calendar.get(Calendar.MONTH) + 1)
put("day", calendar.get(Calendar.DAY_OF_MONTH))
}
}

View file

@ -45,40 +45,61 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
return api.addLibAnime(track)
}
override suspend fun update(track: Track): Track {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (didReadChapter) {
track.status = READING
}
}
return api.updateLibManga(track)
}
override suspend fun update(track: AnimeTrack): AnimeTrack {
override suspend fun update(track: AnimeTrack, didReadChapter: Boolean): AnimeTrack {
if (track.status != COMPLETED) {
if (didReadChapter) {
track.status = READING
}
}
return api.updateLibAnime(track)
}
override suspend fun bind(track: Track): Track {
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
val statusTrack = api.statusLibManga(track)
val remoteTrack = api.findLibManga(track)
return if (remoteTrack != null && statusTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
track.status = statusTrack.status
if (track.status != COMPLETED) {
track.status = if (hasReadChapters) READING else statusTrack.status
}
track.score = statusTrack.score
track.last_chapter_read = statusTrack.last_chapter_read
track.total_chapters = remoteTrack.total_chapters
refresh(track)
} else {
// Set default fields if it's not found in the list
track.status = READING
track.status = if (hasReadChapters) READING else PLANNING
track.score = 0F
add(track)
update(track)
}
}
override suspend fun bind(track: AnimeTrack): AnimeTrack {
override suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean): AnimeTrack {
val statusTrack = api.statusLibAnime(track)
val remoteTrack = api.findLibAnime(track)
return if (remoteTrack != null && statusTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
if (track.status != COMPLETED) {
track.status = if (hasReadChapters) READING else statusTrack.status
}
track.status = statusTrack.status
track.score = statusTrack.score
track.last_episode_seen = statusTrack.last_episode_seen
@ -86,7 +107,7 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
refresh(track)
} else {
// Set default fields if it's not found in the list
track.status = READING
track.status = if (hasReadChapters) READING else PLANNING
track.score = 0F
add(track)
update(track)
@ -142,6 +163,14 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun getReadingStatus(): Int = READING
override fun getWatchingStatus(): Int = READING
override fun getRereadingStatus(): Int = -1
override fun getRewatchingStatus(): Int = -1
override fun getCompletionStatus(): Int = COMPLETED
override suspend fun login(username: String, password: String) = login(password)

View file

@ -61,6 +61,14 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun getReadingStatus(): Int = READING
override fun getWatchingStatus(): Int = WATCHING
override fun getRereadingStatus(): Int = -1
override fun getRewatchingStatus(): Int = -1
override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> {
@ -90,35 +98,57 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
return api.addLibAnime(track, getUserId())
}
override suspend fun update(track: Track): Track {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (didReadChapter) {
track.status = READING
}
}
return api.updateLibManga(track)
}
override suspend fun update(track: AnimeTrack): AnimeTrack {
override suspend fun update(track: AnimeTrack, didReadChapter: Boolean): AnimeTrack {
if (track.status != COMPLETED) {
if (didReadChapter) {
track.status = WATCHING
}
}
return api.updateLibAnime(track)
}
override suspend fun bind(track: Track): Track {
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
val remoteTrack = api.findLibManga(track, getUserId())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.media_id = remoteTrack.media_id
if (track.status != COMPLETED) {
track.status = if (hasReadChapters) READING else track.status
}
update(track)
} else {
track.status = READING
track.status = if (hasReadChapters) READING else PLAN_TO_READ
track.score = 0F
add(track)
}
}
override suspend fun bind(track: AnimeTrack): AnimeTrack {
override suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean): AnimeTrack {
val remoteTrack = api.findLibAnime(track, getUserId())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.media_id = remoteTrack.media_id
if (track.status != COMPLETED) {
track.status = if (hasReadChapters) WATCHING else track.status
}
update(track)
} else {
track.status = WATCHING
track.status = if (hasReadChapters) WATCHING else PLAN_TO_WATCH
track.score = 0F
add(track)
}

View file

@ -55,6 +55,14 @@ class Komga(private val context: Context, id: Int) : TrackService(id), Unattende
}
}
override fun getReadingStatus(): Int = READING
override fun getWatchingStatus(): Int = throw Exception("Not used")
override fun getRereadingStatus(): Int = -1
override fun getRewatchingStatus(): Int = throw Exception("Not used")
override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> = emptyList()
@ -62,17 +70,23 @@ class Komga(private val context: Context, id: Int) : TrackService(id), Unattende
override fun displayScore(track: Track): String = ""
override fun displayScore(track: AnimeTrack): String = throw Exception("Not used")
override suspend fun update(track: Track): Track {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (didReadChapter) {
track.status = READING
}
}
return api.updateProgress(track)
}
override suspend fun update(track: AnimeTrack): AnimeTrack = throw Exception("Not used")
override suspend fun update(track: AnimeTrack, didReadChapter: Boolean): AnimeTrack = throw Exception("Not used")
override suspend fun bind(track: Track): Track {
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
return track
}
override suspend fun bind(track: AnimeTrack): AnimeTrack = throw Exception("Not used")
override suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean): AnimeTrack = throw Exception("Not used")
override suspend fun search(query: String): List<TrackSearch> = throw Exception("Not used")

View file

@ -68,6 +68,14 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun getReadingStatus(): Int = READING
override fun getWatchingStatus(): Int = WATCHING
override fun getRereadingStatus(): Int = REREADING
override fun getRewatchingStatus(): Int = REWATCHING
override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> {
@ -83,8 +91,6 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
}
private suspend fun add(track: Track): Track {
track.status = READING
track.score = 0F
return api.updateItem(track)
}
@ -94,32 +100,62 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
return api.updateItem(track)
}
override suspend fun update(track: Track): Track {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (track.status != REREADING && didReadChapter) {
track.status = READING
}
}
return api.updateItem(track)
}
override suspend fun update(track: AnimeTrack): AnimeTrack {
override suspend fun update(track: AnimeTrack, didReadChapter: Boolean): AnimeTrack {
if (track.status != COMPLETED) {
if (track.status != REWATCHING && didReadChapter) {
track.status = WATCHING
}
}
return api.updateItem(track)
}
override suspend fun bind(track: Track): Track {
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
val remoteTrack = api.findListItem(track)
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.media_id = remoteTrack.media_id
if (track.status != COMPLETED) {
val isRereading = track.status == REREADING
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
}
update(track)
} else {
// Set default fields if it's not found in the list
track.status = if (hasReadChapters) READING else PLAN_TO_READ
track.score = 0F
add(track)
}
}
override suspend fun bind(track: AnimeTrack): AnimeTrack {
override suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean): AnimeTrack {
val remoteTrack = api.findListItem(track)
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.media_id = remoteTrack.media_id
if (track.status != COMPLETED) {
val isRereading = track.status == REWATCHING
track.status = if (isRereading.not() && hasReadChapters) WATCHING else track.status
}
update(track)
} else {
// Set default fields if it's not found in the list
track.status = if (hasReadChapters) WATCHING else PLAN_TO_WATCH
track.score = 0F
add(track)
}
}

View file

@ -54,37 +54,61 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
return api.addLibAnime(track, getUsername())
}
override suspend fun update(track: Track): Track {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (track.status != REPEATING && didReadChapter) {
track.status = READING
}
}
return api.updateLibManga(track, getUsername())
}
override suspend fun update(track: AnimeTrack): AnimeTrack {
override suspend fun update(track: AnimeTrack, didReadChapter: Boolean): AnimeTrack {
if (track.status != COMPLETED) {
if (track.status != REPEATING && didReadChapter) {
track.status = READING
}
}
return api.updateLibAnime(track, getUsername())
}
override suspend fun bind(track: Track): Track {
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
val remoteTrack = api.findLibManga(track, getUsername())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
if (track.status != COMPLETED) {
val isRereading = track.status == REPEATING
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
}
update(track)
} else {
// Set default fields if it's not found in the list
track.status = READING
track.status = if (hasReadChapters) READING else PLANNING
track.score = 0F
add(track)
}
}
override suspend fun bind(track: AnimeTrack): AnimeTrack {
override suspend fun bind(track: AnimeTrack, hasReadChapters: Boolean): AnimeTrack {
val remoteTrack = api.findLibAnime(track, getUsername())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
if (track.status != COMPLETED) {
val isRereading = track.status == REPEATING
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
}
update(track)
} else {
// Set default fields if it's not found in the list
track.status = READING
track.status = if (hasReadChapters) READING else PLANNING
track.score = 0F
add(track)
}
@ -138,6 +162,14 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun getReadingStatus(): Int = READING
override fun getWatchingStatus(): Int = READING
override fun getRereadingStatus(): Int = REPEATING
override fun getRewatchingStatus(): Int = REPEATING
override fun getCompletionStatus(): Int = COMPLETED
override suspend fun login(username: String, password: String) = login(password)

View file

@ -306,7 +306,7 @@ class AnimeController :
}
}
trackSheet = TrackSheet(this, anime!!)
trackSheet = TrackSheet(this, anime!!, (activity as MainActivity).supportFragmentManager)
updateFilterIconState()
}
@ -678,7 +678,7 @@ class AnimeController :
}
}
private fun shareCover() {
fun shareCover() {
try {
val activity = activity!!
val cover = presenter.shareCover(activity)
@ -690,7 +690,7 @@ class AnimeController :
}
}
private fun saveCover() {
fun saveCover() {
try {
presenter.saveCover(activity!!)
activity?.toast(R.string.cover_saved)
@ -700,7 +700,7 @@ class AnimeController :
}
}
private fun changeCover() {
fun changeCover() {
val anime = anime ?: return
if (anime.hasCustomCover(coverCache)) {
ChangeAnimeCoverDialog(this, anime).showDialog(router)

View file

@ -815,7 +815,8 @@ class AnimePresenter(
item.anime_id = anime.id!!
launchIO {
try {
service.bind(item)
val hasReadChapters = allEpisodes.any { it.seen }
service.bind(item, hasReadChapters)
db.insertTrack(item).executeAsBlocking()
if (service is UnattendedTrackService) {
@ -866,6 +867,9 @@ class AnimePresenter(
fun setTrackerLastEpisodeSeen(item: TrackItem, episodeNumber: Int) {
val track = item.track!!
if (track.last_episode_seen == 0 && track.last_episode_seen < episodeNumber && track.status != item.service.getRereadingStatus()) {
track.status = item.service.getReadingStatus()
}
track.last_episode_seen = episodeNumber
if (track.total_episodes != 0 && track.last_episode_seen == track.total_episodes) {
track.status = item.service.getCompletionStatus()

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.anime.episode
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -15,12 +15,13 @@ class DeleteEpisodesDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.confirm_delete_episodes)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setMessage(R.string.confirm_delete_chapters)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? Listener)?.deleteEpisodes()
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -3,9 +3,8 @@ package eu.kanade.tachiyomi.ui.anime.episode
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.DialogCustomDownloadView
@ -57,13 +56,14 @@ class DownloadCustomEpisodesDialog<T> : DialogController
// Build dialog.
// when positive dialog is pressed call custom listener.
return MaterialDialog(activity)
.title(R.string.custom_download)
.customView(view = view, scrollable = true)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity)
.setTitle(R.string.custom_download)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? Listener)?.downloadCustomEpisodes(view.amount)
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -3,8 +3,7 @@ package eu.kanade.tachiyomi.ui.anime.episode
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -24,13 +23,10 @@ class SetEpisodeSettingsDialog(bundle: Bundle? = null) : DialogController(bundle
setOptionDescription(R.string.also_set_episode_settings_for_library)
}
return MaterialDialog(activity!!)
.title(R.string.episode_settings)
.customView(
view = view,
horizontalPadding = true
)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.chapter_settings)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
EpisodeSettingsHelper.setGlobalSettings(args.getSerializable(ANIME_KEY)!! as Anime)
if (view.isChecked()) {
EpisodeSettingsHelper.updateAllAnimesWithGlobalDefaults()
@ -38,7 +34,8 @@ class SetEpisodeSettingsDialog(bundle: Bundle? = null) : DialogController(bundle
activity?.toast(activity!!.getString(R.string.episode_settings_updated))
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
private companion object {

View file

@ -6,6 +6,7 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import coil.loadAny
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
@ -177,16 +178,34 @@ class AnimeInfoHeaderAdapter(
binding.mangaCover.longClicks()
.onEach {
controller.activity?.copyToClipboard(
view.context.getString(R.string.title),
controller.presenter.anime.title
)
showCoverOptionsDialog()
}
.launchIn(controller.viewScope)
setAnimeInfo(anime, source)
}
private fun showCoverOptionsDialog() {
val options = listOfNotNull(
R.string.action_share,
R.string.action_save,
// Can only edit cover for library anime
if (anime.favorite) R.string.action_edit else null
).map(controller.activity!!::getString).toTypedArray()
MaterialAlertDialogBuilder(controller.activity!!)
.setTitle(R.string.manga_cover)
.setItems(options) { _, item ->
when (item) {
0 -> controller.shareCover()
1 -> controller.saveCover()
2 -> controller.changeCover()
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
/**
* Update the view with anime information.
*

View file

@ -2,15 +2,14 @@ package eu.kanade.tachiyomi.ui.anime.track
import android.app.Dialog
import android.os.Bundle
import android.widget.NumberPicker
import android.view.LayoutInflater
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.databinding.TrackChaptersDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -38,23 +37,9 @@ class SetTrackEpisodesDialog<T> : DialogController
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val item = item
val pickerView = TrackChaptersDialogBinding.inflate(LayoutInflater.from(activity!!))
val np = pickerView.chaptersPicker
val dialog = MaterialDialog(activity!!)
.title(R.string.episodes)
.customView(R.layout.track_chapters_dialog, dialogWrapContent = false)
.positiveButton(android.R.string.ok) { dialog ->
val view = dialog.getCustomView()
// Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.chapters_picker)
np.clearFocus()
listener.setEpisodesSeen(item, np.value)
}
.negativeButton(android.R.string.cancel)
val view = dialog.getCustomView()
val np: NumberPicker = view.findViewById(R.id.chapters_picker)
// Set initial value
np.value = item.track?.last_episode_seen ?: 0
@ -66,7 +51,15 @@ class SetTrackEpisodesDialog<T> : DialogController
// Don't allow to go from 0 to 9999
np.wrapSelectorWheel = false
return dialog
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.chapters)
.setView(pickerView.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
np.clearFocus()
listener.setEpisodesSeen(item, np.value)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -2,15 +2,14 @@ package eu.kanade.tachiyomi.ui.anime.track
import android.app.Dialog
import android.os.Bundle
import android.widget.NumberPicker
import android.view.LayoutInflater
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.databinding.TrackScoreDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -38,23 +37,9 @@ class SetTrackScoreDialog<T> : DialogController
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val item = item
val pickerView = TrackScoreDialogBinding.inflate(LayoutInflater.from(activity!!))
val np = pickerView.scorePicker
val dialog = MaterialDialog(activity!!)
.title(R.string.score)
.customView(R.layout.track_score_dialog, dialogWrapContent = false)
.positiveButton(android.R.string.ok) { dialog ->
val view = dialog.getCustomView()
// Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.score_picker)
np.clearFocus()
listener.setScore(item, np.value)
}
.negativeButton(android.R.string.cancel)
val view = dialog.getCustomView()
val np: NumberPicker = view.findViewById(R.id.score_picker)
val scores = item.service.getScoreList().toTypedArray()
np.maxValue = scores.size - 1
np.displayedValues = scores
@ -66,7 +51,15 @@ class SetTrackScoreDialog<T> : DialogController
np.value = if (index != -1) index else 0
}
return dialog
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.score)
.setView(pickerView.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
np.clearFocus()
listener.setScore(item, np.value)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -3,9 +3,8 @@ package eu.kanade.tachiyomi.ui.anime.track
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.TrackManager
@ -39,19 +38,18 @@ class SetTrackStatusDialog<T> : DialogController
val item = item
val statusList = item.service.getStatusListAnime()
val statusString = statusList.map { item.service.getStatus(it) }
val selectedIndex = statusList.indexOf(item.track?.status)
var selectedIndex = statusList.indexOf(item.track?.status)
return MaterialDialog(activity!!)
.title(R.string.status)
.negativeButton(android.R.string.cancel)
.listItemsSingleChoice(
items = statusString,
initialSelection = selectedIndex,
waitForPositiveButton = false
) { dialog, position, _ ->
listener.setStatus(item, position)
dialog.dismiss()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.status)
.setSingleChoiceItems(statusString.toTypedArray(), selectedIndex) { _, which ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) { _, _ ->
listener.setStatus(item, selectedIndex)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -1,87 +0,0 @@
package eu.kanade.tachiyomi.ui.anime.track
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.datetime.datePicker
import com.bluelinelabs.conductor.Controller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.AnimeTrack
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Calendar
class SetTrackWatchingDatesDialog<T> : DialogController
where T : Controller {
private val item: TrackItem
private val dateToUpdate: ReadingDate
private lateinit var listener: Listener
constructor(target: T, listener: Listener, dateToUpdate: ReadingDate, item: TrackItem) : super(
bundleOf(KEY_ITEM_TRACK to item.track)
) {
targetController = target
this.listener = listener
this.item = item
this.dateToUpdate = dateToUpdate
}
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
val track = bundle.getSerializable(KEY_ITEM_TRACK) as AnimeTrack
val service = Injekt.get<TrackManager>().getService(track.sync_id)!!
item = TrackItem(track, service)
dateToUpdate = ReadingDate.Start
}
@Suppress("DEPRECATION")
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(
when (dateToUpdate) {
ReadingDate.Start -> R.string.track_started_reading_date
ReadingDate.Finish -> R.string.track_finished_reading_date
}
)
.datePicker(currentDate = getCurrentDate()) { _, date ->
listener.setReadingDate(item, dateToUpdate, date.timeInMillis)
}
.neutralButton(R.string.action_remove) {
listener.setReadingDate(item, dateToUpdate, 0L)
}
}
private fun getCurrentDate(): Calendar {
// Today if no date is set, otherwise the already set date
return Calendar.getInstance().apply {
item.track?.let {
val date = when (dateToUpdate) {
ReadingDate.Start -> it.started_watching_date
ReadingDate.Finish -> it.finished_watching_date
}
if (date != 0L) {
timeInMillis = date
}
}
}
}
interface Listener {
fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long)
}
enum class ReadingDate {
Start,
Finish
}
companion object {
private const val KEY_ITEM_TRACK = "SetTrackReadingDatesDialog.item.track"
}
}

View file

@ -5,7 +5,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.databinding.TrackItemBinding
import eu.kanade.tachiyomi.util.view.applyElevationOverlay
import uy.kohesive.injekt.api.get
class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter<TrackHolder>() {
@ -40,13 +39,16 @@ class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter<TrackHolder
}
interface OnClickListener {
fun onLogoClick(position: Int)
fun onOpenInBrowserClick(position: Int)
fun onSetClick(position: Int)
fun onTitleLongClick(position: Int)
fun onStatusClick(position: Int)
fun onEpisodesClick(position: Int)
fun onScoreClick(position: Int)
fun onStartDateClick(position: Int)
fun onFinishDateClick(position: Int)
fun onStartDateEditClick(position: Int)
fun onStartDateRemoveClick(position: Int)
fun onFinishDateEditClick(position: Int)
fun onFinishDateRemoveClick(position: Int)
fun onRemoveItemClick(position: Int)
}
}

View file

@ -6,6 +6,7 @@ import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.TrackItemBinding
import eu.kanade.tachiyomi.util.view.popupMenu
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat
@ -17,10 +18,9 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
preferences.dateFormat()
}
init {
val listener = adapter.rowClickListener
private val listener = adapter.rowClickListener
binding.logoContainer.setOnClickListener { listener.onLogoClick(bindingAdapterPosition) }
init {
binding.trackSet.setOnClickListener { listener.onSetClick(bindingAdapterPosition) }
binding.trackTitle.setOnClickListener { listener.onSetClick(bindingAdapterPosition) }
binding.trackTitle.setOnLongClickListener {
@ -30,8 +30,6 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
binding.trackStatus.setOnClickListener { listener.onStatusClick(bindingAdapterPosition) }
binding.trackChapters.setOnClickListener { listener.onEpisodesClick(bindingAdapterPosition) }
binding.trackScore.setOnClickListener { listener.onScoreClick(bindingAdapterPosition) }
binding.trackStartDate.setOnClickListener { listener.onStartDateClick(bindingAdapterPosition) }
binding.trackFinishDate.setOnClickListener { listener.onFinishDateClick(bindingAdapterPosition) }
}
@SuppressLint("SetTextI18n")
@ -42,6 +40,7 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
binding.trackSet.isVisible = track == null
binding.trackTitle.isVisible = track != null
binding.more.isVisible = track != null
binding.middleRow.isVisible = track != null
binding.bottomDivider.isVisible = track != null
@ -77,20 +76,55 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
if (track.started_watching_date != 0L) {
binding.trackStartDate.text = dateFormat.format(track.started_watching_date)
binding.trackStartDate.alpha = SET_STATUS_TEXT_ALPHA
binding.trackStartDate.setOnClickListener {
it.popupMenu(R.menu.track_item_date) {
when (itemId) {
R.id.action_edit -> listener.onStartDateEditClick(bindingAdapterPosition)
R.id.action_remove -> listener.onStartDateRemoveClick(bindingAdapterPosition)
}
}
}
} else {
binding.trackStartDate.text = ctx.getString(R.string.track_started_reading_date)
binding.trackStartDate.alpha = UNSET_STATUS_TEXT_ALPHA
binding.trackStartDate.setOnClickListener {
listener.onStartDateEditClick(bindingAdapterPosition)
}
}
if (track.finished_watching_date != 0L) {
binding.trackFinishDate.text = dateFormat.format(track.finished_watching_date)
binding.trackFinishDate.alpha = SET_STATUS_TEXT_ALPHA
binding.trackFinishDate.setOnClickListener {
it.popupMenu(R.menu.track_item_date) {
when (itemId) {
R.id.action_edit -> listener.onFinishDateEditClick(bindingAdapterPosition)
R.id.action_remove -> listener.onFinishDateRemoveClick(bindingAdapterPosition)
}
}
}
} else {
binding.trackFinishDate.text = ctx.getString(R.string.track_finished_reading_date)
binding.trackFinishDate.alpha = UNSET_STATUS_TEXT_ALPHA
binding.trackFinishDate.setOnClickListener {
listener.onFinishDateEditClick(bindingAdapterPosition)
}
}
}
binding.bottomDivider.isVisible = supportsWatchingDates
binding.bottomRow.isVisible = supportsWatchingDates
binding.more.setOnClickListener {
it.popupMenu(R.menu.track_item) {
when (itemId) {
R.id.action_open_in_browser -> {
listener.onOpenInBrowserClick(bindingAdapterPosition)
}
R.id.action_remove -> {
listener.onRemoveItemClick(bindingAdapterPosition)
}
}
}
}
}
}

View file

@ -74,14 +74,9 @@ class TrackSearchDialog : DialogController {
dialog?.dismiss()
}
}
R.id.remove -> {
trackController.presenter.unregisterTracking(service)
dialog?.dismiss()
}
}
true
}
binding!!.toolbar.menu.findItem(R.id.remove).isVisible = currentTrackUrl != null
// Create adapter
adapter = TrackSearchAdapter(currentTrackUrl) { which ->

View file

@ -3,9 +3,14 @@ package eu.kanade.tachiyomi.ui.anime.track
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import eu.kanade.tachiyomi.R.string
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointBackward
import com.google.android.material.datepicker.DateValidatorPointForward
import com.google.android.material.datepicker.MaterialDatePicker
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeSourceManager
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.track.UnattendedTrackService
@ -13,6 +18,7 @@ import eu.kanade.tachiyomi.databinding.TrackControllerBinding
import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.toUtcCalendar
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.toast
@ -23,13 +29,13 @@ import uy.kohesive.injekt.api.get
class TrackSheet(
val controller: AnimeController,
val anime: Anime,
val fragmentManager: FragmentManager,
private val sourceManager: AnimeSourceManager = Injekt.get()
) : BaseBottomSheetDialog(controller.activity!!),
TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener,
SetTrackEpisodesDialog.Listener,
SetTrackScoreDialog.Listener,
SetTrackWatchingDatesDialog.Listener {
SetTrackScoreDialog.Listener {
private lateinit var binding: TrackControllerBinding
@ -63,7 +69,7 @@ class TrackSheet(
}
}
override fun onLogoClick(position: Int) {
override fun onOpenInBrowserClick(position: Int) {
val track = adapter.getItem(position)?.track ?: return
if (track.tracking_url.isNotBlank()) {
@ -81,7 +87,7 @@ class TrackSheet(
}
if (!item.service.accept(sourceManager.getOrStub(anime.source))) {
controller.presenter.view?.applicationContext?.toast(string.source_unsupported)
controller.presenter.view?.applicationContext?.toast(R.string.source_unsupported)
return
}
@ -90,9 +96,9 @@ class TrackSheet(
item.service.match(anime)?.let { track ->
controller.presenter.registerTracking(track, item.service)
}
?: withUIContext { controller.presenter.view?.applicationContext?.toast(string.error_no_match) }
?: withUIContext { controller.presenter.view?.applicationContext?.toast(R.string.error_no_match) }
} catch (e: Exception) {
withUIContext { controller.presenter.view?.applicationContext?.toast(string.error_no_match) }
withUIContext { controller.presenter.view?.applicationContext?.toast(R.string.error_no_match) }
}
}
} else {
@ -128,18 +134,74 @@ class TrackSheet(
SetTrackScoreDialog(controller, this, item).showDialog(controller.router)
}
override fun onStartDateClick(position: Int) {
override fun onStartDateEditClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
SetTrackWatchingDatesDialog(controller, this, SetTrackWatchingDatesDialog.ReadingDate.Start, item).showDialog(controller.router)
val selection = item.track.started_watching_date.toUtcCalendar()?.timeInMillis
?: MaterialDatePicker.todayInUtcMilliseconds()
// No time travellers allowed
val constraints = CalendarConstraints.Builder().apply {
val finishedMillis = item.track.finished_watching_date.toUtcCalendar()?.timeInMillis
if (finishedMillis != null) {
setValidator(DateValidatorPointBackward.before(finishedMillis))
}
}.build()
val picker = MaterialDatePicker.Builder.datePicker()
.setTitleText(R.string.track_started_reading_date)
.setSelection(selection)
.setCalendarConstraints(constraints)
.build()
picker.addOnPositiveButtonClickListener {
controller.presenter.setTrackerStartDate(item, it)
}
picker.show(fragmentManager, null)
}
override fun onFinishDateClick(position: Int) {
override fun onFinishDateEditClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
SetTrackWatchingDatesDialog(controller, this, SetTrackWatchingDatesDialog.ReadingDate.Finish, item).showDialog(controller.router)
val selection = item.track.finished_watching_date.toUtcCalendar()?.timeInMillis
?: MaterialDatePicker.todayInUtcMilliseconds()
// No time travellers allowed
val constraints = CalendarConstraints.Builder().apply {
val startMillis = item.track.started_watching_date.toUtcCalendar()?.timeInMillis
if (startMillis != null) {
setValidator(DateValidatorPointForward.from(item.track.started_watching_date))
}
}.build()
val picker = MaterialDatePicker.Builder.datePicker()
.setTitleText(R.string.track_finished_reading_date)
.setSelection(selection)
.setCalendarConstraints(constraints)
.build()
picker.addOnPositiveButtonClickListener {
controller.presenter.setTrackerFinishDate(item, it)
}
picker.show(fragmentManager, null)
}
override fun onStartDateRemoveClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
controller.presenter.setTrackerStartDate(item, 0)
}
override fun onFinishDateRemoveClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
controller.presenter.setTrackerFinishDate(item, 0)
}
override fun onRemoveItemClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
controller.presenter.unregisterTracking(item.service)
}
override fun setStatus(item: TrackItem, selection: Int) {
@ -154,13 +216,6 @@ class TrackSheet(
controller.presenter.setTrackerScore(item, score)
}
override fun setReadingDate(item: TrackItem, type: SetTrackWatchingDatesDialog.ReadingDate, date: Long) {
when (type) {
SetTrackWatchingDatesDialog.ReadingDate.Start -> controller.presenter.setTrackerStartDate(item, date)
SetTrackWatchingDatesDialog.ReadingDate.Finish -> controller.presenter.setTrackerFinishDate(item, date)
}
}
fun getSearchDialog(): TrackSearchDialog? {
return controller.router.getControllerWithTag(TAG_SEARCH_CONTROLLER) as? TrackSearchDialog
}

View file

@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.animecategory
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
/**
* Dialog to create a new category for the library.
@ -30,18 +30,16 @@ class CategoryCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
* @return a new dialog instance.
*/
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_add_category)
.negativeButton(android.R.string.cancel)
.input(
hint = resources?.getString(R.string.name),
prefill = currentName
) { _, input ->
currentName = input.toString()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_add_category)
.setTextInput(prefill = currentName) {
currentName = it
}
.positiveButton(android.R.string.ok) {
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? Listener)?.createCategory(currentName)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.ui.animecategory
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
/**
* Dialog to rename an existing category of the library.
@ -35,16 +35,14 @@ class CategoryRenameDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
* @return a new dialog instance.
*/
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_rename_category)
.negativeButton(android.R.string.cancel)
.input(
hint = resources?.getString(R.string.name),
prefill = currentName
) { _, input ->
currentName = input.toString()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_rename_category)
.setTextInput(prefill = currentName) {
currentName = it
}
.positiveButton(android.R.string.ok) { onPositive() }
.setPositiveButton(android.R.string.ok) { _, _ -> onPositive() }
.setNegativeButton(android.R.string.cancel, null)
.create()
}
/**

View file

@ -14,9 +14,11 @@ import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.AnimelibCategoryBinding
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
@ -106,6 +108,10 @@ class AnimelibCategoryView @JvmOverloads constructor(context: Context, attrs: At
}
.launchIn(scope)
recycler.onAnimationsFinished {
(controller.activity as? MainActivity)?.ready = true
}
// Double the distance required to trigger sync
binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt())
binding.swipeRefresh.refreshes()

View file

@ -277,6 +277,7 @@ class AnimelibController(
binding.emptyView.hide()
} else {
binding.emptyView.show(R.string.information_empty_library)
(activity as? MainActivity)?.ready = true
}
// Get the current active category.

View file

@ -2,15 +2,14 @@ package eu.kanade.tachiyomi.ui.animelib
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.animecategory.CategoryController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.category.CategoryController
class ChangeAnimeCategoriesDialog<T>(bundle: Bundle? = null) :
DialogController(bundle) where T : Controller, T : ChangeAnimeCategoriesDialog.Listener {
@ -32,32 +31,34 @@ class ChangeAnimeCategoriesDialog<T>(bundle: Bundle? = null) :
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_move_category)
.negativeButton(android.R.string.cancel)
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_move_category)
.setNegativeButton(android.R.string.cancel, null)
.apply {
if (categories.isNotEmpty()) {
listItemsMultiChoice(
items = categories.map { it.name },
initialSelection = preselected.toIntArray(),
allowEmptySelection = true
) { _, selections, _ ->
val newCategories = selections.map { categories[it] }
val selected = categories
.mapIndexed { i, _ -> preselected.contains(i) }
.toBooleanArray()
setMultiChoiceItems(categories.map { it.name }.toTypedArray(), selected) { _, which, checked ->
selected[which] = checked
}
setPositiveButton(android.R.string.ok) { _, _ ->
val newCategories = categories.filterIndexed { i, _ -> selected[i] }
(targetController as? Listener)?.updateCategoriesForAnimes(animes, newCategories)
}
.positiveButton(android.R.string.ok)
} else {
message(R.string.information_empty_category_dialog)
.positiveButton(R.string.action_edit_categories) {
if (targetController is AnimelibController) {
val libController = targetController as AnimelibController
libController.clearSelection()
}
router.popCurrentController()
router.pushController(CategoryController().withFadeTransaction())
setMessage(R.string.information_empty_category_dialog)
setPositiveButton(R.string.action_edit_categories) { _, _ ->
if (targetController is AnimelibController) {
val libController = targetController as AnimelibController
libController.clearSelection()
}
router.popCurrentController()
router.pushController(CategoryController().withFadeTransaction())
}
}
}
.create()
}
interface Listener {

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.animelib
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -20,15 +20,16 @@ class ChangeAnimeCoverDialog<T>(bundle: Bundle? = null) :
@Suppress("DEPRECATION")
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_edit_cover)
.positiveButton(R.string.action_edit) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_edit_cover)
.setPositiveButton(R.string.action_edit) { _, _ ->
(targetController as? Listener)?.openAnimeCoverPicker(anime)
}
.negativeButton(android.R.string.cancel)
.neutralButton(R.string.action_delete) {
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.action_delete) { _, _ ->
(targetController as? Listener)?.deleteAnimeCover(anime)
}
.create()
}
interface Listener {

View file

@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.animelib
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -20,18 +19,22 @@ class DeleteAnimelibAnimesDialog<T>(bundle: Bundle? = null) :
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_remove)
.listItemsMultiChoice(
R.array.delete_selected_animes,
initialSelection = intArrayOf(0)
) { _, selections, _ ->
val deleteFromAnimelib = 0 in selections
val deleteChapters = 1 in selections
(targetController as? Listener)?.deleteAnimes(animes, deleteFromAnimelib, deleteChapters)
val items = resources!!.getStringArray(R.array.delete_selected_animes)
val selected = items
.mapIndexed { i, _ -> i == 0 }
.toBooleanArray()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_remove)
.setMultiChoiceItems(items, selected) { _, which, checked ->
selected[which] = checked
}
.positiveButton(android.R.string.ok)
.negativeButton(android.R.string.cancel)
.setPositiveButton(android.R.string.ok) { _, _ ->
val deleteFromLibrary = selected[0]
val deleteEpisodes = selected[1]
(targetController as? Listener)?.deleteAnimes(animes, deleteFromLibrary, deleteEpisodes)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -53,12 +53,15 @@ abstract class BaseThemedActivity : AppCompatActivity() {
PreferenceValues.AppTheme.STRAWBERRY_DAIQUIRI -> {
resIds += R.style.Theme_Tachiyomi_StrawberryDaiquiri
}
PreferenceValues.AppTheme.YOTSUBA -> {
resIds += R.style.Theme_Tachiyomi_Yotsuba
PreferenceValues.AppTheme.TAKO -> {
resIds += R.style.Theme_Tachiyomi_Tako
}
PreferenceValues.AppTheme.YINYANG -> {
resIds += R.style.Theme_Tachiyomi_YinYang
}
PreferenceValues.AppTheme.YOTSUBA -> {
resIds += R.style.Theme_Tachiyomi_Yotsuba
}
else -> {
resIds += R.style.Theme_Tachiyomi
}

View file

@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.browse.animeextension
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -21,15 +21,16 @@ class AnimeExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bu
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.untrusted_extension)
.message(R.string.untrusted_extension_message)
.positiveButton(R.string.ext_trust) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.untrusted_extension)
.setMessage(R.string.untrusted_extension_message)
.setPositiveButton(R.string.ext_trust) { _, _ ->
(targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!)
}
.negativeButton(R.string.ext_uninstall) {
.setNegativeButton(R.string.ext_uninstall) { _, _ ->
(targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!)
}
.create()
}
private companion object {

View file

@ -9,10 +9,9 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
@ -32,6 +31,8 @@ import eu.kanade.tachiyomi.ui.browse.BrowseController
import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceController
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchController
import eu.kanade.tachiyomi.ui.browse.animesource.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -82,6 +83,9 @@ class AnimeSourceController :
// Create recycler and set adapter.
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
adapter?.fastScroller = binding.fastScroller
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
@ -238,15 +242,13 @@ class AnimeSourceController :
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(text = source)
.listItems(
items = items.map { it.first },
waitForPositiveButton = false
) { dialog, which, _ ->
return MaterialAlertDialogBuilder(activity!!)
.setTitle(source)
.setItems(items.map { it.first }.toTypedArray()) { dialog, which ->
items[which].second()
dialog.dismiss()
}
.create()
}
}

View file

@ -13,8 +13,7 @@ import androidx.core.view.updatePadding
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.tfcporciuncula.flow.Preference
@ -589,11 +588,9 @@ open class BrowseAnimeSourceController(bundle: Bundle) :
val anime = (adapter?.getItem(position) as? AnimeSourceItem?)?.anime ?: return
if (anime.favorite) {
MaterialDialog(activity)
.listItems(
items = listOf(activity.getString(R.string.remove_from_library)),
waitForPositiveButton = false
) { _, which, _ ->
MaterialAlertDialogBuilder(activity)
.setTitle(anime.title)
.setItems(arrayOf(activity.getString(R.string.remove_from_library))) { _, which ->
when (which) {
0 -> {
presenter.changeAnimeFavorite(anime)

View file

@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.browse.extension
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -21,15 +21,16 @@ class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.untrusted_extension)
.message(R.string.untrusted_extension_message)
.positiveButton(R.string.ext_trust) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.untrusted_extension)
.setMessage(R.string.untrusted_extension_message)
.setPositiveButton(R.string.ext_trust) { _, _ ->
(targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!)
}
.negativeButton(R.string.ext_uninstall) {
.setNegativeButton(R.string.ext_uninstall) { _, _ ->
(targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!)
}
.create()
}
private companion object {

View file

@ -3,13 +3,14 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
import android.app.Dialog
import android.os.Bundle
import androidx.core.view.isVisible
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.anime.AnimeController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchController
@ -69,12 +70,14 @@ class AnimeSearchController(
super.onAnimeClick(anime)
}
fun renderIsReplacingAnime(isReplacingAnime: Boolean) {
if (isReplacingAnime) {
binding.progress.isVisible = true
} else {
binding.progress.isVisible = false
fun renderIsReplacingAnime(isReplacingAnime: Boolean, newAnime: Anime?) {
binding.progress.isVisible = isReplacingAnime
if (!isReplacingAnime) {
router.popController(this)
if (newAnime != null) {
// Replaces old AnimeController
router.replaceTopController(RouterTransaction.with(AnimeController(newAnime)))
}
}
}
@ -82,28 +85,29 @@ class AnimeSearchController(
private val preferences: PreferencesHelper by injectLazy()
@Suppress("DEPRECATION")
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val prefValue = preferences.migrateFlags().get()
val enabledFlagsPositions = AnimeMigrationFlags.getEnabledFlagsPositions(prefValue)
val items = AnimeMigrationFlags.titles
.map { resources?.getString(it) }
.toTypedArray()
val selected = items
.mapIndexed { i, _ -> enabledFlagsPositions.contains(i) }
.toBooleanArray()
val preselected =
AnimeMigrationFlags.getEnabledFlagsPositions(
prefValue
)
return MaterialDialog(activity!!)
.title(R.string.migration_dialog_what_to_include)
.listItemsMultiChoice(
items = AnimeMigrationFlags.titles.map { resources?.getString(it) as CharSequence },
initialSelection = preselected.toIntArray()
) { _, positions, _ ->
// Save current settings for the next time
val newValue =
AnimeMigrationFlags.getFlagsFromPositions(
positions.toTypedArray()
)
preferences.migrateFlags().set(newValue)
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.migration_dialog_what_to_include)
.setMultiChoiceItems(items, selected) { _, which, checked ->
selected[which] = checked
}
.positiveButton(R.string.migrate) {
.setPositiveButton(R.string.migrate) { _, _ ->
// Save current settings for the next time
val selectedIndices = mutableListOf<Int>()
selected.forEachIndexed { i, b -> if (b) selectedIndices.add(i) }
val newValue = AnimeMigrationFlags.getFlagsFromPositions(selectedIndices.toTypedArray())
preferences.migrateFlags().set(newValue)
if (callingController != null) {
if (callingController.javaClass == AnimeSourceSearchController::class.java) {
router.popController(callingController)
@ -111,7 +115,7 @@ class AnimeSearchController(
}
(targetController as? AnimeSearchController)?.migrateAnime(anime, newAnime)
}
.negativeButton(R.string.copy) {
.setNegativeButton(R.string.copy) { _, _, ->
if (callingController != null) {
if (callingController.javaClass == AnimeSourceSearchController::class.java) {
router.popController(callingController)
@ -119,7 +123,8 @@ class AnimeSearchController(
}
(targetController as? AnimeSearchController)?.copyAnime(anime, newAnime)
}
.neutralButton(android.R.string.cancel)
.setNeutralButton(android.R.string.cancel, null)
.create()
}
}

View file

@ -26,12 +26,16 @@ class AnimeSearchPresenter(
private val anime: Anime
) : GlobalAnimeSearchPresenter(initialQuery) {
private val replacingAnimeRelay = BehaviorRelay.create<Boolean>()
private val replacingAnimeRelay = BehaviorRelay.create<Pair<Boolean, Anime?>>()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
replacingAnimeRelay.subscribeLatestCache({ controller, isReplacingAnime -> (controller as? AnimeSearchController)?.renderIsReplacingAnime(isReplacingAnime) })
replacingAnimeRelay.subscribeLatestCache(
{ controller, (isReplacingAnime, newAnime) ->
(controller as? AnimeSearchController)?.renderIsReplacingAnime(isReplacingAnime, newAnime)
}
)
}
override fun getEnabledSources(): List<AnimeCatalogueSource> {
@ -55,7 +59,7 @@ class AnimeSearchPresenter(
fun migrateAnime(prevAnime: Anime, anime: Anime, replace: Boolean) {
val source = sourceManager.get(anime.source) ?: return
replacingAnimeRelay.call(true)
replacingAnimeRelay.call(Pair(true, null))
presenterScope.launchIO {
try {
@ -67,7 +71,7 @@ class AnimeSearchPresenter(
withUIContext { view?.applicationContext?.toast(e.message) }
}
presenterScope.launchUI { replacingAnimeRelay.call(false) }
presenterScope.launchUI { replacingAnimeRelay.call(Pair(false, anime)) }
}
}

View file

@ -3,10 +3,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
import android.app.Dialog
import android.os.Bundle
import androidx.core.view.isVisible
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -89,26 +88,26 @@ class SearchController(
@Suppress("DEPRECATION")
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val prefValue = preferences.migrateFlags().get()
val enabledFlagsPositions = MigrationFlags.getEnabledFlagsPositions(prefValue)
val items = MigrationFlags.titles
.map { resources?.getString(it) }
.toTypedArray()
val selected = items
.mapIndexed { i, _ -> enabledFlagsPositions.contains(i) }
.toBooleanArray()
val preselected =
MigrationFlags.getEnabledFlagsPositions(
prefValue
)
return MaterialDialog(activity!!)
.title(R.string.migration_dialog_what_to_include)
.listItemsMultiChoice(
items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence },
initialSelection = preselected.toIntArray()
) { _, positions, _ ->
// Save current settings for the next time
val newValue =
MigrationFlags.getFlagsFromPositions(
positions.toTypedArray()
)
preferences.migrateFlags().set(newValue)
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.migration_dialog_what_to_include)
.setMultiChoiceItems(items, selected) { _, which, checked ->
selected[which] = checked
}
.positiveButton(R.string.migrate) {
.setPositiveButton(R.string.migrate) { _, _ ->
// Save current settings for the next time
val selectedIndices = mutableListOf<Int>()
selected.forEachIndexed { i, b -> if (b) selectedIndices.add(i) }
val newValue = MigrationFlags.getFlagsFromPositions(selectedIndices.toTypedArray())
preferences.migrateFlags().set(newValue)
if (callingController != null) {
if (callingController.javaClass == SourceSearchController::class.java) {
router.popController(callingController)
@ -116,7 +115,7 @@ class SearchController(
}
(targetController as? SearchController)?.migrateManga(manga, newManga)
}
.negativeButton(R.string.copy) {
.setNegativeButton(R.string.copy) { _, _, ->
if (callingController != null) {
if (callingController.javaClass == SourceSearchController::class.java) {
router.popController(callingController)
@ -124,7 +123,8 @@ class SearchController(
}
(targetController as? SearchController)?.copyManga(manga, newManga)
}
.neutralButton(android.R.string.cancel)
.setNeutralButton(android.R.string.cancel, null)
.create()
}
}

View file

@ -9,10 +9,9 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
@ -32,6 +31,8 @@ import eu.kanade.tachiyomi.ui.browse.BrowseController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -82,6 +83,9 @@ class SourceController :
// Create recycler and set adapter.
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
adapter?.fastScroller = binding.fastScroller
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)
@ -238,15 +242,13 @@ class SourceController :
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(text = source)
.listItems(
items = items.map { it.first },
waitForPositiveButton = false
) { dialog, which, _ ->
return MaterialAlertDialogBuilder(activity!!)
.setTitle(source)
.setItems(items.map { it.first }.toTypedArray()) { dialog, which ->
items[which].second()
dialog.dismiss()
}
.create()
}
}

View file

@ -13,8 +13,7 @@ import androidx.core.view.updatePadding
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.tfcporciuncula.flow.Preference
@ -589,11 +588,9 @@ open class BrowseSourceController(bundle: Bundle) :
val manga = (adapter?.getItem(position) as? SourceItem?)?.manga ?: return
if (manga.favorite) {
MaterialDialog(activity)
.listItems(
items = listOf(activity.getString(R.string.remove_from_library)),
waitForPositiveButton = false
) { _, which, _ ->
MaterialAlertDialogBuilder(activity)
.setTitle(manga.title)
.setItems(arrayOf(activity.getString(R.string.remove_from_library))) { _, which ->
when (which) {
0 -> {
presenter.changeMangaFavorite(manga)

View file

@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.category
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
/**
* Dialog to create a new category for the library.
@ -30,18 +30,16 @@ class CategoryCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
* @return a new dialog instance.
*/
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_add_category)
.negativeButton(android.R.string.cancel)
.input(
hint = resources?.getString(R.string.name),
prefill = currentName
) { _, input ->
currentName = input.toString()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_add_category)
.setTextInput(prefill = currentName) {
currentName = it
}
.positiveButton(android.R.string.ok) {
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? Listener)?.createCategory(currentName)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.ui.category
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
/**
* Dialog to rename an existing category of the library.
@ -35,16 +35,14 @@ class CategoryRenameDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
* @return a new dialog instance.
*/
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_rename_category)
.negativeButton(android.R.string.cancel)
.input(
hint = resources?.getString(R.string.name),
prefill = currentName
) { _, input ->
currentName = input.toString()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_rename_category)
.setTextInput(prefill = currentName) {
currentName = it
}
.positiveButton(android.R.string.ok) { onPositive() }
.setPositiveButton(android.R.string.ok) { _, _ -> onPositive() }
.setNegativeButton(android.R.string.cancel, null)
.create()
}
/**

View file

@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.library
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga
@ -32,32 +31,34 @@ class ChangeMangaCategoriesDialog<T>(bundle: Bundle? = null) :
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_move_category)
.negativeButton(android.R.string.cancel)
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_move_category)
.setNegativeButton(android.R.string.cancel, null)
.apply {
if (categories.isNotEmpty()) {
listItemsMultiChoice(
items = categories.map { it.name },
initialSelection = preselected.toIntArray(),
allowEmptySelection = true
) { _, selections, _ ->
val newCategories = selections.map { categories[it] }
val selected = categories
.mapIndexed { i, _ -> preselected.contains(i) }
.toBooleanArray()
setMultiChoiceItems(categories.map { it.name }.toTypedArray(), selected) { _, which, checked ->
selected[which] = checked
}
setPositiveButton(android.R.string.ok) { _, _ ->
val newCategories = categories.filterIndexed { i, _ -> selected[i] }
(targetController as? Listener)?.updateCategoriesForMangas(mangas, newCategories)
}
.positiveButton(android.R.string.ok)
} else {
message(R.string.information_empty_category_dialog)
.positiveButton(R.string.action_edit_categories) {
if (targetController is LibraryController) {
val libController = targetController as LibraryController
libController.clearSelection()
}
router.popCurrentController()
router.pushController(CategoryController().withFadeTransaction())
setMessage(R.string.information_empty_category_dialog)
setPositiveButton(R.string.action_edit_categories) { _, _ ->
if (targetController is LibraryController) {
val libController = targetController as LibraryController
libController.clearSelection()
}
router.popCurrentController()
router.pushController(CategoryController().withFadeTransaction())
}
}
}
.create()
}
interface Listener {

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.library
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -20,15 +20,16 @@ class ChangeMangaCoverDialog<T>(bundle: Bundle? = null) :
@Suppress("DEPRECATION")
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_edit_cover)
.positiveButton(R.string.action_edit) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_edit_cover)
.setPositiveButton(R.string.action_edit) { _, _ ->
(targetController as? Listener)?.openMangaCoverPicker(manga)
}
.negativeButton(android.R.string.cancel)
.neutralButton(R.string.action_delete) {
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.action_delete) { _, _ ->
(targetController as? Listener)?.deleteMangaCover(manga)
}
.create()
}
interface Listener {

View file

@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.library
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -20,18 +19,22 @@ class DeleteLibraryMangasDialog<T>(bundle: Bundle? = null) :
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(R.string.action_remove)
.listItemsMultiChoice(
R.array.delete_selected_mangas,
initialSelection = intArrayOf(0)
) { _, selections, _ ->
val deleteFromLibrary = 0 in selections
val deleteChapters = 1 in selections
val items = resources!!.getStringArray(R.array.delete_selected_mangas)
val selected = items
.mapIndexed { i, _ -> i == 0 }
.toBooleanArray()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.action_remove)
.setMultiChoiceItems(items, selected) { _, which, checked ->
selected[which] = checked
}
.setPositiveButton(android.R.string.ok) { _, _ ->
val deleteFromLibrary = selected[0]
val deleteChapters = selected[1]
(targetController as? Listener)?.deleteMangas(mangas, deleteFromLibrary, deleteChapters)
}
.positiveButton(android.R.string.ok)
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -14,9 +14,11 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.LibraryCategoryBinding
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
@ -106,6 +108,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
}
.launchIn(scope)
recycler.onAnimationsFinished {
(controller.activity as? MainActivity)?.ready = true
}
// Double the distance required to trigger sync
binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt())
binding.swipeRefresh.refreshes()

View file

@ -277,6 +277,7 @@ class LibraryController(
binding.emptyView.hide()
} else {
binding.emptyView.show(R.string.information_empty_library)
(activity as? MainActivity)?.ready = true
}
// Get the current active category.

View file

@ -1,18 +1,27 @@
package eu.kanade.tachiyomi.ui.main
import android.animation.ValueAnimator
import android.app.SearchManager
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.animation.doOnEnd
import androidx.core.splashscreen.SplashScreen
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceDialogController
import com.bluelinelabs.conductor.Conductor
@ -53,6 +62,7 @@ import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.ui.recent.UpdatesTabsController
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
import kotlinx.coroutines.delay
@ -87,7 +97,13 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>()
// To be checked by splash screen. If true then splash screen will be removed.
var ready = false
override fun onCreate(savedInstanceState: Bundle?) {
// Prevent splash screen showing up on configuration changes
val splashScreen = if (savedInstanceState == null) installSplashScreen() else null
super.onCreate(savedInstanceState)
val didMigration = if (savedInstanceState == null) Migrations.upgrade(preferences) else false
@ -121,13 +137,12 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
}
}
// Make sure navigation bar is on bottom before we modify it
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
window.setNavigationBarTransparentCompat(this)
}
insets
val startTime = System.currentTimeMillis()
splashScreen?.setKeepVisibleCondition {
val elapsed = System.currentTimeMillis() - startTime
elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION)
}
setSplashScreenExitAnimation(splashScreen)
tabAnimator = ViewHeightAnimator(binding.tabs, 0L)
@ -274,6 +289,79 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
.launchIn(lifecycleScope)
}
/**
* Sets custom splash screen exit animation on devices prior to Android 12.
*
* When custom animation is used, status and navigation bar color will be set to transparent and will be restored
* after the animation is finished.
*/
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
val setNavbarScrim = {
// Make sure navigation bar is on bottom before we modify it
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
window.setNavigationBarTransparentCompat(this@MainActivity)
}
insets
}
ViewCompat.requestApplyInsets(binding.root)
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
val oldStatusColor = window.statusBarColor
val oldNavigationColor = window.navigationBarColor
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
val wicc = WindowInsetsControllerCompat(window, window.decorView)
val isLightStatusBars = wicc.isAppearanceLightStatusBars
val isLightNavigationBars = wicc.isAppearanceLightNavigationBars
wicc.isAppearanceLightStatusBars = false
wicc.isAppearanceLightNavigationBars = false
splashScreen?.setOnExitAnimationListener { splashProvider ->
// For some reason the SplashScreen applies (incorrect) Y translation to the iconView
splashProvider.iconView.translationY = 0F
val activityAnim = ValueAnimator.ofFloat(1F, 0F).apply {
interpolator = LinearOutSlowInInterpolator()
duration = SPLASH_EXIT_ANIM_DURATION
addUpdateListener { va ->
val value = va.animatedValue as Float
binding.root.translationY = value * 16.dpToPx
}
}
var barColorRestored = false
val splashAnim = ValueAnimator.ofFloat(1F, 0F).apply {
interpolator = FastOutSlowInInterpolator()
duration = SPLASH_EXIT_ANIM_DURATION
addUpdateListener { va ->
val value = va.animatedValue as Float
splashProvider.view.alpha = value
if (!barColorRestored && value <= 0.5F) {
barColorRestored = true
wicc.isAppearanceLightStatusBars = isLightStatusBars
wicc.isAppearanceLightNavigationBars = isLightNavigationBars
}
}
doOnEnd {
splashProvider.remove()
window.statusBarColor = oldStatusColor
window.navigationBarColor = oldNavigationColor
setNavbarScrim()
}
}
activityAnim.start()
splashAnim.start()
}
} else {
setNavbarScrim()
}
}
override fun onNewIntent(intent: Intent) {
if (!handleIntentAction(intent)) {
super.onNewIntent(intent)
@ -402,6 +490,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
}
}
ready = true
isHandlingShortcut = false
return true
}
@ -573,6 +662,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
get() = binding.bottomNav ?: binding.sideNav!!
companion object {
// Splash screen
private const val SPLASH_MIN_DURATION = 500 // ms
private const val SPLASH_MAX_DURATION = 5000 // ms
private const val SPLASH_EXIT_ANIM_DURATION = 400L // ms
// Shortcut actions
const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY"
const val SHORTCUT_ANIMELIB = "eu.kanade.tachiyomi.SHOW_ANIMELIB"

View file

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.main
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -12,11 +12,12 @@ class WhatsNewDialogController(bundle: Bundle? = null) : DialogController(bundle
@Suppress("DEPRECATION")
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(text = activity!!.getString(R.string.updated_version, BuildConfig.VERSION_NAME))
.positiveButton(android.R.string.ok)
.neutralButton(R.string.whats_new) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(activity!!.getString(R.string.updated_version, BuildConfig.VERSION_NAME))
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(R.string.whats_new) { _, _ ->
openInBrowser("https://github.com/jmir1/aniyomi/releases/tag/v${BuildConfig.VERSION_NAME}")
}
.create()
}
}

View file

@ -289,7 +289,7 @@ class MangaController :
}
}
trackSheet = TrackSheet(this, manga!!)
trackSheet = TrackSheet(this, manga!!, (activity as MainActivity).supportFragmentManager)
updateFilterIconState()
}
@ -661,7 +661,7 @@ class MangaController :
}
}
private fun shareCover() {
fun shareCover() {
try {
val activity = activity!!
val cover = presenter.shareCover(activity)
@ -673,7 +673,7 @@ class MangaController :
}
}
private fun saveCover() {
fun saveCover() {
try {
presenter.saveCover(activity!!)
activity?.toast(R.string.cover_saved)
@ -683,7 +683,7 @@ class MangaController :
}
}
private fun changeCover() {
fun changeCover() {
val manga = manga ?: return
if (manga.hasCustomCover(coverCache)) {
ChangeMangaCoverDialog(this, manga).showDialog(router)

View file

@ -779,7 +779,8 @@ class MangaPresenter(
item.manga_id = manga.id!!
launchIO {
try {
service.bind(item)
val hasReadChapters = allChapters.any { it.read }
service.bind(item, hasReadChapters)
db.insertTrack(item).executeAsBlocking()
if (service is UnattendedTrackService) {
@ -830,6 +831,9 @@ class MangaPresenter(
fun setTrackerLastChapterRead(item: TrackItem, chapterNumber: Int) {
val track = item.track!!
if (track.last_chapter_read == 0 && track.last_chapter_read < chapterNumber && track.status != item.service.getRereadingStatus()) {
track.status = item.service.getReadingStatus()
}
track.last_chapter_read = chapterNumber
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
track.status = item.service.getCompletionStatus()

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.manga.chapter
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -15,12 +15,13 @@ class DeleteChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.confirm_delete_chapters)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setMessage(R.string.confirm_delete_chapters)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? Listener)?.deleteChapters()
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -3,9 +3,8 @@ package eu.kanade.tachiyomi.ui.manga.chapter
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.widget.DialogCustomDownloadView
@ -57,13 +56,14 @@ class DownloadCustomChaptersDialog<T> : DialogController
// Build dialog.
// when positive dialog is pressed call custom listener.
return MaterialDialog(activity)
.title(R.string.custom_download)
.customView(view = view, scrollable = true)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity)
.setTitle(R.string.custom_download)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? Listener)?.downloadCustomChapters(view.amount)
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -3,8 +3,7 @@ package eu.kanade.tachiyomi.ui.manga.chapter
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -24,13 +23,10 @@ class SetChapterSettingsDialog(bundle: Bundle? = null) : DialogController(bundle
setOptionDescription(R.string.also_set_chapter_settings_for_library)
}
return MaterialDialog(activity!!)
.title(R.string.chapter_settings)
.customView(
view = view,
horizontalPadding = true
)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.chapter_settings)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
ChapterSettingsHelper.setGlobalSettings(args.getSerializable(MANGA_KEY)!! as Manga)
if (view.isChecked()) {
ChapterSettingsHelper.updateAllMangasWithGlobalDefaults()
@ -38,7 +34,8 @@ class SetChapterSettingsDialog(bundle: Bundle? = null) : DialogController(bundle
activity?.toast(activity!!.getString(R.string.chapter_settings_updated))
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
private companion object {

View file

@ -6,6 +6,7 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import coil.loadAny
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.track.TrackManager
@ -177,16 +178,34 @@ class MangaInfoHeaderAdapter(
binding.mangaCover.longClicks()
.onEach {
controller.activity?.copyToClipboard(
view.context.getString(R.string.title),
controller.presenter.manga.title
)
showCoverOptionsDialog()
}
.launchIn(controller.viewScope)
setMangaInfo(manga, source)
}
private fun showCoverOptionsDialog() {
val options = listOfNotNull(
R.string.action_share,
R.string.action_save,
// Can only edit cover for library manga
if (manga.favorite) R.string.action_edit else null
).map(controller.activity!!::getString).toTypedArray()
MaterialAlertDialogBuilder(controller.activity!!)
.setTitle(R.string.manga_cover)
.setItems(options) { _, item ->
when (item) {
0 -> controller.shareCover()
1 -> controller.saveCover()
2 -> controller.changeCover()
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
/**
* Update the view with manga information.
*

View file

@ -2,15 +2,14 @@ package eu.kanade.tachiyomi.ui.manga.track
import android.app.Dialog
import android.os.Bundle
import android.widget.NumberPicker
import android.view.LayoutInflater
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.databinding.TrackChaptersDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -38,23 +37,9 @@ class SetTrackChaptersDialog<T> : DialogController
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val item = item
val pickerView = TrackChaptersDialogBinding.inflate(LayoutInflater.from(activity!!))
val np = pickerView.chaptersPicker
val dialog = MaterialDialog(activity!!)
.title(R.string.chapters)
.customView(R.layout.track_chapters_dialog, dialogWrapContent = false)
.positiveButton(android.R.string.ok) { dialog ->
val view = dialog.getCustomView()
// Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.chapters_picker)
np.clearFocus()
listener.setChaptersRead(item, np.value)
}
.negativeButton(android.R.string.cancel)
val view = dialog.getCustomView()
val np: NumberPicker = view.findViewById(R.id.chapters_picker)
// Set initial value
np.value = item.track?.last_chapter_read ?: 0
@ -66,7 +51,15 @@ class SetTrackChaptersDialog<T> : DialogController
// Don't allow to go from 0 to 9999
np.wrapSelectorWheel = false
return dialog
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.chapters)
.setView(pickerView.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
np.clearFocus()
listener.setChaptersRead(item, np.value)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -1,87 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.track
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.datetime.datePicker
import com.bluelinelabs.conductor.Controller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Calendar
class SetTrackReadingDatesDialog<T> : DialogController
where T : Controller {
private val item: TrackItem
private val dateToUpdate: ReadingDate
private lateinit var listener: Listener
constructor(target: T, listener: Listener, dateToUpdate: ReadingDate, item: TrackItem) : super(
bundleOf(KEY_ITEM_TRACK to item.track)
) {
targetController = target
this.listener = listener
this.item = item
this.dateToUpdate = dateToUpdate
}
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
val track = bundle.getSerializable(KEY_ITEM_TRACK) as Track
val service = Injekt.get<TrackManager>().getService(track.sync_id)!!
item = TrackItem(track, service)
dateToUpdate = ReadingDate.Start
}
@Suppress("DEPRECATION")
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(
when (dateToUpdate) {
ReadingDate.Start -> R.string.track_started_reading_date
ReadingDate.Finish -> R.string.track_finished_reading_date
}
)
.datePicker(currentDate = getCurrentDate()) { _, date ->
listener.setReadingDate(item, dateToUpdate, date.timeInMillis)
}
.neutralButton(R.string.action_remove) {
listener.setReadingDate(item, dateToUpdate, 0L)
}
}
private fun getCurrentDate(): Calendar {
// Today if no date is set, otherwise the already set date
return Calendar.getInstance().apply {
item.track?.let {
val date = when (dateToUpdate) {
ReadingDate.Start -> it.started_reading_date
ReadingDate.Finish -> it.finished_reading_date
}
if (date != 0L) {
timeInMillis = date
}
}
}
}
interface Listener {
fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long)
}
enum class ReadingDate {
Start,
Finish
}
companion object {
private const val KEY_ITEM_TRACK = "SetTrackReadingDatesDialog.item.track"
}
}

View file

@ -2,15 +2,14 @@ package eu.kanade.tachiyomi.ui.manga.track
import android.app.Dialog
import android.os.Bundle
import android.widget.NumberPicker
import android.view.LayoutInflater
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.databinding.TrackScoreDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -38,23 +37,9 @@ class SetTrackScoreDialog<T> : DialogController
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val item = item
val pickerView = TrackScoreDialogBinding.inflate(LayoutInflater.from(activity!!))
val np = pickerView.scorePicker
val dialog = MaterialDialog(activity!!)
.title(R.string.score)
.customView(R.layout.track_score_dialog, dialogWrapContent = false)
.positiveButton(android.R.string.ok) { dialog ->
val view = dialog.getCustomView()
// Remove focus to update selected number
val np: NumberPicker = view.findViewById(R.id.score_picker)
np.clearFocus()
listener.setScore(item, np.value)
}
.negativeButton(android.R.string.cancel)
val view = dialog.getCustomView()
val np: NumberPicker = view.findViewById(R.id.score_picker)
val scores = item.service.getScoreList().toTypedArray()
np.maxValue = scores.size - 1
np.displayedValues = scores
@ -66,7 +51,15 @@ class SetTrackScoreDialog<T> : DialogController
np.value = if (index != -1) index else 0
}
return dialog
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.score)
.setView(pickerView.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
np.clearFocus()
listener.setScore(item, np.value)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -3,9 +3,8 @@ package eu.kanade.tachiyomi.ui.manga.track
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
@ -36,22 +35,20 @@ class SetTrackStatusDialog<T> : DialogController
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val item = item
val statusList = item.service.getStatusList()
val statusString = statusList.map { item.service.getStatus(it) }
val selectedIndex = statusList.indexOf(item.track?.status)
var selectedIndex = statusList.indexOf(item.track?.status)
return MaterialDialog(activity!!)
.title(R.string.status)
.negativeButton(android.R.string.cancel)
.listItemsSingleChoice(
items = statusString,
initialSelection = selectedIndex,
waitForPositiveButton = false
) { dialog, position, _ ->
listener.setStatus(item, position)
dialog.dismiss()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.status)
.setSingleChoiceItems(statusString.toTypedArray(), selectedIndex) { _, which ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) { _, _ ->
listener.setStatus(item, selectedIndex)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -5,7 +5,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.databinding.TrackItemBinding
import eu.kanade.tachiyomi.util.view.applyElevationOverlay
import uy.kohesive.injekt.api.get
class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter<TrackHolder>() {
@ -40,13 +39,16 @@ class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter<TrackHolder
}
interface OnClickListener {
fun onLogoClick(position: Int)
fun onOpenInBrowserClick(position: Int)
fun onSetClick(position: Int)
fun onTitleLongClick(position: Int)
fun onStatusClick(position: Int)
fun onChaptersClick(position: Int)
fun onScoreClick(position: Int)
fun onStartDateClick(position: Int)
fun onFinishDateClick(position: Int)
fun onStartDateEditClick(position: Int)
fun onStartDateRemoveClick(position: Int)
fun onFinishDateEditClick(position: Int)
fun onFinishDateRemoveClick(position: Int)
fun onRemoveItemClick(position: Int)
}
}

View file

@ -6,6 +6,7 @@ import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.TrackItemBinding
import eu.kanade.tachiyomi.util.view.popupMenu
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat
@ -17,10 +18,9 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
preferences.dateFormat()
}
init {
val listener = adapter.rowClickListener
private val listener = adapter.rowClickListener
binding.logoContainer.setOnClickListener { listener.onLogoClick(bindingAdapterPosition) }
init {
binding.trackSet.setOnClickListener { listener.onSetClick(bindingAdapterPosition) }
binding.trackTitle.setOnClickListener { listener.onSetClick(bindingAdapterPosition) }
binding.trackTitle.setOnLongClickListener {
@ -30,8 +30,6 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
binding.trackStatus.setOnClickListener { listener.onStatusClick(bindingAdapterPosition) }
binding.trackChapters.setOnClickListener { listener.onChaptersClick(bindingAdapterPosition) }
binding.trackScore.setOnClickListener { listener.onScoreClick(bindingAdapterPosition) }
binding.trackStartDate.setOnClickListener { listener.onStartDateClick(bindingAdapterPosition) }
binding.trackFinishDate.setOnClickListener { listener.onFinishDateClick(bindingAdapterPosition) }
}
@SuppressLint("SetTextI18n")
@ -42,6 +40,7 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
binding.trackSet.isVisible = track == null
binding.trackTitle.isVisible = track != null
binding.more.isVisible = track != null
binding.middleRow.isVisible = track != null
binding.bottomDivider.isVisible = track != null
@ -77,20 +76,55 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
if (track.started_reading_date != 0L) {
binding.trackStartDate.text = dateFormat.format(track.started_reading_date)
binding.trackStartDate.alpha = SET_STATUS_TEXT_ALPHA
binding.trackStartDate.setOnClickListener {
it.popupMenu(R.menu.track_item_date) {
when (itemId) {
R.id.action_edit -> listener.onStartDateEditClick(bindingAdapterPosition)
R.id.action_remove -> listener.onStartDateRemoveClick(bindingAdapterPosition)
}
}
}
} else {
binding.trackStartDate.text = ctx.getString(R.string.track_started_reading_date)
binding.trackStartDate.alpha = UNSET_STATUS_TEXT_ALPHA
binding.trackStartDate.setOnClickListener {
listener.onStartDateEditClick(bindingAdapterPosition)
}
}
if (track.finished_reading_date != 0L) {
binding.trackFinishDate.text = dateFormat.format(track.finished_reading_date)
binding.trackFinishDate.alpha = SET_STATUS_TEXT_ALPHA
binding.trackFinishDate.setOnClickListener {
it.popupMenu(R.menu.track_item_date) {
when (itemId) {
R.id.action_edit -> listener.onFinishDateEditClick(bindingAdapterPosition)
R.id.action_remove -> listener.onFinishDateRemoveClick(bindingAdapterPosition)
}
}
}
} else {
binding.trackFinishDate.text = ctx.getString(R.string.track_finished_reading_date)
binding.trackFinishDate.alpha = UNSET_STATUS_TEXT_ALPHA
binding.trackFinishDate.setOnClickListener {
listener.onFinishDateEditClick(bindingAdapterPosition)
}
}
}
binding.bottomDivider.isVisible = supportsReadingDates
binding.bottomRow.isVisible = supportsReadingDates
binding.more.setOnClickListener {
it.popupMenu(R.menu.track_item) {
when (itemId) {
R.id.action_open_in_browser -> {
listener.onOpenInBrowserClick(bindingAdapterPosition)
}
R.id.action_remove -> {
listener.onRemoveItemClick(bindingAdapterPosition)
}
}
}
}
}
}

View file

@ -74,14 +74,9 @@ class TrackSearchDialog : DialogController {
dialog?.dismiss()
}
}
R.id.remove -> {
trackController.presenter.unregisterTracking(service)
dialog?.dismiss()
}
}
true
}
binding!!.toolbar.menu.findItem(R.id.remove).isVisible = currentTrackUrl != null
// Create adapter
adapter = TrackSearchAdapter(currentTrackUrl) { which ->

View file

@ -3,9 +3,14 @@ package eu.kanade.tachiyomi.ui.manga.track
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import eu.kanade.tachiyomi.R.string
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointBackward
import com.google.android.material.datepicker.DateValidatorPointForward
import com.google.android.material.datepicker.MaterialDatePicker
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.track.UnattendedTrackService
import eu.kanade.tachiyomi.databinding.TrackControllerBinding
@ -13,6 +18,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.toUtcCalendar
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.toast
@ -23,13 +29,13 @@ import uy.kohesive.injekt.api.get
class TrackSheet(
val controller: MangaController,
val manga: Manga,
val fragmentManager: FragmentManager,
private val sourceManager: SourceManager = Injekt.get()
) : BaseBottomSheetDialog(controller.activity!!),
TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener,
SetTrackChaptersDialog.Listener,
SetTrackScoreDialog.Listener,
SetTrackReadingDatesDialog.Listener {
SetTrackScoreDialog.Listener {
private lateinit var binding: TrackControllerBinding
@ -63,7 +69,7 @@ class TrackSheet(
}
}
override fun onLogoClick(position: Int) {
override fun onOpenInBrowserClick(position: Int) {
val track = adapter.getItem(position)?.track ?: return
if (track.tracking_url.isNotBlank()) {
@ -81,7 +87,7 @@ class TrackSheet(
}
if (!item.service.accept(sourceManager.getOrStub(manga.source))) {
controller.presenter.view?.applicationContext?.toast(string.source_unsupported)
controller.presenter.view?.applicationContext?.toast(R.string.source_unsupported)
return
}
@ -90,9 +96,9 @@ class TrackSheet(
item.service.match(manga)?.let { track ->
controller.presenter.registerTracking(track, item.service)
}
?: withUIContext { controller.presenter.view?.applicationContext?.toast(string.error_no_match) }
?: withUIContext { controller.presenter.view?.applicationContext?.toast(R.string.error_no_match) }
} catch (e: Exception) {
withUIContext { controller.presenter.view?.applicationContext?.toast(string.error_no_match) }
withUIContext { controller.presenter.view?.applicationContext?.toast(R.string.error_no_match) }
}
}
} else {
@ -128,18 +134,74 @@ class TrackSheet(
SetTrackScoreDialog(controller, this, item).showDialog(controller.router)
}
override fun onStartDateClick(position: Int) {
override fun onStartDateEditClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
SetTrackReadingDatesDialog(controller, this, SetTrackReadingDatesDialog.ReadingDate.Start, item).showDialog(controller.router)
val selection = item.track.started_reading_date.toUtcCalendar()?.timeInMillis
?: MaterialDatePicker.todayInUtcMilliseconds()
// No time travellers allowed
val constraints = CalendarConstraints.Builder().apply {
val finishedMillis = item.track.finished_reading_date.toUtcCalendar()?.timeInMillis
if (finishedMillis != null) {
setValidator(DateValidatorPointBackward.before(finishedMillis))
}
}.build()
val picker = MaterialDatePicker.Builder.datePicker()
.setTitleText(R.string.track_started_reading_date)
.setSelection(selection)
.setCalendarConstraints(constraints)
.build()
picker.addOnPositiveButtonClickListener {
controller.presenter.setTrackerStartDate(item, it)
}
picker.show(fragmentManager, null)
}
override fun onFinishDateClick(position: Int) {
override fun onFinishDateEditClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
SetTrackReadingDatesDialog(controller, this, SetTrackReadingDatesDialog.ReadingDate.Finish, item).showDialog(controller.router)
val selection = item.track.finished_reading_date.toUtcCalendar()?.timeInMillis
?: MaterialDatePicker.todayInUtcMilliseconds()
// No time travellers allowed
val constraints = CalendarConstraints.Builder().apply {
val startMillis = item.track.started_reading_date.toUtcCalendar()?.timeInMillis
if (startMillis != null) {
setValidator(DateValidatorPointForward.from(item.track.started_reading_date))
}
}.build()
val picker = MaterialDatePicker.Builder.datePicker()
.setTitleText(R.string.track_finished_reading_date)
.setSelection(selection)
.setCalendarConstraints(constraints)
.build()
picker.addOnPositiveButtonClickListener {
controller.presenter.setTrackerFinishDate(item, it)
}
picker.show(fragmentManager, null)
}
override fun onStartDateRemoveClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
controller.presenter.setTrackerStartDate(item, 0)
}
override fun onFinishDateRemoveClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
controller.presenter.setTrackerFinishDate(item, 0)
}
override fun onRemoveItemClick(position: Int) {
val item = adapter.getItem(position) ?: return
if (item.track == null) return
controller.presenter.unregisterTracking(item.service)
}
override fun setStatus(item: TrackItem, selection: Int) {
@ -154,13 +216,6 @@ class TrackSheet(
controller.presenter.setTrackerScore(item, score)
}
override fun setReadingDate(item: TrackItem, type: SetTrackReadingDatesDialog.ReadingDate, date: Long) {
when (type) {
SetTrackReadingDatesDialog.ReadingDate.Start -> controller.presenter.setTrackerStartDate(item, date)
SetTrackReadingDatesDialog.ReadingDate.Finish -> controller.presenter.setTrackerFinishDate(item, date)
}
}
fun getSearchDialog(): TrackSearchDialog? {
return controller.router.getControllerWithTag(TAG_SEARCH_CONTROLLER) as? TrackSearchDialog
}

View file

@ -4,7 +4,7 @@ import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.mikepenz.aboutlibraries.LibsBuilder
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
@ -133,10 +133,10 @@ class AboutController : SettingsController(), NoToolbarElevationController {
)
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(res = R.string.update_check_notification_update_available)
.message(text = args.getString(BODY_KEY) ?: "")
.positiveButton(R.string.update_check_confirm) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.update_check_notification_update_available)
.setMessage(args.getString(BODY_KEY) ?: "")
.setPositiveButton(R.string.update_check_confirm) { _, _ ->
val appContext = applicationContext
if (appContext != null) {
// Start download
@ -144,7 +144,8 @@ class AboutController : SettingsController(), NoToolbarElevationController {
UpdaterService.start(appContext, url)
}
}
.negativeButton(R.string.update_check_ignore)
.setNegativeButton(R.string.update_check_ignore, null)
.create()
}
private companion object {

View file

@ -29,6 +29,7 @@ import androidx.core.graphics.ColorUtils
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
@ -603,6 +604,9 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
viewer?.setChapters(viewerChapters)
binding.toolbar.subtitle = viewerChapters.currChapter.chapter.name
val currentChapterPageCount = viewerChapters.currChapter.pages?.size ?: 1
binding.readerSeekbar.isInvisible = currentChapterPageCount == 1
val leftChapterObject = if (viewer is R2LPagerViewer) viewerChapters.nextChapter else viewerChapters.prevChapter
val rightChapterObject = if (viewer is R2LPagerViewer) viewerChapters.prevChapter else viewerChapters.nextChapter

View file

@ -19,17 +19,14 @@ class ReaderNavigationOverlayView(context: Context, attributeSet: AttributeSet)
private var navigation: ViewerNavigation? = null
fun setNavigation(navigation: ViewerNavigation, showOnStart: Boolean) {
if (!showOnStart && this.navigation == null) {
this.navigation = navigation
isVisible = false
return
}
fun setNavigation(navigation: ViewerNavigation, tappingEnabled: Boolean, showOnStart: Boolean) {
val firstLaunch = this.navigation == null
this.navigation = navigation
invalidate()
if (isVisible) return
if (isVisible || (!showOnStart && firstLaunch) || !tappingEnabled) {
return
}
viewPropertyAnimator = animate()
.alpha(1f)

View file

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.reader
import android.view.LayoutInflater
import android.view.View
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.ReaderPageSheetBinding
import eu.kanade.tachiyomi.source.model.Page
@ -35,13 +35,12 @@ class ReaderPageSheet(
private fun setAsCover() {
if (page.status != Page.READY) return
MaterialDialog(activity)
.message(R.string.confirm_set_image_as_cover)
.positiveButton(android.R.string.ok) {
MaterialAlertDialogBuilder(activity)
.setMessage(R.string.confirm_set_image_as_cover)
.setPositiveButton(android.R.string.ok) { _, _ ->
activity.setAsCover(page)
dismiss()
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.show()
}

View file

@ -713,7 +713,7 @@ class ReaderPresenter(
// for a while. The view can still be garbage collected.
async {
runCatching {
service.update(track)
service.update(track, true)
db.insertTrack(track).executeAsBlocking()
}
}

View file

@ -128,7 +128,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
config.navigationModeChangedListener = {
val showOnStart = config.navigationOverlayOnStart || config.forceNavigationOverlay
activity.binding.navigationOverlay.setNavigation(config.navigator, showOnStart)
activity.binding.navigationOverlay.setNavigation(config.navigator, config.tappingEnabled, showOnStart)
}
}

View file

@ -129,7 +129,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
config.navigationModeChangedListener = {
val showOnStart = config.navigationOverlayOnStart || config.forceNavigationOverlay
activity.binding.navigationOverlay.setNavigation(config.navigator, showOnStart)
activity.binding.navigationOverlay.setNavigation(config.navigator, config.tappingEnabled, showOnStart)
}
frame.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)

View file

@ -9,7 +9,7 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
@ -23,9 +23,11 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.browse.ProgressItem
import eu.kanade.tachiyomi.ui.browse.animesource.browse.ProgressItem
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.player.PlayerActivity
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -109,6 +111,9 @@ class AnimeHistoryController :
} else {
adapter?.onLoadMoreComplete(animeAnimeHistory)
}
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
}
/**
@ -219,12 +224,13 @@ class AnimeHistoryController :
class ClearAnimeHistoryDialogController : DialogController() {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.clear_history_confirmation)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setMessage(R.string.clear_history_confirmation)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? AnimeHistoryController)?.clearAnimeHistory()
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}

View file

@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.recent.animehistory
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Anime
import eu.kanade.tachiyomi.data.database.models.AnimeHistory
@ -33,11 +32,12 @@ class RemoveAnimeHistoryDialog<T>(bundle: Bundle? = null) : DialogController(bun
setOptionDescription(R.string.dialog_with_checkbox_reset_anime)
}
return MaterialDialog(activity)
.title(R.string.action_remove)
.customView(view = dialogCheckboxView, horizontalPadding = true)
.positiveButton(R.string.action_remove) { onPositive(dialogCheckboxView.isChecked()) }
.negativeButton(android.R.string.cancel)
return MaterialAlertDialogBuilder(activity)
.setTitle(R.string.action_remove)
.setView(dialogCheckboxView)
.setPositiveButton(R.string.action_remove) { _, _ -> onPositive(dialogCheckboxView.isChecked()) }
.setNegativeButton(android.R.string.cancel, null)
.create()
}
private fun onPositive(checked: Boolean) {

View file

@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.player.PlayerActivity
import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
@ -233,6 +234,9 @@ class AnimeUpdatesController :
fun onNextRecentEpisodes(episodes: List<IFlexible<*>>) {
destroyActionModeIfNeeded()
adapter?.updateDataSet(episodes)
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
}
override fun onUpdateEmptyView(size: Int) {

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.recent.animeupdates
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -18,12 +18,13 @@ class ConfirmDeleteEpisodesDialog<T>(bundle: Bundle? = null) : DialogController(
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.confirm_delete_episodes)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setMessage(R.string.confirm_delete_episodes)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? Listener)?.deleteEpisodes(episodesToDelete)
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -9,7 +9,7 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
@ -23,9 +23,11 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.browse.ProgressItem
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -110,6 +112,9 @@ class HistoryController :
} else {
adapter?.onLoadMoreComplete(mangaHistory)
}
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
}
/**
@ -221,12 +226,13 @@ class HistoryController :
class ClearHistoryDialogController : DialogController() {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.clear_history_confirmation)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setMessage(R.string.clear_history_confirmation)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? HistoryController)?.clearHistory()
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}

View file

@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.recent.history
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga
@ -33,11 +32,12 @@ class RemoveHistoryDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
setOptionDescription(R.string.dialog_with_checkbox_reset)
}
return MaterialDialog(activity)
.title(R.string.action_remove)
.customView(view = dialogCheckboxView, horizontalPadding = true)
.positiveButton(R.string.action_remove) { onPositive(dialogCheckboxView.isChecked()) }
.negativeButton(android.R.string.cancel)
return MaterialAlertDialogBuilder(activity)
.setTitle(R.string.action_remove)
.setView(dialogCheckboxView)
.setPositiveButton(R.string.action_remove) { _, _ -> onPositive(dialogCheckboxView.isChecked()) }
.setNegativeButton(android.R.string.cancel, null)
.create()
}
private fun onPositive(checked: Boolean) {

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.recent.updates
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Controller
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.controller.DialogController
@ -18,12 +18,13 @@ class ConfirmDeleteChaptersDialog<T>(bundle: Bundle? = null) : DialogController(
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.confirm_delete_chapters)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setMessage(R.string.confirm_delete_chapters)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? Listener)?.deleteChapters(chaptersToDelete)
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChaptersAdapter
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
@ -224,6 +225,9 @@ class UpdatesController :
fun onNextRecentChapters(chapters: List<IFlexible<*>>) {
destroyActionModeIfNeeded()
adapter?.updateDataSet(chapters)
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
}
override fun onUpdateEmptyView(size: Int) {

View file

@ -8,7 +8,7 @@ import android.os.Bundle
import android.provider.Settings
import androidx.core.net.toUri
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService
@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
@ -90,6 +91,16 @@ class SettingsAdvancedController : SettingsController() {
}
}
preference {
key = "pref_dont_kill_my_app"
title = "Don't kill my app!"
summaryRes = R.string.about_dont_kill_my_app
onClick {
openInBrowser("https://dontkillmyapp.com/")
}
}
preferenceCategory {
titleRes = R.string.label_data
@ -191,12 +202,13 @@ class SettingsAdvancedController : SettingsController() {
class ClearDatabaseDialogController : DialogController() {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.message(R.string.clear_database_confirmation)
.positiveButton(android.R.string.ok) {
return MaterialAlertDialogBuilder(activity!!)
.setMessage(R.string.clear_database_confirmation)
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? SettingsAdvancedController)?.clearDatabase()
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}

View file

@ -9,12 +9,12 @@ import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupConst
@ -246,29 +246,34 @@ class SettingsBackupController : SettingsController() {
R.string.history
)
.map { activity.getString(it) }
val selected = options.map { true }.toBooleanArray()
return MaterialDialog(activity)
.title(R.string.pref_create_backup)
.message(R.string.backup_choice)
.listItemsMultiChoice(
items = options,
disabledIndices = intArrayOf(0),
initialSelection = intArrayOf(0, 1, 2, 3, 4)
) { _, positions, _ ->
return MaterialAlertDialogBuilder(activity)
.setTitle(R.string.backup_choice)
.setMultiChoiceItems(options.toTypedArray(), selected) { dialog, which, checked ->
if (which == 0) {
(dialog as AlertDialog).listView.setItemChecked(which, true)
} else {
selected[which] = checked
}
}
.setPositiveButton(R.string.action_create) { _, _ ->
var flags = 0
for (i in 1 until positions.size) {
when (positions[i]) {
1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
3 -> flags = flags or BackupCreateService.BACKUP_TRACK
4 -> flags = flags or BackupCreateService.BACKUP_HISTORY
selected.forEachIndexed { i, checked ->
if (checked) {
when (i) {
1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
3 -> flags = flags or BackupCreateService.BACKUP_TRACK
4 -> flags = flags or BackupCreateService.BACKUP_HISTORY
}
}
}
(targetController as? SettingsBackupController)?.createBackup(flags)
}
.positiveButton(R.string.action_create)
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}
@ -306,17 +311,19 @@ class SettingsBackupController : SettingsController() {
message += "\n\n${activity.getString(R.string.backup_restore_missing_trackers)}\n${results.missingTrackers.joinToString("\n") { "- $it" }}"
}
MaterialDialog(activity)
.title(R.string.pref_restore_backup)
.message(text = message)
.positiveButton(R.string.action_restore) {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.pref_restore_backup)
.setMessage(message)
.setPositiveButton(R.string.action_restore) { _, _ ->
BackupRestoreService.start(activity, uri, type)
}
.create()
} catch (e: Exception) {
MaterialDialog(activity)
.title(R.string.invalid_backup_file)
.message(text = e.message)
.positiveButton(android.R.string.cancel)
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.invalid_backup_file)
.setMessage(e.message)
.setPositiveButton(android.R.string.cancel, null)
.create()
}
}

View file

@ -10,8 +10,7 @@ import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.text.buildSpannedString
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -21,8 +20,8 @@ import eu.kanade.tachiyomi.data.preference.asImmediateFlow
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.preference.*
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateCheckBox
import eu.kanade.tachiyomi.widget.materialdialogs.listItemsQuadStateMultiChoice
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.Injekt
@ -220,20 +219,22 @@ class SettingsDownloadController : SettingsController() {
val activity = activity!!
val currentDir = preferences.downloadsDirectory().get()
val externalDirs = (getExternalDirs() + File(activity.getString(R.string.custom_dir))).map(File::toString)
val selectedIndex = externalDirs.indexOfFirst { it in currentDir }
var selectedIndex = externalDirs.indexOfFirst { it in currentDir }
return MaterialDialog(activity)
.listItemsSingleChoice(
items = externalDirs,
initialSelection = selectedIndex
) { _, position, text ->
return MaterialAlertDialogBuilder(activity)
.setTitle(R.string.pref_download_directory)
.setSingleChoiceItems(externalDirs.toTypedArray(), selectedIndex) { _, which ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) { _, _ ->
val target = targetController as? SettingsDownloadController
if (position == externalDirs.lastIndex) {
if (selectedIndex == externalDirs.lastIndex) {
target?.customDirectorySelected()
} else {
target?.predefinedDirectorySelected(text.toString())
target?.predefinedDirectorySelected(externalDirs[selectedIndex])
}
}
.create()
}
private fun getExternalDirs(): List<File> {
@ -256,30 +257,33 @@ class SettingsDownloadController : SettingsController() {
val categories = listOf(Category.createDefault()) + dbCategories
val items = categories.map { it.name }
val preselected = categories
var selected = categories
.map {
when (it.id.toString()) {
in preferences.downloadNewCategories().get() -> QuadStateCheckBox.State.CHECKED.ordinal
in preferences.downloadNewCategoriesExclude().get() -> QuadStateCheckBox.State.INVERSED.ordinal
else -> QuadStateCheckBox.State.UNCHECKED.ordinal
in preferences.downloadNewCategories().get() -> QuadStateTextView.State.CHECKED.ordinal
in preferences.downloadNewCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal
else -> QuadStateTextView.State.UNCHECKED.ordinal
}
}
.toIntArray()
return MaterialDialog(activity!!)
.title(R.string.categories)
.message(R.string.pref_download_new_categories_details)
.listItemsQuadStateMultiChoice(
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.categories)
.setMessage(R.string.pref_download_new_categories_details)
.setQuadStateMultiChoiceItems(
items = items,
initialSelected = preselected
initialSelected = selected
) { selections ->
val included = selections
.mapIndexed { index, value -> if (value == QuadStateCheckBox.State.CHECKED.ordinal) index else null }
selected = selections
}
.setPositiveButton(android.R.string.ok) { _, _ ->
val included = selected
.mapIndexed { index, value -> if (value == QuadStateTextView.State.CHECKED.ordinal) index else null }
.filterNotNull()
.map { categories[it].id.toString() }
.toSet()
val excluded = selections
.mapIndexed { index, value -> if (value == QuadStateCheckBox.State.INVERSED.ordinal) index else null }
val excluded = selected
.mapIndexed { index, value -> if (value == QuadStateTextView.State.INVERSED.ordinal) index else null }
.filterNotNull()
.map { categories[it].id.toString() }
.toSet()
@ -287,8 +291,8 @@ class SettingsDownloadController : SettingsController() {
preferences.downloadNewCategories().set(included)
preferences.downloadNewCategoriesExclude().set(excluded)
}
.positiveButton(android.R.string.ok)
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}

View file

@ -2,12 +2,11 @@ package eu.kanade.tachiyomi.ui.setting
import android.app.Dialog
import android.os.Bundle
import android.view.View
import android.view.LayoutInflater
import androidx.core.content.ContextCompat
import androidx.core.text.buildSpannedString
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateJob
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -18,6 +17,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.UNMETERED_NETWORK
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.databinding.PrefLibraryColumnsBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.category.CategoryController
@ -33,9 +33,8 @@ import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.isTablet
import eu.kanade.tachiyomi.widget.MinMaxNumberPicker
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateCheckBox
import eu.kanade.tachiyomi.widget.materialdialogs.listItemsQuadStateMultiChoice
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -302,21 +301,21 @@ class SettingsLibraryController : SettingsController() {
private var landscape = preferences.landscapeColumns().get()
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dialog = MaterialDialog(activity!!)
.title(R.string.pref_library_columns)
.customView(R.layout.pref_library_columns, horizontalPadding = true)
.positiveButton(android.R.string.ok) {
val binding = PrefLibraryColumnsBinding.inflate(LayoutInflater.from(activity!!))
onViewCreated(binding)
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.pref_library_columns)
.setView(binding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
preferences.portraitColumns().set(portrait)
preferences.landscapeColumns().set(landscape)
}
.negativeButton(android.R.string.cancel)
onViewCreated(dialog.view)
return dialog
.setNegativeButton(android.R.string.cancel, null)
.create()
}
fun onViewCreated(view: View) {
with(view.findViewById(R.id.portrait_columns) as MinMaxNumberPicker) {
fun onViewCreated(binding: PrefLibraryColumnsBinding) {
with(binding.portraitColumns) {
displayedValues = arrayOf(context.getString(R.string.default_columns)) +
IntRange(1, 10).map(Int::toString)
value = portrait
@ -325,7 +324,7 @@ class SettingsLibraryController : SettingsController() {
portrait = newValue
}
}
with(view.findViewById(R.id.landscape_columns) as MinMaxNumberPicker) {
with(binding.landscapeColumns) {
displayedValues = arrayOf(context.getString(R.string.default_columns)) +
IntRange(1, 10).map(Int::toString)
value = landscape
@ -347,30 +346,30 @@ class SettingsLibraryController : SettingsController() {
val categories = listOf(Category.createDefault()) + dbCategories
val items = categories.map { it.name }
val preselected = categories
var selected = categories
.map {
when (it.id.toString()) {
in preferences.libraryUpdateCategories().get() -> QuadStateCheckBox.State.CHECKED.ordinal
in preferences.libraryUpdateCategoriesExclude().get() -> QuadStateCheckBox.State.INVERSED.ordinal
else -> QuadStateCheckBox.State.UNCHECKED.ordinal
in preferences.libraryUpdateCategories().get() -> QuadStateTextView.State.CHECKED.ordinal
in preferences.libraryUpdateCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal
else -> QuadStateTextView.State.UNCHECKED.ordinal
}
}
.toIntArray()
return MaterialDialog(activity!!)
.title(R.string.categories)
.message(R.string.pref_library_update_categories_details)
.listItemsQuadStateMultiChoice(
items = items,
initialSelected = preselected
) { selections ->
val included = selections
.mapIndexed { index, value -> if (value == QuadStateCheckBox.State.CHECKED.ordinal) index else null }
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.categories)
.setMessage(R.string.pref_library_update_categories_details)
.setQuadStateMultiChoiceItems(items = items, initialSelected = selected) { selections ->
selected = selections
}
.setPositiveButton(android.R.string.ok) { _, _ ->
val included = selected
.mapIndexed { index, value -> if (value == QuadStateTextView.State.CHECKED.ordinal) index else null }
.filterNotNull()
.map { categories[it].id.toString() }
.toSet()
val excluded = selections
.mapIndexed { index, value -> if (value == QuadStateCheckBox.State.INVERSED.ordinal) index else null }
val excluded = selected
.mapIndexed { index, value -> if (value == QuadStateTextView.State.INVERSED.ordinal) index else null }
.filterNotNull()
.map { categories[it].id.toString() }
.toSet()
@ -378,8 +377,8 @@ class SettingsLibraryController : SettingsController() {
preferences.libraryUpdateCategories().set(included)
preferences.libraryUpdateCategoriesExclude().set(excluded)
}
.positiveButton(android.R.string.ok)
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}
}

View file

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.setting.track
import android.app.Dialog
import android.os.Bundle
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
@ -20,14 +20,15 @@ class TrackLogoutDialog(bundle: Bundle? = null) : DialogController(bundle) {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val serviceName = activity!!.getString(service.nameRes())
return MaterialDialog(activity!!)
.title(text = activity!!.getString(R.string.logout_title, serviceName))
.positiveButton(R.string.logout) {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(activity!!.getString(R.string.logout_title, serviceName))
.setPositiveButton(R.string.logout) { _, _ ->
service.logout()
(targetController as? Listener)?.trackLogoutDialogClosed(service)
activity?.toast(R.string.logout_success)
}
.negativeButton(android.R.string.cancel)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.util.lang
import java.text.DateFormat
import java.util.Calendar
import java.util.Date
import java.util.TimeZone
fun Date.toDateTimestampString(dateFormatter: DateFormat): String {
val date = dateFormatter.format(this)
@ -43,3 +44,28 @@ fun Long.toCalendar(): Calendar? {
cal.timeInMillis = this
return cal
}
/**
* Convert epoch long to Calendar instance in UTC
*
* @return UTC Calendar instance at supplied epoch time. Null if epoch was 0.
*/
fun Long.toUtcCalendar(): Calendar? {
if (this == 0L) {
return null
}
val rawCalendar = Calendar.getInstance().apply {
timeInMillis = this@toUtcCalendar
}
return Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
clear()
set(
rawCalendar.get(Calendar.YEAR),
rawCalendar.get(Calendar.MONTH),
rawCalendar.get(Calendar.DAY_OF_MONTH),
rawCalendar.get(Calendar.HOUR_OF_DAY),
rawCalendar.get(Calendar.MINUTE),
rawCalendar.get(Calendar.SECOND)
)
}
}

View file

@ -197,3 +197,20 @@ inline fun TextView.setMaxLinesAndEllipsize(_ellipsize: TextUtils.TruncateAt = T
maxLines = (measuredHeight - paddingTop - paddingBottom) / lineHeight
ellipsize = _ellipsize
}
/**
* Callback will be run immediately when no animation running
*/
fun RecyclerView.onAnimationsFinished(callback: (RecyclerView) -> Unit) = post(
object : Runnable {
override fun run() {
if (isAnimating) {
itemAnimator?.isRunning {
post(this)
}
} else {
callback(this@onAnimationsFinished)
}
}
}
)

View file

@ -0,0 +1,54 @@
package eu.kanade.tachiyomi.widget.materialdialogs
import android.view.LayoutInflater
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.core.content.getSystemService
import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.databinding.DialogStubQuadstatemultichoiceBinding
import eu.kanade.tachiyomi.databinding.DialogStubTextinputBinding
fun MaterialAlertDialogBuilder.setTextInput(
hint: String? = null,
prefill: String? = null,
onTextChanged: (String) -> Unit
): MaterialAlertDialogBuilder {
val binding = DialogStubTextinputBinding.inflate(LayoutInflater.from(context))
binding.textField.hint = hint
binding.textField.editText?.apply {
setText(prefill, TextView.BufferType.EDITABLE)
doAfterTextChanged {
onTextChanged(it?.toString() ?: "")
}
post {
requestFocusFromTouch()
context.getSystemService<InputMethodManager>()?.showSoftInput(this, 0)
}
}
return setView(binding.root)
}
/**
* Sets a list of items with checkboxes that supports 4 states.
*
* @see eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
*/
fun MaterialAlertDialogBuilder.setQuadStateMultiChoiceItems(
items: List<CharSequence>,
initialSelected: IntArray,
disabledIndices: IntArray? = null,
selection: QuadStateMultiChoiceListener
): MaterialAlertDialogBuilder {
val binding = DialogStubQuadstatemultichoiceBinding.inflate(LayoutInflater.from(context))
binding.list.layoutManager = LinearLayoutManager(context)
binding.list.adapter = QuadStateMultiChoiceDialogAdapter(
items = items,
disabledItems = disabledIndices,
initialSelected = initialSelected,
listener = selection
)
setView(binding.root)
return this
}

View file

@ -1,26 +0,0 @@
package eu.kanade.tachiyomi.widget.materialdialogs
import androidx.annotation.CheckResult
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.customListAdapter
/**
* A variant of listItemsMultiChoice that allows for checkboxes that supports 4 states instead.
*/
@CheckResult
fun MaterialDialog.listItemsQuadStateMultiChoice(
items: List<CharSequence>,
disabledIndices: IntArray? = null,
initialSelected: IntArray = IntArray(items.size),
selection: QuadStateMultiChoiceListener
): MaterialDialog {
return customListAdapter(
QuadStateMultiChoiceDialogAdapter(
dialog = this,
items = items,
disabledItems = disabledIndices,
initialSelected = initialSelected,
selection = selection
)
)
}

View file

@ -1,34 +0,0 @@
package eu.kanade.tachiyomi.widget.materialdialogs
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.view.setVectorCompat
class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
AppCompatImageView(context, attrs) {
var state: State = State.UNCHECKED
set(value) {
field = value
updateDrawable()
}
private fun updateDrawable() {
when (state) {
State.UNCHECKED -> setVectorCompat(R.drawable.ic_check_box_outline_blank_24dp, R.attr.colorControlNormal)
State.INDETERMINATE -> setVectorCompat(R.drawable.ic_indeterminate_check_box_24dp, R.attr.colorAccent)
State.CHECKED -> setVectorCompat(R.drawable.ic_check_box_24dp, R.attr.colorAccent)
State.INVERSED -> setVectorCompat(R.drawable.ic_check_box_x_24dp, R.attr.colorAccent)
}
}
enum class State {
UNCHECKED,
INDETERMINATE,
CHECKED,
INVERSED,
;
}
}

View file

@ -1,14 +1,9 @@
package eu.kanade.tachiyomi.widget.materialdialogs
import android.view.View
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.internal.list.DialogAdapter
import com.afollestad.materialdialogs.list.getItemSelector
import com.afollestad.materialdialogs.utils.MDUtil.inflate
import com.afollestad.materialdialogs.utils.MDUtil.maybeSetTextColor
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DialogQuadstatemultichoiceItemBinding
private object CheckPayload
private object InverseCheckPayload
@ -17,15 +12,13 @@ private object UncheckPayload
typealias QuadStateMultiChoiceListener = (indices: IntArray) -> Unit
internal class QuadStateMultiChoiceDialogAdapter(
private var dialog: MaterialDialog,
internal var items: List<CharSequence>,
disabledItems: IntArray?,
initialSelected: IntArray,
internal var selection: QuadStateMultiChoiceListener
) : RecyclerView.Adapter<QuadStateMultiChoiceViewHolder>(),
DialogAdapter<CharSequence, QuadStateMultiChoiceListener> {
internal var listener: QuadStateMultiChoiceListener
) : RecyclerView.Adapter<QuadStateMultiChoiceViewHolder>() {
private val states = QuadStateCheckBox.State.values()
private val states = QuadStateTextView.State.values()
private var currentSelection: IntArray = initialSelected
set(value) {
@ -34,15 +27,15 @@ internal class QuadStateMultiChoiceDialogAdapter(
previousSelection.forEachIndexed { index, previous ->
val current = value[index]
when {
current == QuadStateCheckBox.State.CHECKED.ordinal && previous != QuadStateCheckBox.State.CHECKED.ordinal -> {
current == QuadStateTextView.State.CHECKED.ordinal && previous != QuadStateTextView.State.CHECKED.ordinal -> {
// This value was selected
notifyItemChanged(index, CheckPayload)
}
current == QuadStateCheckBox.State.INVERSED.ordinal && previous != QuadStateCheckBox.State.INVERSED.ordinal -> {
current == QuadStateTextView.State.INVERSED.ordinal && previous != QuadStateTextView.State.INVERSED.ordinal -> {
// This value was inverse selected
notifyItemChanged(index, InverseCheckPayload)
}
current == QuadStateCheckBox.State.UNCHECKED.ordinal && previous != QuadStateCheckBox.State.UNCHECKED.ordinal -> {
current == QuadStateTextView.State.UNCHECKED.ordinal && previous != QuadStateTextView.State.UNCHECKED.ordinal -> {
// This value was unselected
notifyItemChanged(index, UncheckPayload)
}
@ -54,26 +47,24 @@ internal class QuadStateMultiChoiceDialogAdapter(
internal fun itemClicked(index: Int) {
val newSelection = this.currentSelection.toMutableList()
newSelection[index] = when (currentSelection[index]) {
QuadStateCheckBox.State.CHECKED.ordinal -> QuadStateCheckBox.State.INVERSED.ordinal
QuadStateCheckBox.State.INVERSED.ordinal -> QuadStateCheckBox.State.UNCHECKED.ordinal
QuadStateTextView.State.CHECKED.ordinal -> QuadStateTextView.State.INVERSED.ordinal
QuadStateTextView.State.INVERSED.ordinal -> QuadStateTextView.State.UNCHECKED.ordinal
// INDETERMINATE or UNCHECKED
else -> QuadStateCheckBox.State.CHECKED.ordinal
else -> QuadStateTextView.State.CHECKED.ordinal
}
this.currentSelection = newSelection.toIntArray()
listener(currentSelection)
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): QuadStateMultiChoiceViewHolder {
val listItemView: View = parent.inflate(dialog.windowContext, R.layout.md_listitem_quadstatemultichoice)
val viewHolder = QuadStateMultiChoiceViewHolder(
itemView = listItemView,
return QuadStateMultiChoiceViewHolder(
itemBinding = DialogQuadstatemultichoiceItemBinding
.inflate(LayoutInflater.from(parent.context), parent, false),
adapter = this
)
viewHolder.titleView.maybeSetTextColor(dialog.windowContext, R.attr.md_color_content)
return viewHolder
}
override fun getItemCount() = items.size
@ -83,14 +74,8 @@ internal class QuadStateMultiChoiceDialogAdapter(
position: Int
) {
holder.isEnabled = !disabledIndices.contains(position)
holder.controlView.state = states[currentSelection[position]]
holder.titleView.text = items[position]
holder.itemView.background = dialog.getItemSelector()
if (dialog.bodyFont != null) {
holder.titleView.typeface = dialog.bodyFont
}
holder.controlView.text = items[position]
}
override fun onBindViewHolder(
@ -100,88 +85,18 @@ internal class QuadStateMultiChoiceDialogAdapter(
) {
when (payloads.firstOrNull()) {
CheckPayload -> {
holder.controlView.state = QuadStateCheckBox.State.CHECKED
holder.controlView.state = QuadStateTextView.State.CHECKED
return
}
InverseCheckPayload -> {
holder.controlView.state = QuadStateCheckBox.State.INVERSED
holder.controlView.state = QuadStateTextView.State.INVERSED
return
}
UncheckPayload -> {
holder.controlView.state = QuadStateCheckBox.State.UNCHECKED
holder.controlView.state = QuadStateTextView.State.UNCHECKED
return
}
}
super.onBindViewHolder(holder, position, payloads)
}
override fun positiveButtonClicked() {
selection.invoke(currentSelection)
}
override fun replaceItems(
items: List<CharSequence>,
listener: QuadStateMultiChoiceListener?
) {
this.items = items
if (listener != null) {
this.selection = listener
}
this.notifyDataSetChanged()
}
override fun disableItems(indices: IntArray) {
this.disabledIndices = indices
notifyDataSetChanged()
}
override fun checkItems(indices: IntArray) {
val newSelection = this.currentSelection.toMutableList()
for (index in indices) {
newSelection[index] = QuadStateCheckBox.State.CHECKED.ordinal
}
this.currentSelection = newSelection.toIntArray()
}
override fun uncheckItems(indices: IntArray) {
val newSelection = this.currentSelection.toMutableList()
for (index in indices) {
newSelection[index] = QuadStateCheckBox.State.UNCHECKED.ordinal
}
this.currentSelection = newSelection.toIntArray()
}
override fun toggleItems(indices: IntArray) {
val newSelection = this.currentSelection.toMutableList()
for (index in indices) {
if (this.disabledIndices.contains(index)) {
continue
}
if (this.currentSelection[index] != QuadStateCheckBox.State.CHECKED.ordinal) {
newSelection[index] = QuadStateCheckBox.State.CHECKED.ordinal
} else {
newSelection[index] = QuadStateCheckBox.State.UNCHECKED.ordinal
}
}
this.currentSelection = newSelection.toIntArray()
}
override fun checkAllItems() {
this.currentSelection = IntArray(itemCount) { QuadStateCheckBox.State.CHECKED.ordinal }
}
override fun uncheckAllItems() {
this.currentSelection = IntArray(itemCount) { QuadStateCheckBox.State.UNCHECKED.ordinal }
}
override fun toggleAllChecked() {
if (this.currentSelection.any { it != QuadStateCheckBox.State.CHECKED.ordinal }) {
checkAllItems()
} else {
uncheckAllItems()
}
}
override fun isItemChecked(index: Int) = this.currentSelection[index] == QuadStateCheckBox.State.CHECKED.ordinal
}

View file

@ -1,27 +1,24 @@
package eu.kanade.tachiyomi.widget.materialdialogs
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DialogQuadstatemultichoiceItemBinding
internal class QuadStateMultiChoiceViewHolder(
itemView: View,
itemBinding: DialogQuadstatemultichoiceItemBinding,
private val adapter: QuadStateMultiChoiceDialogAdapter
) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
) : RecyclerView.ViewHolder(itemBinding.root), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
val controlView: QuadStateCheckBox = itemView.findViewById(R.id.md_quad_state_control)
val titleView: TextView = itemView.findViewById(R.id.md_quad_state_title)
val controlView = itemBinding.quadStateControl
var isEnabled: Boolean
get() = itemView.isEnabled
set(value) {
itemView.isEnabled = value
controlView.isEnabled = value
titleView.isEnabled = value
}
override fun onClick(view: View) = adapter.itemClicked(bindingAdapterPosition)

View file

@ -0,0 +1,45 @@
package eu.kanade.tachiyomi.widget.materialdialogs
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import com.mikepenz.aboutlibraries.util.getThemeColor
import eu.kanade.tachiyomi.R
class QuadStateTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
AppCompatTextView(context, attrs) {
var state: State = State.UNCHECKED
set(value) {
field = value
updateDrawable()
}
private fun updateDrawable() {
val drawableStartId = when (state) {
State.UNCHECKED -> R.drawable.ic_check_box_outline_blank_24dp
State.INDETERMINATE -> R.drawable.ic_indeterminate_check_box_24dp
State.CHECKED -> R.drawable.ic_check_box_24dp
State.INVERSED -> R.drawable.ic_check_box_x_24dp
}
setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStartId, 0, 0, 0)
val tint = if (state == State.UNCHECKED) {
context.getThemeColor(R.attr.colorControlNormal)
} else {
context.getThemeColor(R.attr.colorAccent)
}
if (tint != 0) {
compoundDrawableTintList = ColorStateList.valueOf(tint)
}
}
enum class State {
UNCHECKED,
INDETERMINATE,
CHECKED,
INVERSED,
;
}
}

View file

@ -5,11 +5,10 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.annotation.StringRes
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.dd.processbutton.iml.ActionProcessButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.PrefAccountLoginBinding
@ -28,15 +27,13 @@ abstract class LoginDialogPreference(
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
binding = PrefAccountLoginBinding.inflate(LayoutInflater.from(activity!!))
onViewCreated(binding!!.root)
val titleName = activity!!.getString(getTitleName())
val dialog = MaterialDialog(activity!!)
.title(text = activity!!.getString(R.string.login_title, titleName))
.customView(view = binding!!.root)
.negativeButton(android.R.string.cancel)
onViewCreated(dialog.view)
return dialog
return MaterialAlertDialogBuilder(activity!!)
.setTitle(activity!!.getString(R.string.login_title, titleName))
.setView(binding!!.root)
.setNegativeButton(android.R.string.cancel, null)
.create()
}
fun onViewCreated(view: View) {

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View file

@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splash" />
<item
android:width="72dp"
android:height="72dp"
android:drawable="@drawable/ic_tachi"
android:gravity="center" />
</layer-list>

Some files were not shown because too many files have changed in this diff Show more