Tracker improvements (#473)

* add tracker logout dialog from upstream

* update for all trackers to have logout dialog

* remove logout button from login screen

* new login button that is more material
had to remove red_error color cause that was in the library that i removed

* return error message if user has blank username/password

* add x button to clear tracked Manga
add dialog that gives option to clear just in app or from tracking service
added mal as first service to allow clearing

* fix string in dialog for remove
ad ability to remove from anilist service

* add ability to delete from kitsu

* made login dialog look more material

* change the dialog for the remove from tracker

* update coil to 0.11.0

* make track search a little nicer
This commit is contained in:
Carlos 2020-06-06 19:53:13 -04:00 committed by GitHub
parent 585e57c8bf
commit 442a439e66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 425 additions and 214 deletions

View File

@ -196,7 +196,7 @@ dependencies {
// UI
implementation("com.dmitrymalkovich.android:material-design-dimens:1.4")
implementation("com.github.dmytrodanylyk.android-process-button:library:1.0.4")
implementation("br.com.simplepass:loading-button-android:2.2.0")
implementation("com.mikepenz:fastadapter:${Versions.FASTADAPTER}")
implementation("com.mikepenz:fastadapter-extensions-binding:${Versions.FASTADAPTER}")
implementation("eu.davidea:flexible-adapter:5.1.0")

View File

@ -13,7 +13,7 @@ abstract class TrackService(val id: Int) {
val preferences: PreferencesHelper by injectLazy()
val networkService: NetworkHelper by injectLazy()
open fun canRemoveFromService() = false
open val client: OkHttpClient
get() = networkService.client
@ -51,6 +51,8 @@ abstract class TrackService(val id: Int) {
abstract suspend fun login(username: String, password: String): Boolean
open suspend fun removeFromService(track: Track): Boolean = false
@CallSuper
open fun logout() {
preferences.setTrackCredentials(this, "", "")

View File

@ -150,6 +150,12 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun canRemoveFromService(): Boolean = true
override suspend fun removeFromService(track: Track): Boolean {
return api.remove(track)
}
override suspend fun search(query: String) = api.search(query)
override suspend fun refresh(track: Track): Track {

View File

@ -19,6 +19,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import timber.log.Timber
import java.util.Calendar
import java.util.concurrent.TimeUnit
@ -128,6 +129,29 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
suspend fun remove(track: Track): Boolean {
return withContext(Dispatchers.IO) {
try {
val variables = jsonObject(
"listId" to track.library_id
)
val payload = jsonObject(
"query" to deleteFromLibraryQuery(),
"variables" to variables
)
val body = payload.toString().toRequestBody(MediaType.jsonType())
val request = Request.Builder().url(apiUrl).post(body).build()
val result = authClient.newCall(request).execute()
return@withContext true
} catch (e: Exception) {
Timber.w(e)
}
return@withContext false
}
}
fun createOAuth(token: String): OAuth {
return OAuth(
token,
@ -222,6 +246,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|""".trimMargin()
fun deleteFromLibraryQuery() = """
|mutation DeleteManga(${'$'}listId: Int) {
|DeleteMediaListEntry (id: ${'$'}listId) {
|deleted
|}
|}""".trimMargin()
fun updateInLibraryQuery() = """
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {

View File

@ -103,6 +103,12 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
}
}
override fun canRemoveFromService() = true
override suspend fun removeFromService(track: Track): Boolean {
return api.remove(track)
}
override suspend fun search(query: String): List<TrackSearch> {
return api.search(query)
}

View File

@ -16,6 +16,7 @@ import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
@ -25,6 +26,7 @@ import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
import timber.log.Timber
class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) {
@ -97,6 +99,16 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
return track
}
suspend fun remove(track: Track): Boolean {
try {
rest.deleteLibManga(track.media_id)
return true
} catch (e: Exception) {
Timber.w(e)
}
return false
}
suspend fun search(query: String): List<TrackSearch> {
val key = searchRest.getKey()["media"].asJsonObject["key"].string
return algoliaSearch(key, query)
@ -156,6 +168,12 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
@Body data: JsonObject
): JsonObject
@Headers("Content-Type: application/vnd.api+json")
@DELETE("library-entries/{id}")
suspend fun deleteLibManga(
@Path("id") remoteId: Int
): JsonObject
@Headers("Content-Type: application/vnd.api+json")
@PATCH("library-entries/{id}")
suspend fun updateLibManga(

View File

@ -79,6 +79,12 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
return track
}
override fun canRemoveFromService(): Boolean = true
override suspend fun removeFromService(track: Track): Boolean {
return api.remove(track)
}
override suspend fun search(query: String): List<TrackSearch> {
return api.search(query)
}

View File

@ -23,6 +23,7 @@ import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.parser.Parser
import timber.log.Timber
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
@ -40,18 +41,18 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.select("tbody").select("tr").drop(1)
matches.filter { row -> row.select(TD)[2].text() != "Novel" }.map { row ->
TrackSearch.create(TrackManager.MYANIMELIST).apply {
title = row.searchTitle()
media_id = row.searchMediaId()
total_chapters = row.searchTotalChapters()
summary = row.searchSummary()
cover_url = row.searchCoverUrl()
tracking_url = mangaUrl(media_id)
publishing_status = row.searchPublishingStatus()
publishing_type = row.searchPublishingType()
start_date = row.searchStartDate()
}
}.toList()
TrackSearch.create(TrackManager.MYANIMELIST).apply {
title = row.searchTitle()
media_id = row.searchMediaId()
total_chapters = row.searchTotalChapters()
summary = row.searchSummary()
cover_url = row.searchCoverUrl()
tracking_url = mangaUrl(media_id)
publishing_status = row.searchPublishingStatus()
publishing_type = row.searchPublishingType()
start_date = row.searchStartDate()
}
}.toList()
}
}
}
@ -71,6 +72,16 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
return track
}
suspend fun remove(track: Track): Boolean {
try {
authClient.newCall(POST(url = removeUrl(track.media_id))).await()
return true
} catch (e: Exception) {
Timber.w(e)
}
return false
}
suspend fun findLibManga(track: Track): Track? {
return withContext(Dispatchers.IO) {
val response = authClient.newCall(GET(url = listEntryUrl(track.media_id))).await()
@ -133,16 +144,16 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
val results = getListXml(getListUrl()).select("manga")
return results.map {
TrackSearch.create(TrackManager.MYANIMELIST).apply {
title = it.selectText("manga_title")!!
media_id = it.selectInt("manga_mangadb_id")
last_chapter_read = it.selectInt("my_read_chapters")
status = getStatus(it.selectText("my_status")!!)
score = it.selectInt("my_score").toFloat()
total_chapters = it.selectInt("manga_chapters")
tracking_url = mangaUrl(media_id)
}
}.toList()
TrackSearch.create(TrackManager.MYANIMELIST).apply {
title = it.selectText("manga_title")!!
media_id = it.selectInt("manga_mangadb_id")
last_chapter_read = it.selectInt("my_read_chapters")
status = getStatus(it.selectText("my_status")!!)
score = it.selectInt("my_score").toFloat()
total_chapters = it.selectInt("manga_chapters")
tracking_url = mangaUrl(media_id)
}
}.toList()
}
private suspend fun getListUrl(): String {
@ -188,6 +199,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
private fun updateUrl() =
Uri.parse(baseModifyListUrl).buildUpon().appendPath("edit.json").toString()
private fun removeUrl(mediaId: Int) = Uri.parse(baseModifyListUrl).buildUpon().appendPath(mediaId.toString())
.appendPath("delete").toString()
private fun addUrl() =
Uri.parse(baseModifyListUrl).buildUpon().appendPath("add.json").toString()

View File

@ -24,7 +24,7 @@ class DownloadButton @JvmOverloads constructor(context: Context, attrs: Attribut
private val downloadedColor = ContextCompat.getColor(context,
R.color.download)
private val errorColor = ContextCompat.getColor(context,
R.color.red_error)
R.color.material_red_500)
private val filledCircle = ContextCompat.getDrawable(context,
R.drawable.filled_circle)?.mutate()
private val borderCircle = ContextCompat.getDrawable(context,

View File

@ -850,13 +850,18 @@ class MangaDetailsPresenter(
}
fetchTracks()
}
} else {
scope.launch {
withContext(Dispatchers.IO) {
db.deleteTrackForManga(manga, service).executeAsBlocking()
}
}
fun removeTracker(trackItem: TrackItem, removeFromService: Boolean) {
scope.launch {
withContext(Dispatchers.IO) {
db.deleteTrackForManga(manga, trackItem.service).executeAsBlocking()
if (removeFromService && trackItem.service.canRemoveFromService()) {
trackItem.service.removeFromService(trackItem.track!!)
}
fetchTracks()
}
fetchTracks()
}
}

View File

@ -45,5 +45,6 @@ class TrackAdapter(controller: OnClickListener) : RecyclerView.Adapter<TrackHold
fun onStatusClick(position: Int)
fun onChaptersClick(position: Int)
fun onScoreClick(position: Int)
fun onRemoveClick(position: Int)
}
}

