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 // UI
implementation("com.dmitrymalkovich.android:material-design-dimens:1.4") 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:${Versions.FASTADAPTER}")
implementation("com.mikepenz:fastadapter-extensions-binding:${Versions.FASTADAPTER}") implementation("com.mikepenz:fastadapter-extensions-binding:${Versions.FASTADAPTER}")
implementation("eu.davidea:flexible-adapter:5.1.0") 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 preferences: PreferencesHelper by injectLazy()
val networkService: NetworkHelper by injectLazy() val networkService: NetworkHelper by injectLazy()
open fun canRemoveFromService() = false
open val client: OkHttpClient open val client: OkHttpClient
get() = networkService.client get() = networkService.client
@ -51,6 +51,8 @@ abstract class TrackService(val id: Int) {
abstract suspend fun login(username: String, password: String): Boolean abstract suspend fun login(username: String, password: String): Boolean
open suspend fun removeFromService(track: Track): Boolean = false
@CallSuper @CallSuper
open fun logout() { open fun logout() {
preferences.setTrackCredentials(this, "", "") 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 search(query: String) = api.search(query)
override suspend fun refresh(track: Track): Track { override suspend fun refresh(track: Track): Track {

View File

@ -19,6 +19,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import timber.log.Timber
import java.util.Calendar import java.util.Calendar
import java.util.concurrent.TimeUnit 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 { fun createOAuth(token: String): OAuth {
return OAuth( return OAuth(
token, token,
@ -222,6 +246,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|""".trimMargin() |""".trimMargin()
fun deleteFromLibraryQuery() = """
|mutation DeleteManga(${'$'}listId: Int) {
|DeleteMediaListEntry (id: ${'$'}listId) {
|deleted
|}
|}""".trimMargin()
fun updateInLibraryQuery() = """ fun updateInLibraryQuery() = """
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) { |mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) { |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> { override suspend fun search(query: String): List<TrackSearch> {
return api.search(query) return api.search(query)
} }

View File

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

View File

@ -79,6 +79,12 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
return track 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> { override suspend fun search(query: String): List<TrackSearch> {
return api.search(query) return api.search(query)
} }

View File

@ -23,6 +23,7 @@ import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.parser.Parser import org.jsoup.parser.Parser
import timber.log.Timber
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) { class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
@ -71,6 +72,16 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
return track 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? { suspend fun findLibManga(track: Track): Track? {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val response = authClient.newCall(GET(url = listEntryUrl(track.media_id))).await() val response = authClient.newCall(GET(url = listEntryUrl(track.media_id))).await()
@ -188,6 +199,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
private fun updateUrl() = private fun updateUrl() =
Uri.parse(baseModifyListUrl).buildUpon().appendPath("edit.json").toString() 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() = private fun addUrl() =
Uri.parse(baseModifyListUrl).buildUpon().appendPath("add.json").toString() 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, private val downloadedColor = ContextCompat.getColor(context,
R.color.download) R.color.download)
private val errorColor = ContextCompat.getColor(context, private val errorColor = ContextCompat.getColor(context,
R.color.red_error) R.color.material_red_500)
private val filledCircle = ContextCompat.getDrawable(context, private val filledCircle = ContextCompat.getDrawable(context,
R.drawable.filled_circle)?.mutate() R.drawable.filled_circle)?.mutate()
private val borderCircle = ContextCompat.getDrawable(context, private val borderCircle = ContextCompat.getDrawable(context,

View File

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

View File

@ -45,5 +45,6 @@ class TrackAdapter(controller: OnClickListener) : RecyclerView.Adapter<TrackHold
fun onStatusClick(position: Int) fun onStatusClick(position: Int)
fun onChaptersClick(position: Int) fun onChaptersClick(position: Int)
fun onScoreClick(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) } logo_container.setOnClickListener { listener.onLogoClick(adapterPosition) }
add_tracking.setOnClickListener { listener.onSetClick(adapterPosition) } add_tracking.setOnClickListener { listener.onSetClick(adapterPosition) }
track_title.setOnClickListener { listener.onSetClick(adapterPosition) } track_title.setOnClickListener { listener.onSetClick(adapterPosition) }
track_remove.setOnClickListener { listener.onRemoveClick(adapterPosition) }
track_status.setOnClickListener { listener.onStatusClick(adapterPosition) } track_status.setOnClickListener { listener.onStatusClick(adapterPosition) }
track_chapters.setOnClickListener { listener.onChaptersClick(adapterPosition) } track_chapters.setOnClickListener { listener.onChaptersClick(adapterPosition) }
score_container.setOnClickListener { listener.onScoreClick(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 lateinit var bottomSheet: TrackingBottomSheet
private var wasPreviouslyTracked: Boolean = false private var wasPreviouslyTracked: Boolean = false
private lateinit var presenter: MangaDetailsPresenter private lateinit var presenter: MangaDetailsPresenter
constructor(target: TrackingBottomSheet, service: TrackService, wasTracked: Boolean) : super(Bundle() constructor(target: TrackingBottomSheet, service: TrackService, wasTracked: Boolean) : super(Bundle()
@ -61,9 +62,6 @@ class TrackSearchDialog : DialogController {
val dialog = MaterialDialog(activity!!).apply { val dialog = MaterialDialog(activity!!).apply {
customView(viewRes = R.layout.track_search_dialog, scrollable = false) customView(viewRes = R.layout.track_search_dialog, scrollable = false)
negativeButton(android.R.string.cancel) negativeButton(android.R.string.cancel)
if (wasPreviouslyTracked) {
positiveButton(R.string.clear) { onPositiveButtonClick() }
}
} }
if (subscriptions.isUnsubscribed) { if (subscriptions.isUnsubscribed) {
@ -151,12 +149,6 @@ class TrackSearchDialog : DialogController {
adapter?.setItems(emptyList()) adapter?.setItems(emptyList())
} }
private fun onPositiveButtonClick() {
bottomSheet.refreshTrack(service)
presenter.registerTracking(null,
service)
}
private companion object { private companion object {
const val KEY_SERVICE = "service_id" const val KEY_SERVICE = "service_id"
} }

View File

@ -24,7 +24,8 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : Bott
TrackAdapter.OnClickListener, TrackAdapter.OnClickListener,
SetTrackStatusDialog.Listener, SetTrackStatusDialog.Listener,
SetTrackChaptersDialog.Listener, SetTrackChaptersDialog.Listener,
SetTrackScoreDialog.Listener { SetTrackScoreDialog.Listener,
TrackRemoveDialog.Listener {
val activity = controller.activity!! val activity = controller.activity!!
@ -145,6 +146,18 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : Bott
SetTrackStatusDialog(this, item).showDialog(controller.router) 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) { override fun onChaptersClick(position: Int) {
val item = adapter?.getItem(position) ?: return val item = adapter?.getItem(position) ?: return
if (item.track == null) return if (item.track == null) return
@ -197,6 +210,11 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : Bott
refreshItem(item) refreshItem(item)
} }
override fun removeTracker(item: TrackItem, fromServiceAlso: Boolean) {
refreshTrack(item.service)
presenter.removeTracker(item, fromServiceAlso)
}
private companion object { private companion object {
const val TAG_SEARCH_CONTROLLER = "track_search_controller" 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.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.R 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.util.system.getResourceColor
import eu.kanade.tachiyomi.widget.preference.LoginPreference import eu.kanade.tachiyomi.widget.preference.LoginPreference
import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
import eu.kanade.tachiyomi.widget.preference.TrackLogoutDialog
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
class SettingsTrackingController : SettingsController(), class SettingsTrackingController : SettingsController(),
TrackLoginDialog.Listener { TrackLoginDialog.Listener, TrackLogoutDialog.Listener {
private val trackManager: TrackManager by injectLazy() private val trackManager: TrackManager by injectLazy()
@ -34,43 +36,27 @@ class SettingsTrackingController : SettingsController(),
trackPreference(trackManager.myAnimeList) { trackPreference(trackManager.myAnimeList) {
onClick { onClick {
val dialog = TrackLoginDialog(trackManager.myAnimeList) showDialog(trackManager.myAnimeList)
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
} }
} }
trackPreference(trackManager.aniList) { trackPreference(trackManager.aniList) {
onClick { onClick {
val tabsIntent = CustomTabsIntent.Builder() showDialog(trackManager.aniList, AnilistApi.authUrl())
.setToolbarColor(context.getResourceColor(R.attr.colorPrimaryVariant))
.build()
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
tabsIntent.launchUrl(activity!!, AnilistApi.authUrl())
} }
} }
trackPreference(trackManager.kitsu) { trackPreference(trackManager.kitsu) {
onClick { onClick {
val dialog = TrackLoginDialog(trackManager.kitsu, context.getString(R.string.email)) showDialog(trackManager.kitsu, userNameLabel = context.getString(R.string.email))
dialog.targetController = this@SettingsTrackingController
dialog.showDialog(router)
} }
} }
trackPreference(trackManager.shikimori) { trackPreference(trackManager.shikimori) {
onClick { onClick {
val tabsIntent = CustomTabsIntent.Builder() showDialog(trackManager.shikimori, ShikimoriApi.authUrl())
.setToolbarColor(context.getResourceColor(R.attr.colorPrimaryVariant))
.build()
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
tabsIntent.launchUrl(activity!!, ShikimoriApi.authUrl())
} }
} }
trackPreference(trackManager.bangumi) { trackPreference(trackManager.bangumi) {
onClick { onClick {
val tabsIntent = CustomTabsIntent.Builder() showDialog(trackManager.bangumi, BangumiApi.authUrl())
.setToolbarColor(context.getResourceColor(R.attr.colorPrimaryVariant))
.build()
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
tabsIntent.launchUrl(activity!!, BangumiApi.authUrl())
} }
} }
} }
@ -91,6 +77,25 @@ class SettingsTrackingController : SettingsController(),
// Manually refresh anilist holder // Manually refresh anilist holder
updatePreference(trackManager.aniList.id) updatePreference(trackManager.aniList.id)
updatePreference(trackManager.shikimori.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) { private fun updatePreference(id: Int) {
@ -98,7 +103,11 @@ class SettingsTrackingController : SettingsController(),
pref?.notifyChanged() pref?.notifyChanged()
} }
override fun trackDialogClosed(service: TrackService) { override fun trackLoginDialogClosed(service: TrackService) {
updatePreference(service.id)
}
override fun trackLogoutDialogClosed(service: TrackService) {
updatePreference(service.id) updatePreference(service.id)
} }
} }

View File

@ -2,18 +2,14 @@ package eu.kanade.tachiyomi.widget.preference
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.text.method.PasswordTransformationMethod
import android.view.View import android.view.View
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.customview.customView
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.dd.processbutton.iml.ActionProcessButton
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.DialogController 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.android.synthetic.main.pref_account_login.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -42,7 +38,6 @@ abstract class LoginDialogPreference(
override fun onCreateDialog(savedViewState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dialog = MaterialDialog(activity!!).apply { val dialog = MaterialDialog(activity!!).apply {
customView(R.layout.pref_account_login, scrollable = false) customView(R.layout.pref_account_login, scrollable = false)
positiveButton(android.R.string.cancel)
} }
onViewCreated(dialog.view) onViewCreated(dialog.view)
@ -50,40 +45,18 @@ abstract class LoginDialogPreference(
return dialog return dialog
} }
open fun logout() {}
fun onViewCreated(view: View) { fun onViewCreated(view: View) {
v = view.apply { v = view.apply {
show_password.setOnCheckedChangeListener { _, isChecked ->
if (isChecked)
password.transformationMethod = null
else
password.transformationMethod = PasswordTransformationMethod()
}
if (!usernameLabel.isNullOrEmpty()) { if (!usernameLabel.isNullOrEmpty()) {
username_label.text = usernameLabel username_input.hint = usernameLabel
} }
login.setMode(ActionProcessButton.Mode.ENDLESS) login.setOnClickListener {
login.setOnClickListener { checkLogin() } checkLogin()
}
setCredentialsOnView(this) 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.os.Bundle
import android.view.View import android.view.View
import br.com.simplepass.loadingbutton.animatedDrawables.ProgressType
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
@ -18,8 +19,6 @@ class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
override var canLogout = true override var canLogout = true
constructor(service: TrackService) : this(service, null)
constructor(service: TrackService, usernameLabel: String?) : constructor(service: TrackService, usernameLabel: String?) :
this(usernameLabel, Bundle().apply { putInt("key", service.id) }) this(usernameLabel, Bundle().apply { putInt("key", service.id) })
@ -32,13 +31,18 @@ class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
override fun checkLogin() { override fun checkLogin() {
v?.apply { 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 return
}
login.progress = 1
val user = username.text.toString() val user = username.text.toString()
val pass = password.text.toString() val pass = password.text.toString()
scope.launch { scope.launch {
try { try {
val result = service.login(user, pass) val result = service.login(user, pass)
@ -58,24 +62,18 @@ class TrackLoginDialog(usernameLabel: String? = null, bundle: Bundle? = null) :
private fun errorResult() { private fun errorResult() {
v?.apply { v?.apply {
login.progress = -1 login.revertAnimation {
login.setText(R.string.unknown_error) login.text = activity!!.getText(R.string.unknown_error)
} }
} }
override fun logout() {
if (service.isLogged) {
service.logout()
activity?.toast(R.string.successfully_logged_out)
}
} }
override fun onDialogClosed() { override fun onDialogClosed() {
super.onDialogClosed() super.onDialogClosed()
(targetController as? Listener)?.trackDialogClosed(service) (targetController as? Listener)?.trackLoginDialogClosed(service)
} }
interface Listener { 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"?> <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:background="@color/red_error"> tools:background="@color/material_red_500">
<item <item
android:bottom="1dp" android:bottom="1dp"
android:left="-2dp" android:left="-2dp"

View File

@ -3,7 +3,7 @@
<item android:state_activated="true" android:color="@color/md_white_1000"> <item android:state_activated="true" android:color="@color/md_white_1000">
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<corners android:radius="2dp" /> <corners android:radius="2dp" />
<solid android:color="@color/red_error" /> <solid android:color="@color/material_red_500" />
<padding android:left="8dp" android:right="8dp" /> <padding android:left="8dp" android:right="8dp" />
</shape> </shape>
</item> </item>
@ -15,11 +15,11 @@
<padding android:left="8dp" android:right="8dp" /> <padding android:left="8dp" android:right="8dp" />
</shape> </shape>
</item> </item>
<item android:color="@color/red_error"> <item android:color="@color/material_red_500">
<shape android:color="@color/red_error" android:shape="rectangle"> <shape android:color="@color/material_red_500" android:shape="rectangle">
<corners android:radius="2dp" /> <corners android:radius="2dp" />
<solid android:color="@android:color/transparent" /> <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" /> <padding android:left="8dp" android:right="8dp" />
</shape> </shape>
</item> </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:height="100dp"
android:viewportHeight="10" android:viewportHeight="10"
android:viewportWidth="100" android:viewportWidth="100"
tools:background="@color/red_error"> tools:background="@color/material_red_500">
<path <path
android:fillColor="@color/unread_badge" android:fillColor="@color/unread_badge"

View File

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

View File

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

View File

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

View File

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

View File

@ -31,9 +31,9 @@
<ImageView <ImageView
android:id="@+id/track_logo" android:id="@+id/track_logo"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:contentDescription="@string/tracking"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:contentDescription="@string/tracking"
tools:src="@drawable/ic_tracker_mal" /> tools:src="@drawable/ic_tracker_mal" />
<ProgressBar <ProgressBar
@ -70,11 +70,28 @@
android:text="@string/title" android:text="@string/title"
android:textSize="14sp" android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@id/track_remove"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="dfdffggjdfigjssdfgidfjgidgjdifgjfdgifdjgid" /> 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> </androidx.constraintlayout.widget.ConstraintLayout>
<View <View
@ -122,9 +139,9 @@
android:layout_height="0dp" android:layout_height="0dp"
android:background="@drawable/card_item_selector" android:background="@drawable/card_item_selector"
android:gravity="center" android:gravity="center"
android:maxLines="2"
android:paddingStart="6dp" android:paddingStart="6dp"
android:paddingEnd="6dp" android:paddingEnd="6dp"
android:maxLines="2"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/score_container" app:layout_constraintEnd_toStartOf="@id/score_container"
app:layout_constraintStart_toEndOf="@id/track_status" app:layout_constraintStart_toEndOf="@id/track_status"
@ -159,10 +176,10 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:alpha="0.25" android:alpha="0.25"
android:background="@color/strong_divider" android:background="@color/strong_divider"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/track_chapters"
app:layout_constraintStart_toEndOf="@+id/track_status" app:layout_constraintStart_toEndOf="@+id/track_status"
app:layout_constraintEnd_toStartOf="@id/track_chapters"/> app:layout_constraintTop_toTopOf="parent" />
<View <View
android:layout_width="1dp" android:layout_width="1dp"
@ -171,10 +188,10 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:alpha="0.25" android:alpha="0.25"
android:background="@color/strong_divider" android:background="@color/strong_divider"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/score_container"
app:layout_constraintStart_toEndOf="@+id/track_chapters" app:layout_constraintStart_toEndOf="@+id/track_chapters"
app:layout_constraintEnd_toStartOf="@id/score_container"/> app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton

View File

@ -1,34 +1,48 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<EditText <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
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:id="@+id/track_search"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/title" android:hint="@string/title"
android:layout_marginEnd="16dp" android:inputType="text" />
android:layout_marginStart="16dp"
android:inputType="text" </com.google.android.material.textfield.TextInputLayout>
android:maxLines="1"/>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"> android:layout_weight="1"
android:elevation="12dp"
>
<ProgressBar <ProgressBar
android:id="@+id/progress" android:id="@+id/progress"
style="?android:attr/progressBarStyle" style="?android:attr/progressBarStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:layout_marginTop="32dp"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
android:visibility="invisible" android:visibility="invisible"
tools:visibility="visible" /> tools:visibility="visible" />
@ -37,15 +51,15 @@
style="@style/Theme.Widget.CardView" style="@style/Theme.Widget.CardView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false"
android:choiceMode="singleChoice" android:choiceMode="singleChoice"
android:clipToPadding="false"
android:divider="@null" android:divider="@null"
android:dividerHeight="10dp" android:dividerHeight="10dp"
android:footerDividersEnabled="true" android:footerDividersEnabled="true"
android:headerDividersEnabled="true" android:headerDividersEnabled="true"
android:listSelector="@drawable/list_item_selector" android:listSelector="@drawable/list_item_selector"
android:paddingBottom="4dp"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingBottom="4dp"
android:scrollbars="none" android:scrollbars="none"
android:visibility="invisible" android:visibility="invisible"
tools:listitem="@layout/track_search_item" tools:listitem="@layout/track_search_item"
@ -53,9 +67,5 @@
</FrameLayout> </FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/divider"/>
</LinearLayout> </LinearLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?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:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -15,10 +15,9 @@
android:id="@+id/track_search_cover" android:id="@+id/track_search_cover"
android:layout_width="135dp" android:layout_width="135dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginBottom="8dp" android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/cover_of_image" android:contentDescription="@string/cover_of_image"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -26,6 +25,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" app:layout_constraintVertical_bias="0.0"
tools:src="@mipmap/ic_launcher" /> tools:src="@mipmap/ic_launcher" />
<TextView <TextView
android:id="@+id/track_search_title" android:id="@+id/track_search_title"
android:layout_width="0dp" android:layout_width="0dp"
@ -147,4 +147,4 @@
</androidx.constraintlayout.widget.ConstraintLayout> </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="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="refresh_tracking">Refresh tracking</string>
<string name="add_tracking">Add 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 --> <!-- Migration -->
<string name="select_sources">Select sources</string> <string name="select_sources">Select sources</string>
@ -582,7 +584,7 @@
<string name="website">Website</string> <string name="website">Website</string>
<string name="open_source_licenses">Open source licenses</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="log_in_to_">Log in to %1$s</string>
<string name="username">Username</string> <string name="username">Username</string>
<string name="email">Email address</string> <string name="email">Email address</string>
@ -590,7 +592,11 @@
<string name="show_password">Show password</string> <string name="show_password">Show password</string>
<string name="login">Login</string> <string name="login">Login</string>
<string name="logout">Logout</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="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="successfully_logged_out">You are now logged out</string>
<string name="could_not_log_in">Could not log in</string> <string name="could_not_log_in">Could not log in</string>

View File

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

View File

@ -1,7 +1,7 @@
object Versions { object Versions {
const val ACRA = "4.9.2" const val ACRA = "4.9.2"
const val CHUCKER = "3.2.0" 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 COROUTINES = "1.3.5"
const val FASTADAPTER = "5.0.0" const val FASTADAPTER = "5.0.0"
const val HYPERION = "0.9.27" const val HYPERION = "0.9.27"