From ed8128ba874ef126ae0b21f1d418c527187a4533 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Thu, 30 Mar 2023 18:27:16 +0200 Subject: [PATCH] Migrate main app UI to Material Design 3 --- app/build.gradle | 3 +- .../java/io/heckel/ntfy/app/Application.kt | 6 ++++ .../java/io/heckel/ntfy/ui/AddFragment.kt | 3 +- app/src/main/java/io/heckel/ntfy/ui/Colors.kt | 29 +++++++++---------- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 20 +++++-------- .../java/io/heckel/ntfy/ui/MainActivity.kt | 14 ++++----- .../java/io/heckel/ntfy/ui/MainAdapter.kt | 2 +- .../io/heckel/ntfy/ui/NotificationFragment.kt | 3 +- .../io/heckel/ntfy/ui/SettingsActivity.kt | 3 +- .../java/io/heckel/ntfy/ui/UserFragment.kt | 3 +- app/src/main/java/io/heckel/ntfy/util/Util.kt | 2 +- ...ifications_off_time_white_outline_24dp.xml | 1 + ...c_notifications_off_white_outline_24dp.xml | 1 + .../drawable/ic_notifications_white_24dp.xml | 1 + app/src/main/res/layout/activity_share.xml | 2 ++ .../main/res/layout/fragment_add_dialog.xml | 3 ++ .../main/res/layout/fragment_user_dialog.xml | 2 ++ app/src/main/res/values-night/styles.xml | 15 +++------- app/src/main/res/values/styles.xml | 19 ++++-------- 19 files changed, 63 insertions(+), 69 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a9a28c5..76d078b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,6 +36,7 @@ android { debug { minifyEnabled false debuggable true + applicationIdSuffix '.debug' } } @@ -120,7 +121,7 @@ dependencies { implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' // Material design - implementation "com.google.android.material:material:1.6.1" + implementation "com.google.android.material:material:1.8.0" // LiveData implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" diff --git a/app/src/main/java/io/heckel/ntfy/app/Application.kt b/app/src/main/java/io/heckel/ntfy/app/Application.kt index f6cb30c..d07c7cb 100644 --- a/app/src/main/java/io/heckel/ntfy/app/Application.kt +++ b/app/src/main/java/io/heckel/ntfy/app/Application.kt @@ -1,6 +1,7 @@ package io.heckel.ntfy.app import android.app.Application +import com.google.android.material.color.DynamicColors import io.heckel.ntfy.db.Repository import io.heckel.ntfy.util.Log @@ -12,4 +13,9 @@ class Application : Application() { } repository } + + override fun onCreate() { + DynamicColors.applyToActivitiesIfAvailable(this) + super.onCreate() + } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt index 8e56bab..256108a 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt @@ -11,6 +11,7 @@ import android.view.inputmethod.InputMethodManager import android.widget.* import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout import io.heckel.ntfy.BuildConfig @@ -144,7 +145,7 @@ class AddFragment : DialogFragment() { loginPasswordText.addTextChangedListener(loginTextWatcher) // Build dialog - val dialog = AlertDialog.Builder(activity) + val dialog = MaterialAlertDialogBuilder(requireContext()) .setView(view) .setPositiveButton(R.string.add_dialog_button_subscribe) { _, _ -> // This will be overridden below to avoid closing the dialog immediately diff --git a/app/src/main/java/io/heckel/ntfy/ui/Colors.kt b/app/src/main/java/io/heckel/ntfy/ui/Colors.kt index ada14cb..be5b499 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/Colors.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/Colors.kt @@ -1,48 +1,45 @@ package io.heckel.ntfy.ui import android.content.Context +import android.graphics.Color import androidx.core.content.ContextCompat +import com.google.android.material.color.MaterialColors +import com.google.android.material.elevation.SurfaceColors import io.heckel.ntfy.R import io.heckel.ntfy.util.isDarkThemeOn class Colors { companion object { - const val refreshProgressIndicator = R.color.teal - fun notificationIcon(context: Context): Int { return if (isDarkThemeOn(context)) R.color.teal_light else R.color.teal } fun itemSelectedBackground(context: Context): Int { - return if (isDarkThemeOn(context)) R.color.black_800b else R.color.gray_400 - } - - fun cardBackground(context: Context): Int { - return if (isDarkThemeOn(context)) R.color.black_800b else R.color.white - } - - fun cardSelectedBackground(context: Context): Int { - return if (isDarkThemeOn(context)) R.color.black_700b else R.color.gray_500 + return SurfaceColors.getColorForElevation(context, 10f) } fun cardBackgroundColor(context: Context): Int { - return ContextCompat.getColor(context, cardBackground(context)) + return SurfaceColors.getColorForElevation(context, 5f) } fun cardSelectedBackgroundColor(context: Context): Int { - return ContextCompat.getColor(context, cardSelectedBackground(context)) + return SurfaceColors.getColorForElevation(context, 20f) } fun statusBarNormal(context: Context): Int { - return if (isDarkThemeOn(context)) R.color.black_900 else R.color.teal + return MaterialColors.getColor(context, R.attr.backgroundColor, Color.BLACK) } fun statusBarActionMode(context: Context): Int { - return if (isDarkThemeOn(context)) R.color.black_900 else R.color.teal_dark + return MaterialColors.getColor(context, R.attr.backgroundColor, Color.BLACK) } fun dangerText(context: Context): Int { - return if (isDarkThemeOn(context)) R.color.red_light else R.color.red_dark + return MaterialColors.getColor(context, R.attr.colorError, Color.RED) + } + + fun swipeToRefreshColor(context: Context): Int { + return MaterialColors.getColor(context, R.attr.colorPrimary, Color.GREEN) } } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index 851f43a..afda3e9 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R @@ -190,7 +191,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra // Swipe to refresh mainListContainer = findViewById(R.id.detail_notification_list_container) mainListContainer.setOnRefreshListener { refresh() } - mainListContainer.setColorSchemeResources(Colors.refreshProgressIndicator) + mainListContainer.setColorSchemeColors(Colors.swipeToRefreshColor(this)) // Update main list based on viewModel (& its datasource/livedata) val noEntriesText: View = findViewById(R.id.detail_no_notifications) @@ -568,8 +569,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra private fun onClearClick() { Log.d(TAG, "Clearing all notifications for ${topicShortUrl(subscriptionBaseUrl, subscriptionTopic)}") - val builder = AlertDialog.Builder(this) - val dialog = builder + val dialog = MaterialAlertDialogBuilder(this) .setMessage(R.string.detail_clear_dialog_message) .setPositiveButton(R.string.detail_clear_dialog_permanently_delete) { _, _ -> lifecycleScope.launch(Dispatchers.IO) { @@ -600,8 +600,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra private fun onDeleteClick() { Log.d(TAG, "Deleting subscription ${topicShortUrl(subscriptionBaseUrl, subscriptionTopic)}") - val builder = AlertDialog.Builder(this) - val dialog = builder + val dialog = MaterialAlertDialogBuilder(this) .setMessage(R.string.detail_delete_dialog_message) .setPositiveButton(R.string.detail_delete_dialog_permanently_delete) { _, _ -> Log.d(TAG, "Deleting subscription with subscription ID $subscriptionId (topic: $subscriptionTopic)") @@ -716,8 +715,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra private fun onMultiDeleteClick() { Log.d(TAG, "Showing multi-delete dialog for selected items") - val builder = AlertDialog.Builder(this) - val dialog = builder + val dialog = MaterialAlertDialogBuilder(this) .setMessage(R.string.detail_action_mode_delete_dialog_message) .setPositiveButton(R.string.detail_action_mode_delete_dialog_permanently_delete) { _, _ -> adapter.selected.map { notificationId -> viewModel.markAsDeleted(notificationId) } @@ -744,9 +742,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra adapter.toggleSelection(notification.id) // Fade status bar color - val fromColor = ContextCompat.getColor(this, Colors.statusBarNormal(this)) - val toColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this)) - fadeStatusBarColor(window, fromColor, toColor) + fadeStatusBarColor(window, Colors.statusBarNormal(this), Colors.statusBarActionMode(this)) } private fun finishActionMode() { @@ -760,9 +756,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra adapter.notifyItemRangeChanged(0, adapter.currentList.size) // Fade status bar color - val fromColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this)) - val toColor = ContextCompat.getColor(this, Colors.statusBarNormal(this)) - fadeStatusBarColor(window, fromColor, toColor) + fadeStatusBarColor(window, Colors.statusBarActionMode(this), Colors.statusBarNormal(this)) } companion object { diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index e131f32..4daec6b 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -28,6 +28,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.work.* +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R @@ -96,7 +97,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc // Swipe to refresh mainListContainer = findViewById(R.id.main_subscriptions_list_container) mainListContainer.setOnRefreshListener { refreshAllSubscriptions() } - mainListContainer.setColorSchemeResources(Colors.refreshProgressIndicator) + mainListContainer.setColorSchemeColors(Colors.swipeToRefreshColor(this)) // Update main list based on viewModel (& its datasource/livedata) val noEntries: View = findViewById(R.id.main_no_subscriptions) @@ -608,8 +609,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc private fun onMultiDeleteClick() { Log.d(DetailActivity.TAG, "Showing multi-delete dialog for selected items") - val builder = AlertDialog.Builder(this) - val dialog = builder + val dialog = MaterialAlertDialogBuilder(this) .setMessage(R.string.main_action_mode_delete_dialog_message) .setPositiveButton(R.string.main_action_mode_delete_dialog_permanently_delete) { _, _ -> adapter.selected.map { subscriptionId -> viewModel.remove(this, subscriptionId) } @@ -648,9 +648,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc }) // Fade status bar color - val fromColor = ContextCompat.getColor(this, Colors.statusBarNormal(this)) - val toColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this)) - fadeStatusBarColor(window, fromColor, toColor) + fadeStatusBarColor(window, Colors.statusBarNormal(this), Colors.statusBarActionMode(this)) } private fun finishActionMode() { @@ -677,9 +675,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc }) // Fade status bar color - val fromColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this)) - val toColor = ContextCompat.getColor(this, Colors.statusBarNormal(this)) - fadeStatusBarColor(window, fromColor, toColor) + fadeStatusBarColor(window, Colors.statusBarActionMode(this), Colors.statusBarNormal(this)) } private fun redrawList() { diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt index ed0d2bd..4aa7e5b 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt @@ -115,7 +115,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs itemView.setOnClickListener { onClick(subscription) } itemView.setOnLongClickListener { onLongClick(subscription); true } if (selected.contains(subscription.id)) { - itemView.setBackgroundResource(Colors.itemSelectedBackground(context)) + itemView.setBackgroundColor(Colors.itemSelectedBackground(context)) } else { itemView.setBackgroundColor(Color.TRANSPARENT) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/NotificationFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/NotificationFragment.kt index 2d84879..920d1bd 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/NotificationFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/NotificationFragment.kt @@ -7,6 +7,7 @@ import android.os.Bundle import android.widget.RadioButton import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope +import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.heckel.ntfy.R import io.heckel.ntfy.db.Repository import kotlinx.coroutines.Dispatchers @@ -74,7 +75,7 @@ class NotificationFragment : DialogFragment() { muteForeverButton = view.findViewById(R.id.notification_dialog_forever) muteForeverButton.setOnClickListener{ onClick(Repository.MUTED_UNTIL_FOREVER) } - return AlertDialog.Builder(activity) + return MaterialAlertDialogBuilder(requireContext()) .setView(view) .create() } diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index f4ef5d8..1ce185e 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -23,6 +23,7 @@ import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import androidx.preference.* import androidx.preference.Preference.OnPreferenceClickListener +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.gson.Gson import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R @@ -658,7 +659,7 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } else { getString(R.string.settings_advanced_export_logs_scrub_dialog_empty) } - val dialog = AlertDialog.Builder(activity) + val dialog = MaterialAlertDialogBuilder(requireContext()) .setTitle(title) .setMessage(scrubbedText) .setPositiveButton(R.string.settings_advanced_export_logs_scrub_dialog_button_ok) { _, _ -> /* Nothing */ } diff --git a/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt index 6da8304..a6c5020 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt @@ -9,6 +9,7 @@ import android.view.WindowManager import android.widget.Button import android.widget.TextView import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText import io.heckel.ntfy.R import io.heckel.ntfy.db.User @@ -75,7 +76,7 @@ class UserFragment : DialogFragment() { } // Build dialog - val builder = AlertDialog.Builder(activity) + val builder = MaterialAlertDialogBuilder(requireContext()) .setView(view) .setPositiveButton(positiveButtonTextResId) { _, _ -> saveClicked() diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index 2ffbb76..d177e0b 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -501,7 +501,7 @@ fun Button.dangerButton(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { setTextAppearance(R.style.DangerText) } else { - setTextColor(ContextCompat.getColor(context, Colors.dangerText(context))) + setTextColor(Colors.dangerText(context)) } } diff --git a/app/src/main/res/drawable/ic_notifications_off_time_white_outline_24dp.xml b/app/src/main/res/drawable/ic_notifications_off_time_white_outline_24dp.xml index f3c0ad0..09425d1 100644 --- a/app/src/main/res/drawable/ic_notifications_off_time_white_outline_24dp.xml +++ b/app/src/main/res/drawable/ic_notifications_off_time_white_outline_24dp.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/layout/fragment_add_dialog.xml b/app/src/main/res/layout/fragment_add_dialog.xml index ad0c7d4..77b603b 100644 --- a/app/src/main/res/layout/fragment_add_dialog.xml +++ b/app/src/main/res/layout/fragment_add_dialog.xml @@ -47,6 +47,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/add_dialog_topic_name_hint" android:importantForAutofill="no" + android:backgroundTint="?attr/colorPrimary" android:maxLines="1" android:inputType="text" android:maxLength="64" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_description"/> @@ -187,6 +188,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/add_dialog_login_username_hint" android:importantForAutofill="no" + android:backgroundTint="?attr/colorPrimary" android:maxLines="1" android:inputType="text" android:maxLength="64" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="10dp" app:layout_constraintTop_toBottomOf="@+id/add_dialog_login_description"/> @@ -195,6 +197,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/add_dialog_login_password_hint" android:importantForAutofill="no" + android:backgroundTint="?attr/colorPrimary" android:maxLines="1" android:inputType="textPassword" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/add_dialog_login_username"/> diff --git a/app/src/main/res/layout/fragment_user_dialog.xml b/app/src/main/res/layout/fragment_user_dialog.xml index f6ed271..b28ae57 100644 --- a/app/src/main/res/layout/fragment_user_dialog.xml +++ b/app/src/main/res/layout/fragment_user_dialog.xml @@ -32,6 +32,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/user_dialog_base_url_hint" android:importantForAutofill="no" + android:backgroundTint="?attr/colorPrimary" android:maxLines="1" android:inputType="text" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="6dp" app:layout_constraintTop_toBottomOf="@id/user_dialog_description"/> @@ -40,6 +41,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/user_dialog_username_hint" android:importantForAutofill="no" + android:backgroundTint="?attr/colorPrimary" android:maxLines="1" android:inputType="text" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="6dp" app:layout_constraintTop_toBottomOf="@id/user_dialog_base_url"/> diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index a71491b..bb89181 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -10,7 +10,7 @@ - https://developer.android.com/guide/topics/ui/look-and-feel/themes --> - - + + + - + + +