View File

@ -17,6 +17,7 @@ class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) {
logo_container.setOnClickListener { listener.onLogoClick(adapterPosition) }
add_tracking.setOnClickListener { listener.onSetClick(adapterPosition) }
track_title.setOnClickListener { listener.onSetClick(adapterPosition) }
track_remove.setOnClickListener { listener.onRemoveClick(adapterPosition) }
track_status.setOnClickListener { listener.onStatusClick(adapterPosition) }
track_chapters.setOnClickListener { listener.onChaptersClick(adapterPosition) }
score_container.setOnClickListener { listener.onScoreClick(adapterPosition) }

View File

@ -0,0 +1,64 @@
package eu.kanade.tachiyomi.ui.manga.track
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.checkbox.checkBoxPrompt
import com.afollestad.materialdialogs.checkbox.getCheckBoxPrompt
import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked
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
class TrackRemoveDialog<T> : DialogController
where T : TrackRemoveDialog.Listener {
private val item: TrackItem
private lateinit var listener: Listener
constructor(target: T, item: TrackItem) : super(Bundle().apply {
putSerializable(KEY_ITEM_TRACK, item.track)
}) {
listener = target
this.item = item
}
@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)
}
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val item = item
val dialog = MaterialDialog(activity!!)
.title(R.string.remove_tracking)
.negativeButton(android.R.string.cancel)
if (item.service.canRemoveFromService()) {
dialog.checkBoxPrompt(
text = activity!!.getString(
R.string.remove_tracking_from_, item.service.name
), onToggle = null
).positiveButton(android.R.string.ok) { listener.removeTracker(item, it.isCheckPromptChecked()) }
dialog.getCheckBoxPrompt().textSize = 16f
} else {
dialog.positiveButton(android.R.string.ok) { listener.removeTracker(item, false) }
}
return dialog
}
interface Listener {
fun removeTracker(item: TrackItem, fromServiceAlso: Boolean)
}
private companion object {
const val KEY_ITEM_TRACK = "TrackRemoveDialog.item.track"
}
}

