From e1f82d147c956dd0c62fe8466dcb966572c7b631 Mon Sep 17 00:00:00 2001 From: Zakhar Timoshenko Date: Tue, 9 May 2023 17:28:15 +0300 Subject: [PATCH 1/5] Fix ActionMode background on Android Lollipop --- .../org/koitharu/kotatsu/base/ui/BaseActivity.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt index 78099ae3e..c9474a173 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.base.ui import android.content.Intent import android.content.res.Configuration import android.graphics.Color +import android.os.Build import android.os.Bundle import android.view.KeyEvent import android.view.MenuItem @@ -122,10 +123,14 @@ abstract class BaseActivity : override fun onSupportActionModeStarted(mode: ActionMode) { super.onSupportActionModeStarted(mode) actionModeDelegate.onSupportActionModeStarted(mode) - val actionModeColor = ColorUtils.compositeColors( - ContextCompat.getColor(this, com.google.android.material.R.color.m3_appbar_overlay_color), - getThemeColor(com.google.android.material.R.attr.colorSurface), - ) + val actionModeColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ColorUtils.compositeColors( + ContextCompat.getColor(this, com.google.android.material.R.color.m3_appbar_overlay_color), + getThemeColor(com.google.android.material.R.attr.colorSurface), + ) + } else { + ContextCompat.getColor(this, R.color.kotatsu_secondaryContainer) + } val insets = ViewCompat.getRootWindowInsets(binding.root) ?.getInsets(WindowInsetsCompat.Type.systemBars()) ?: return findViewById(androidx.appcompat.R.id.action_mode_bar).apply { From 8e856211aa4b9089146bf31c859115b9122c764e Mon Sep 17 00:00:00 2001 From: Zakhar Timoshenko Date: Tue, 9 May 2023 17:29:02 +0300 Subject: [PATCH 2/5] Use system color for notifications --- .../kotatsu/download/ui/worker/DownloadNotificationFactory.kt | 1 - app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt | 2 -- .../org/koitharu/kotatsu/local/ui/LocalChaptersRemoveService.kt | 2 -- .../org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt | 1 - .../main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt | 2 -- 5 files changed, 8 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt index 306e3c6df..65b289971 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/worker/DownloadNotificationFactory.kt @@ -92,7 +92,6 @@ class DownloadNotificationFactory @AssistedInject constructor( createChannel() builder.setOnlyAlertOnce(true) builder.setDefaults(0) - builder.color = ContextCompat.getColor(context, R.color.blue_primary) builder.foregroundServiceBehavior = NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE builder.setSilent(true) builder.setGroup(GROUP_ID) diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt index 8aa05f52e..4e5d31b4b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt @@ -70,7 +70,6 @@ class ImportWorker @AssistedInject constructor( .setContentTitle(title) .setPriority(NotificationCompat.PRIORITY_MIN) .setDefaults(0) - .setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark)) .setSilent(true) .setProgress(0, 0, true) .setSmallIcon(android.R.drawable.stat_sys_download) @@ -85,7 +84,6 @@ class ImportWorker @AssistedInject constructor( val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setDefaults(0) - .setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark)) .setSilent(true) result.onSuccess { manga -> notification.setLargeIcon( diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalChaptersRemoveService.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalChaptersRemoveService.kt index ae742f144..e7606dfcf 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalChaptersRemoveService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalChaptersRemoveService.kt @@ -56,7 +56,6 @@ class LocalChaptersRemoveService : CoroutineIntentService() { .setContentTitle(getString(R.string.error_occurred)) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setDefaults(0) - .setColor(ContextCompat.getColor(this, R.color.blue_primary_dark)) .setSilent(true) .setContentText(error.getDisplayMessage(resources)) .setSmallIcon(android.R.drawable.stat_notify_error) @@ -82,7 +81,6 @@ class LocalChaptersRemoveService : CoroutineIntentService() { .setContentTitle(title) .setPriority(NotificationCompat.PRIORITY_MIN) .setDefaults(0) - .setColor(ContextCompat.getColor(this, R.color.blue_primary_dark)) .setSilent(true) .setProgress(0, 0, true) .setSmallIcon(android.R.drawable.stat_notify_sync) diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt index 64d77f505..e0a2e2d57 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt @@ -79,7 +79,6 @@ class SuggestionsWorker @AssistedInject constructor( .setPriority(NotificationCompat.PRIORITY_MIN) .setCategory(NotificationCompat.CATEGORY_SERVICE) .setDefaults(0) - .setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark)) .setSilent(true) .setProgress(0, 0, true) .setSmallIcon(android.R.drawable.stat_notify_sync) diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt index 8f87fbfe0..58bfdfb07 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt @@ -182,7 +182,6 @@ class TrackWorker @AssistedInject constructor( setAutoCancel(true) setCategory(NotificationCompat.CATEGORY_PROMO) setVisibility(if (manga.isNsfw) VISIBILITY_SECRET else VISIBILITY_PUBLIC) - color = colorPrimary setShortcutId(manga.id.toString()) priority = NotificationCompat.PRIORITY_DEFAULT if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { @@ -220,7 +219,6 @@ class TrackWorker @AssistedInject constructor( .setPriority(NotificationCompat.PRIORITY_MIN) .setCategory(NotificationCompat.CATEGORY_SERVICE) .setDefaults(0) - .setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark)) .setSilent(true) .setProgress(0, 0, true) .setSmallIcon(android.R.drawable.stat_notify_sync) From be666b7854bf71e10a2d38210c02378e907134d8 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 10 May 2023 12:14:03 +0300 Subject: [PATCH 3/5] Two buttons alert dialog --- .../base/ui/dialog/TwoButtonsAlertDialog.kt | 79 +++++++++++++++++++ .../main/res/layout/dialog_two_buttons.xml | 56 +++++++++++++ app/src/main/res/values/strings.xml | 2 + 3 files changed, 137 insertions(+) create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TwoButtonsAlertDialog.kt create mode 100644 app/src/main/res/layout/dialog_two_buttons.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TwoButtonsAlertDialog.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TwoButtonsAlertDialog.kt new file mode 100644 index 000000000..12bc0d955 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TwoButtonsAlertDialog.kt @@ -0,0 +1,79 @@ +package org.koitharu.kotatsu.base.ui.dialog + +import android.content.Context +import android.content.DialogInterface +import android.view.LayoutInflater +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.koitharu.kotatsu.databinding.DialogTwoButtonsBinding + +class TwoButtonsAlertDialog private constructor( + private val delegate: AlertDialog +) : DialogInterface by delegate { + + fun show() = delegate.show() + + class Builder(context: Context) { + + private val binding = DialogTwoButtonsBinding.inflate(LayoutInflater.from(context)) + + private val delegate = MaterialAlertDialogBuilder(context) + .setView(binding.root) + + fun setTitle(@StringRes titleResId: Int): Builder { + binding.title.setText(titleResId) + return this + } + + fun setTitle(title: CharSequence): Builder { + binding.title.text = title + return this + } + + fun setIcon(@DrawableRes iconId: Int): Builder { + binding.icon.setImageResource(iconId) + return this + } + + fun setPositiveButton( + @StringRes textId: Int, + listener: DialogInterface.OnClickListener, + ): Builder { + initButton(binding.button1, DialogInterface.BUTTON_POSITIVE, textId, listener) + return this + } + + fun setNegativeButton( + @StringRes textId: Int, + listener: DialogInterface.OnClickListener? = null + ): Builder { + initButton(binding.button2, DialogInterface.BUTTON_NEGATIVE, textId, listener) + return this + } + + fun create(): TwoButtonsAlertDialog { + val dialog = delegate.create() + binding.root.tag = dialog + return TwoButtonsAlertDialog(dialog) + } + + private fun initButton( + button: MaterialButton, + which: Int, + @StringRes textId: Int, + listener: DialogInterface.OnClickListener?, + ) { + button.setText(textId) + button.isVisible = true + button.setOnClickListener { + val dialog = binding.root.tag as DialogInterface + listener?.onClick(dialog, which) + dialog.dismiss() + } + } + } +} diff --git a/app/src/main/res/layout/dialog_two_buttons.xml b/app/src/main/res/layout/dialog_two_buttons.xml new file mode 100644 index 000000000..85b031bb1 --- /dev/null +++ b/app/src/main/res/layout/dialog_two_buttons.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0148884e4..fc464a07a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -448,4 +448,6 @@ Cancel all Download only via Wi-Fi Stop downloading when switching to a mobile network + Enable + No thanks From 8bfdc73072498a8d381a24a31d81cbb16e494c34 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 10 May 2023 13:09:26 +0300 Subject: [PATCH 4/5] Fix sync via https --- .../koitharu/kotatsu/sync/data/SyncAuthApi.kt | 12 +++++++++++- .../kotatsu/sync/data/SyncAuthenticator.kt | 3 ++- .../kotatsu/sync/data/SyncInterceptor.kt | 5 +++-- .../koitharu/kotatsu/sync/domain/SyncHelper.kt | 16 ++++++++++++++-- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt index 55f983967..a3792664f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt @@ -20,8 +20,9 @@ class SyncAuthApi @Inject constructor( val body = JSONObject( mapOf("email" to email, "password" to password), ).toRequestBody() + val scheme = getScheme(host) val request = Request.Builder() - .url("http://$host/auth") + .url("$scheme://$host/auth") .post(body) .build() val response = okHttpClient.newCall(request).await() @@ -33,4 +34,13 @@ class SyncAuthApi @Inject constructor( throw SyncApiException(message, code) } } + + private suspend fun getScheme(host: String): String { + val request = Request.Builder() + .url("http://$host/") + .head() + .build() + val response = okHttpClient.newCall(request).await() + return response.request.url.scheme + } } diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt index c37809ecf..dc660e381 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt @@ -9,6 +9,7 @@ import okhttp3.Request import okhttp3.Response import okhttp3.Route import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.network.CommonHeaders class SyncAuthenticator( context: Context, @@ -24,7 +25,7 @@ class SyncAuthenticator( val newToken = tryRefreshToken() ?: return null accountManager.setAuthToken(account, tokenType, newToken) return response.request.newBuilder() - .header("Authorization", "Bearer $newToken") + .header(CommonHeaders.AUTHORIZATION, "Bearer $newToken") .build() } diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncInterceptor.kt b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncInterceptor.kt index 0a9cc0db4..bcc677e6c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncInterceptor.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncInterceptor.kt @@ -8,6 +8,7 @@ import okhttp3.Response import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.db.DATABASE_VERSION +import org.koitharu.kotatsu.core.network.CommonHeaders class SyncInterceptor( context: Context, @@ -21,10 +22,10 @@ class SyncInterceptor( val token = accountManager.peekAuthToken(account, tokenType) val requestBuilder = chain.request().newBuilder() if (token != null) { - requestBuilder.header("Authorization", "Bearer $token") + requestBuilder.header(CommonHeaders.AUTHORIZATION, "Bearer $token") } requestBuilder.header("X-App-Version", BuildConfig.VERSION_CODE.toString()) requestBuilder.header("X-Db-Version", DATABASE_VERSION.toString()) return chain.proceed(requestBuilder.build()) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt index 1e8c7d745..1b0e5e958 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt @@ -54,8 +54,11 @@ class SyncHelper( .addInterceptor(SyncInterceptor(context, account)) .addInterceptor(GZipInterceptor()) .build() - private val baseUrl: String - get() = "http://${settings.host}" + private val baseUrl: String by lazy { + val host = settings.host + val scheme = getScheme(host) + "$scheme://$host" + } private val defaultGcPeriod: Long // gc period if sync enabled get() = TimeUnit.DAYS.toMillis(4) @@ -260,6 +263,15 @@ class SyncHelper( return requireNotNull(tag) } + private fun getScheme(host: String): String { + val request = Request.Builder() + .url("http://$host/") + .head() + .build() + val response = httpClient.newCall(request).execute() + return response.request.url.scheme + } + private fun gcFavourites() { val deletedAt = System.currentTimeMillis() - defaultGcPeriod val selection = "deleted_at != 0 AND deleted_at < ?" From 2b12dbd8d7523c4bcaae404068a8cccfb82e3197 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 10 May 2023 13:38:15 +0300 Subject: [PATCH 5/5] Disallow multiple sync accounts --- .../koitharu/kotatsu/sync/ui/SyncAuthActivity.kt | 14 ++++++++++++++ .../koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt index 14bb26817..cf24fb7b9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt @@ -8,6 +8,7 @@ import android.text.Editable import android.text.TextWatcher import android.view.View import android.widget.Button +import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.core.graphics.Insets @@ -54,6 +55,9 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi viewModel.onTokenObtained.observe(this, ::onTokenReceived) viewModel.onError.observe(this, ::onError) viewModel.isLoading.observe(this, ::onLoadingStateChanged) + viewModel.onAccountAlreadyExists.observe(this) { + onAccountAlreadyExists() + } supportFragmentManager.setFragmentResultListener(SyncHostDialogFragment.REQUEST_KEY, this, this) pageBackCallback.update() @@ -151,6 +155,16 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi finish() } + private fun onAccountAlreadyExists() { + Toast.makeText(this, R.string.account_already_exists, Toast.LENGTH_SHORT) + .show() + accountAuthenticatorResponse?.onError( + AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + getString(R.string.account_already_exists), + ) + super.finishAfterTransition() + } + private class EmailTextWatcher( private val button: Button, ) : TextWatcher { diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt index 567722392..d86bc3c8d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.sync.ui +import android.accounts.AccountManager import android.content.Context import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel @@ -19,11 +20,22 @@ class SyncAuthViewModel @Inject constructor( private val api: SyncAuthApi, ) : BaseViewModel() { + val onAccountAlreadyExists = SingleLiveEvent() val onTokenObtained = SingleLiveEvent() val host = MutableLiveData("") private val defaultHost = context.getString(R.string.sync_host_default) + init { + launchJob(Dispatchers.Default) { + val am = AccountManager.get(context) + val accounts = am.getAccountsByType(context.getString(R.string.account_type_sync)) + if (accounts.isNotEmpty()) { + onAccountAlreadyExists.emitCall(Unit) + } + } + } + fun obtainToken(email: String, password: String) { val hostValue = host.value.ifNullOrEmpty { defaultHost } launchLoadingJob(Dispatchers.Default) {