View File

@ -40,6 +40,7 @@ class TrackSearchDialog : DialogController {
private lateinit var bottomSheet: TrackingBottomSheet
private var wasPreviouslyTracked: Boolean = false
private lateinit var presenter: MangaDetailsPresenter
constructor(target: TrackingBottomSheet, service: TrackService, wasTracked: Boolean) : super(Bundle()
@ -61,9 +62,6 @@ class TrackSearchDialog : DialogController {
val dialog = MaterialDialog(activity!!).apply {
customView(viewRes = R.layout.track_search_dialog, scrollable = false)
negativeButton(android.R.string.cancel)
if (wasPreviouslyTracked) {
positiveButton(R.string.clear) { onPositiveButtonClick() }
}
}
if (subscriptions.isUnsubscribed) {
@ -151,12 +149,6 @@ class TrackSearchDialog : DialogController {
adapter?.setItems(emptyList())
}
private fun onPositiveButtonClick() {
bottomSheet.refreshTrack(service)
presenter.registerTracking(null,
service)
}
private companion object {
const val KEY_SERVICE = "service_id"
}

View File

@ -24,7 +24,8 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : Bott
TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener,
SetTrackChaptersDialog.Listener,
SetTrackScoreDialog.Listener {
SetTrackScoreDialog.Listener,
TrackRemoveDialog.Listener {
val activity = controller.activity!!
@ -145,6 +146,18 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : Bott
SetTrackStatusDialog(this, item).showDialog(controller.router)
}
override fun onRemoveClick(position: Int) {
val item = adapter?.getItem(position) ?: return
if (item.track == null) return
if (controller.isNotOnline()) {
dismiss()
return
}
TrackRemoveDialog(this, item).showDialog(controller.router)
}
override fun onChaptersClick(position: Int) {
val item = adapter?.getItem(position) ?: return
if (item.track == null) return
@ -197,6 +210,11 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : Bott
refreshItem(item)
}
override fun removeTracker(item: TrackItem, fromServiceAlso: Boolean) {
refreshTrack(item.service)
presenter.removeTracker(item, fromServiceAlso)
}
private companion object {
const val TAG_SEARCH_CONTROLLER = "track_search_controller"
}

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.setting
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.R
@ -13,11 +14,12 @@ import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.widget.preference.LoginPreference
import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
import eu.kanade.tachiyomi.widget.preference.TrackLogoutDialog
import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
class SettingsTrackingController : SettingsController(),
TrackLoginDialog.Listener {
TrackLoginDialog.Listener, TrackLogoutDialog.Listener {
private val trackManager: TrackManager by injectLazy()
@ -34,43 +36,27 @@ class SettingsTrackingController : SettingsController(),
trackPreference(trackManager.myAnimeList) {
onClick {
val dialog = TrackLoginDialog(trackManager.myAnimeList)
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
showDialog(trackManager.myAnimeList)
}
}
trackPreference(trackManager.aniList) {
onClick {
val tabsIntent = CustomTabsIntent.Builder()
.setToolbarColor(context.getResourceColor(R.attr.colorPrimaryVariant))
.build()
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
tabsIntent.launchUrl(activity!!, AnilistApi.authUrl())
showDialog(trackManager.aniList, AnilistApi.authUrl())
}
}
trackPreference(trackManager.kitsu) {
onClick {
val dialog = TrackLoginDialog(trackManager.kitsu, context.getString(R.string.email))
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
showDialog(trackManager.kitsu, userNameLabel = context.getString(R.string.email))
}
}
trackPreference(trackManager.shikimori) {
onClick {
val tabsIntent = CustomTabsIntent.Builder()
.setToolbarColor(context.getResourceColor(R.attr.colorPrimaryVariant))
.build()
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
tabsIntent.launchUrl(activity!!, ShikimoriApi.authUrl())
showDialog(trackManager.shikimori, ShikimoriApi.authUrl())
}
}
trackPreference(trackManager.bangumi) {
onClick {
val tabsIntent = CustomTabsIntent.Builder()
.setToolbarColor(context.getResourceColor(R.attr.colorPrimaryVariant))
.build()
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
tabsIntent.launchUrl(activity!!, BangumiApi.authUrl())
showDialog(trackManager.bangumi, BangumiApi.authUrl())
}
}
}
@ -91,6 +77,25 @@ class SettingsTrackingController : SettingsController(),
// Manually refresh anilist holder
updatePreference(trackManager.aniList.id)
updatePreference(trackManager.shikimori.id)
updatePreference(trackManager.bangumi.id)
}
private fun showDialog(trackService: TrackService, url: Uri? = null, userNameLabel: String? = null) {
if (trackService.isLogged) {
val dialog = TrackLogoutDialog(trackService)
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
} else if (url == null) {
val dialog = TrackLoginDialog(trackService, userNameLabel)
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
} else {
val tabsIntent = CustomTabsIntent.Builder()
.setToolbarColor(activity!!.getResourceColor(R.attr.colorPrimaryVariant))
.build()
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
tabsIntent.launchUrl(activity!!, url)
}
}
private fun updatePreference(id: Int) {
@ -98,7 +103,11 @@ class SettingsTrackingController : SettingsController(),
pref?.notifyChanged()
}
override fun trackDialogClosed(service: TrackService) {
override fun trackLoginDialogClosed(service: TrackService) {
updatePreference(service.id)
}
override fun trackLogoutDialogClosed(service: TrackService) {
updatePreference(service.id)
}
}

View File

@ -2,18 +2,14 @@ package eu.kanade.tachiyomi.widget.preference
import android.app.Dialog
import android.os.Bundle
import android.text.method.PasswordTransformationMethod
import android.view.View
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 eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.view.visible
import eu.kanade.tachiyomi.widget.SimpleTextWatcher
import kotlinx.android.synthetic.main.pref_account_login.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -42,7 +38,6 @@ abstract class LoginDialogPreference(
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dialog = MaterialDialog(activity!!).apply {
customView(R.layout.pref_account_login, scrollable = false)
positiveButton(android.R.string.cancel)
}
onViewCreated(dialog.view)
@ -50,40 +45,18 @@ abstract class LoginDialogPreference(
return dialog
}
open fun logout() {}
fun onViewCreated(view: View) {
v = view.apply {
show_password.setOnCheckedChangeListener { _, isChecked ->
if (isChecked)
password.transformationMethod = null
else
password.transformationMethod = PasswordTransformationMethod()
}
if (!usernameLabel.isNullOrEmpty()) {
username_label.text = usernameLabel
username_input.hint = usernameLabel
}
login.setMode(ActionProcessButton.Mode.ENDLESS)
login.setOnClickListener { checkLogin() }
login.setOnClickListener {
checkLogin()
}
setCredentialsOnView(this)
if (canLogout && !username.text.isNullOrEmpty()) {
logout.visible()
logout.setOnClickListener { logout() }
}
show_password.isEnabled = password.text.isNullOrEmpty()
password.addTextChangedListener(object : SimpleTextWatcher() {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (s.isEmpty()) {
show_password.isEnabled = true
}
}
})
}
}

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.widget.preference
import android.os.Bundle
import android.view.View
import br.com.simplepass.loadingbutton.animatedDrawables.ProgressType
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
@ -18,8 +19,6 @@ class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
override var canLogout = true
constructor(service: TrackService) : this(service, null)
constructor(service: TrackService, usernameLabel: String?) :
this(usernameLabel, Bundle().apply { putInt("key", service.id) })
@ -32,13 +31,18 @@ class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
override fun checkLogin() {
v?.apply {
if (username.text.isEmpty() || password.text.isEmpty())
login.apply {
progressType = ProgressType.INDETERMINATE
startAnimation()
}
if (username.text.isNullOrBlank() || password.text.isNullOrBlank()) {
errorResult()
context.toast(R.string.username_must_not_be_blank)
return
}
login.progress = 1
val user = username.text.toString()
val pass = password.text.toString()
scope.launch {
try {
val result = service.login(user, pass)
@ -58,24 +62,18 @@ class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
private fun errorResult() {
v?.apply {
login.progress = -1
login.setText(R.string.unknown_error)
}
}
override fun logout() {
if (service.isLogged) {
service.logout()
activity?.toast(R.string.successfully_logged_out)
login.revertAnimation {
login.text = activity!!.getText(R.string.unknown_error)
}
}
}
override fun onDialogClosed() {
super.onDialogClosed()
(targetController as? Listener)?.trackDialogClosed(service)
(targetController as? Listener)?.trackLoginDialogClosed(service)
}
interface Listener {
fun trackDialogClosed(service: TrackService)
fun trackLoginDialogClosed(service: TrackService)
}
}

View File

@ -0,0 +1,34 @@
package eu.kanade.tachiyomi.widget.preference
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.system.toast
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class TrackLogoutDialog(bundle: Bundle? = null) : DialogController(bundle) {
private val service = Injekt.get<TrackManager>().getService(args.getInt("key"))!!
constructor(service: TrackService) : this(Bundle().apply { putInt("key", service.id) })
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog(activity!!)
.title(text = activity!!.getString(R.string.logout_from_, service.name))
.negativeButton(R.string.cancel)
.positiveButton(R.string.logout) { _ ->
service.logout()
(targetController as? Listener)?.trackLogoutDialogClosed(service)
activity!!.toast(R.string.successfully_logged_out)
}
}
interface Listener {
fun trackLogoutDialogClosed(service: TrackService)
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:background="@color/red_error">
tools:background="@color/material_red_500">
<item
android:bottom="1dp"
android:left="-2dp"

View File

@ -3,7 +3,7 @@
<item android:state_activated="true" android:color="@color/md_white_1000">
<shape android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="@color/red_error" />
<solid android:color="@color/material_red_500" />
<padding android:left="8dp" android:right="8dp" />
</shape>
</item>
@ -15,11 +15,11 @@
<padding android:left="8dp" android:right="8dp" />
</shape>
</item>
<item android:color="@color/red_error">
<shape android:color="@color/red_error" android:shape="rectangle">
<item android:color="@color/material_red_500">
<shape android:color="@color/material_red_500" android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="@android:color/transparent" />
<stroke android:width="1dp" android:color="@color/red_error" />
<stroke android:width="1dp" android:color="@color/material_red_500" />
<padding android:left="8dp" android:right="8dp" />
</shape>
</item>

View File

@ -0,0 +1,8 @@
<!-- drawable/close_circle.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#fff" android:pathData="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z" />
</vector>

View File

@ -5,7 +5,7 @@
android:height="100dp"
android:viewportHeight="10"
android:viewportWidth="100"
tools:background="@color/red_error">
tools:background="@color/material_red_500">
<path
android:fillColor="@color/unread_badge"

View File

@ -10,7 +10,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="@color/red_error">
android:background="@color/material_red_500">
<ImageView
android:id="@+id/close_right"
@ -28,7 +28,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="@color/red_error">
android:background="@color/material_red_500">
<ImageView
android:id="@+id/close_left"

View File

@ -88,8 +88,8 @@
app:atg_isAppendMode="true"
app:atg_inputHintColor="?android:attr/textColorSecondary"
app:atg_inputTextColor="?android:attr/textColorPrimary"
app:atg_checkedBackgroundColor="@color/red_error"
app:atg_checkedBorderColor="@color/red_error"
app:atg_checkedBackgroundColor="@color/material_red_500"
app:atg_checkedBorderColor="@color/material_red_500"
app:atg_borderColor="?attr/colorAccent"
app:atg_textColor="?attr/colorAccent" />

View File

@ -1,77 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/dialog_title"
style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="16sp"
android:textStyle="bold"/>
tools:text="Log in to AniList" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="24dp"
android:layout_marginTop="6dp"
android:background="@color/divider"/>
<TextView
android:id="@+id/username_label"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_input"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/username"/>
android:layout_marginTop="15dp"
android:hint="@string/username"
app:boxStrokeColor="@color/colorAccent"
app:hintTextColor="@color/colorAccent">
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/password"/>
android:layout_marginTop="12dp"
android:hint="@string/password"
app:boxStrokeColor="@color/colorAccent"
app:endIconMode="password_toggle"
app:hintTextColor="@color/colorAccent">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword"/>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<CheckBox
android:id="@+id/show_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/show_password"/>
<com.dd.processbutton.iml.ActionProcessButton
<br.com.simplepass.loadingbutton.customViews.CircularProgressButton
android:id="@+id/login"
style="@style/Theme.Widget.Button.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="55dp"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:scaleType="fitCenter"
android:text="@string/login"
android:textColor="@android:color/white"
app:pb_textComplete="@string/successfully_logged_in"
app:pb_textError="@string/could_not_log_in"
app:pb_textProgress="@string/loading"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/logout"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:background="@color/md_grey_800"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/logout"
android:textColor="?colorAccent"/>
android:textSize="16sp"
app:finalCornerAngle="50dp"
app:initialCornerAngle="2dp"
app:spinning_bar_color="@color/md_white_1000"
app:spinning_bar_padding="6dp"
app:spinning_bar_width="3dp" />
</LinearLayout>

View File

@ -9,7 +9,7 @@
android:id="@+id/right_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/red_error"
android:background="@color/material_red_500"
android:visibility="gone">
<com.google.android.material.textview.MaterialTextView
@ -30,7 +30,7 @@
android:id="@+id/left_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/red_error">
android:background="@color/material_red_500">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/close_left"

View File

@ -31,9 +31,9 @@
<ImageView
android:id="@+id/track_logo"
android:layout_width="wrap_content"
android:contentDescription="@string/tracking"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/tracking"
tools:src="@drawable/ic_tracker_mal" />
<ProgressBar
@ -70,11 +70,28 @@
android:text="@string/title"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/track_remove"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="dfdffggjdfigjssdfgidfjgidgjdifgjfdgifdjgid" />
<ImageView
android:id="@+id/track_remove"
style="@style/Theme.Widget.CustomImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/open_in_webview"
android:src="@drawable/ic_close_circle_24dp"
android:layout_marginStart="2dp"
android:layout_marginEnd="4dp"
android:tint="@color/text_color_secondary"
android:tooltipText="@string/remove_tracking"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/status_container"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@id/track_title"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
@ -122,9 +139,9 @@
android:layout_height="0dp"
android:background="@drawable/card_item_selector"
android:gravity="center"
android:maxLines="2"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:maxLines="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/score_container"
app:layout_constraintStart_toEndOf="@id/track_status"
@ -159,10 +176,10 @@
android:layout_marginBottom="10dp"
android:alpha="0.25"
android:background="@color/strong_divider"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/track_chapters"
app:layout_constraintStart_toEndOf="@+id/track_status"
app:layout_constraintEnd_toStartOf="@id/track_chapters"/>
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="1dp"
@ -171,10 +188,10 @@
android:layout_marginBottom="10dp"
android:alpha="0.25"
android:background="@color/strong_divider"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/score_container"
app:layout_constraintStart_toEndOf="@+id/track_chapters"
app:layout_constraintEnd_toStartOf="@id/score_container"/>
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.button.MaterialButton

View File

@ -1,61 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/track_search"
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:inputType="text"
android:maxLines="1"/>
android:layout_marginEnd="16dp"
android:hint="@string/title"
app:boxStrokeColor="@color/colorAccent"
app:endIconMode="clear_text"
app:hintEnabled="false"
app:hintTextColor="@color/colorAccent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/track_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:layout_weight="1"
android:elevation="12dp"
>
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:layout_marginTop="32dp"
android:layout_gravity="center"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
android:visibility="invisible"
tools:visibility="visible"/>
tools:visibility="visible" />
<ListView
android:id="@+id/track_search_list"
style="@style/Theme.Widget.CardView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:choiceMode="singleChoice"
android:clipToPadding="false"
android:divider="@null"
android:dividerHeight="10dp"
android:footerDividersEnabled="true"
android:headerDividersEnabled="true"
android:listSelector="@drawable/list_item_selector"
android:paddingBottom="4dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:scrollbars="none"
android:visibility="invisible"
tools:listitem="@layout/track_search_item"
tools:visibility="visible"/>
tools:visibility="visible" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/divider"/>
</LinearLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView style="@style/Theme.Widget.CardView.Item"
<com.google.android.material.card.MaterialCardView style="@style/Theme.Widget.CardView.Item"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@ -15,17 +15,17 @@
android:id="@+id/track_search_cover"
android:layout_width="135dp"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/cover_of_image"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:src="@mipmap/ic_launcher"/>
tools:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/track_search_title"
android:layout_width="0dp"
@ -147,4 +147,4 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</com.google.android.material.card.MaterialCardView>

View File

@ -421,6 +421,8 @@
<string name="url_not_set_click_again">Manga URL not set, please click title and select manga again</string>
<string name="refresh_tracking">Refresh tracking</string>
<string name="add_tracking">Add tracking</string>
<string name="remove_tracking">Remove tracking from app</string>
<string name="remove_tracking_from_">Also remove from %1$s</string>
<!-- Migration -->
<string name="select_sources">Select sources</string>
@ -582,7 +584,7 @@
<string name="website">Website</string>
<string name="open_source_licenses">Open source licenses</string>
<!-- Login dialog -->
<!-- Login/Logout dialog -->
<string name="log_in_to_">Log in to %1$s</string>
<string name="username">Username</string>
<string name="email">Email address</string>
@ -590,7 +592,11 @@
<string name="show_password">Show password</string>
<string name="login">Login</string>
<string name="logout">Logout</string>
<string name="logout_from_">Logout from %1$s?</string>
<string name="successfully_logged_in">Successfully logged in</string>
<string name="username_must_not_be_blank">Username or password cannot be blank</string>
<string name="successfully_logged_out">You are now logged out</string>
<string name="could_not_log_in">Could not log in</string>

View File

@ -5,8 +5,7 @@
<!--Toolbars-->
<!--========-->
<style name="Theme.ActionBar.Dark.DayNight"
parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<style name="Theme.ActionBar.Dark.DayNight" parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<item name="popupTheme">@style/ThemeOverlay.MaterialComponents.Light</item>
</style>
@ -202,7 +201,7 @@
<!--==============-->
<!--Widgets.Button-->
<!--==============-->
<style name="Theme.Widget.Button" parent="Widget.AppCompat.Button"/>
<style name="Theme.Widget.Button" parent="Widget.AppCompat.Button" />
<style name="Theme.Widget.Button.Colored" parent="Widget.MaterialComponents.Button">
<item name="backgroundTint">?attr/colorAccent</item>
@ -223,8 +222,7 @@
<item name="android:minWidth">48dip</item>
</style>
<style name="Theme.Widget.Button.RoundedOutline"
parent="Widget.MaterialComponents.Button.OutlinedButton.Icon">
<style name="Theme.Widget.Button.RoundedOutline" parent="Widget.MaterialComponents.Button.OutlinedButton.Icon">
<item name="android:layout_width">wrap_content</item>
<item name="android:textAllCaps">false</item>
<item name="android:letterSpacing">0.0</item>
@ -261,6 +259,7 @@
<item name="android:tint">?colorAccent</item>
</style>
<!--===-->
<!--OLD-->
<!--===-->

View File

@ -28,7 +28,7 @@
<item name="android:divider">@color/divider</item>
<item name="android:listDivider">@drawable/line_divider</item>
<item name="actionModeStyle">@style/CustomActionModeStyle</item>
<!-- Themes -->
<item name="windowActionModeOverlay">true</item>
<item name="actionBarTheme">@style/ThemeOverlay.AppCompat.DayNight.ActionBar</item>

View File

@ -1,7 +1,7 @@
object Versions {
const val ACRA = "4.9.2"
const val CHUCKER = "3.2.0"
const val COIL = "0.10.1"
const val COIL = "0.11.0"
const val COROUTINES = "1.3.5"
const val FASTADAPTER = "5.0.0"
const val HYPERION = "0.9.27"