diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index b858973..c3b5554 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -12,6 +12,7 @@ import io.heckel.ntfy.R import io.heckel.ntfy.db.* import io.heckel.ntfy.db.Notification import io.heckel.ntfy.log.Log +import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.DetailActivity import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.util.* @@ -52,7 +53,7 @@ class NotificationService(val context: Context) { val channelId = toChannelId(notification.priority) val builder = NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification) - .setColor(ContextCompat.getColor(context, R.color.primaryColor)) + .setColor(ContextCompat.getColor(context, Colors.notificationIcon)) .setContentTitle(title) .setOnlyAlertOnce(true) // Do not vibrate or play sound if already showing (updates!) .setAutoCancel(true) // Cancel when notification is clicked diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt index d12d493..bc03680 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt @@ -19,6 +19,7 @@ import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.log.Log import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.NotificationDispatcher +import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.util.topicUrl import kotlinx.coroutines.CoroutineScope @@ -292,7 +293,7 @@ class SubscriberService : Service() { } return NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification_instant) - .setColor(ContextCompat.getColor(this, R.color.primaryColor)) + .setColor(ContextCompat.getColor(this, Colors.notificationIcon)) .setContentTitle(title) .setContentText(text) .setContentIntent(pendingIntent) diff --git a/app/src/main/java/io/heckel/ntfy/ui/Colors.kt b/app/src/main/java/io/heckel/ntfy/ui/Colors.kt new file mode 100644 index 0000000..c36165b --- /dev/null +++ b/app/src/main/java/io/heckel/ntfy/ui/Colors.kt @@ -0,0 +1,28 @@ +package io.heckel.ntfy.ui + +import android.content.Context +import io.heckel.ntfy.R +import io.heckel.ntfy.util.isDarkThemeOn + +class Colors { + companion object { + const val refreshProgressIndicator = R.color.teal + const val notificationIcon = R.color.teal + + fun itemSelectedBackground(context: Context): Int { + return if (isDarkThemeOn(context)) R.color.gray_dark else R.color.gray_light + } + + fun statusBarNormal(context: Context): Int { + return if (isDarkThemeOn(context)) R.color.black_light else R.color.teal + } + + fun statusBarActionMode(context: Context): Int { + return if (isDarkThemeOn(context)) R.color.black_light else R.color.teal_dark + } + + fun dangerText(context: Context): Int { + return if (isDarkThemeOn(context)) R.color.red_light else R.color.red_dark + } + } +} 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 756473b..4bb3e54 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -106,7 +106,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra // Swipe to refresh mainListContainer = findViewById(R.id.detail_notification_list_container) mainListContainer.setOnRefreshListener { refresh() } - mainListContainer.setColorSchemeResources(R.color.primaryColor) + mainListContainer.setColorSchemeResources(Colors.refreshProgressIndicator) // Update main list based on viewModel (& its datasource/livedata) val noEntriesText: View = findViewById(R.id.detail_no_notifications) @@ -477,7 +477,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra dialog.setOnShowListener { dialog .getButton(AlertDialog.BUTTON_POSITIVE) - .setTextColor(ContextCompat.getColor(this, R.color.primaryDangerButtonColor)) + .setTextAppearance(R.style.DangerText) } dialog.show() } @@ -512,7 +512,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra dialog.setOnShowListener { dialog .getButton(AlertDialog.BUTTON_POSITIVE) - .setTextColor(ContextCompat.getColor(this, R.color.primaryDangerButtonColor)) + .setTextAppearance(R.style.DangerText) } dialog.show() } @@ -619,7 +619,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra dialog.setOnShowListener { dialog .getButton(AlertDialog.BUTTON_POSITIVE) - .setTextColor(ContextCompat.getColor(this, R.color.primaryDangerButtonColor)) + .setTextAppearance(R.style.DangerText) } dialog.show() } @@ -634,8 +634,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra redrawList() // Fade status bar color - val fromColor = ContextCompat.getColor(this, R.color.primaryColor) - val toColor = ContextCompat.getColor(this, R.color.primaryDarkColor) + val fromColor = ContextCompat.getColor(this, Colors.statusBarNormal(this)) + val toColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this)) fadeStatusBarColor(window, fromColor, toColor) } @@ -650,8 +650,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra redrawList() // Fade status bar color - val fromColor = ContextCompat.getColor(this, R.color.primaryDarkColor) - val toColor = ContextCompat.getColor(this, R.color.primaryColor) + val fromColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this)) + val toColor = ContextCompat.getColor(this, Colors.statusBarNormal(this)) fadeStatusBarColor(window, fromColor, toColor) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt index 1df33ff..0b54915 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt @@ -96,8 +96,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo tagsView.visibility = View.GONE } if (selected.contains(notification.id)) { - val backgroundColor = if (isDarkThemeOn(context, repository)) R.color.primaryDarkSelectedRowColor else R.color.primaryLightSelectedRowColor - itemView.setBackgroundResource(backgroundColor); + itemView.setBackgroundResource(Colors.itemSelectedBackground(context)) } renderPriority(context, notification) maybeRenderAttachment(context, notification) 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 7e27002..a5155f7 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.work.* +import com.google.android.material.floatingactionbutton.FloatingActionButton import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R import io.heckel.ntfy.app.Application @@ -56,7 +57,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc private lateinit var mainList: RecyclerView private lateinit var mainListContainer: SwipeRefreshLayout private lateinit var adapter: MainAdapter - private lateinit var fab: View + private lateinit var fab: FloatingActionButton // Other stuff private var actionMode: ActionMode? = null @@ -88,7 +89,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc // Swipe to refresh mainListContainer = findViewById(R.id.main_subscriptions_list_container) mainListContainer.setOnRefreshListener { refreshAllSubscriptions() } - mainListContainer.setColorSchemeResources(R.color.primaryColor) + mainListContainer.setColorSchemeResources(Colors.refreshProgressIndicator) // Update main list based on viewModel (& its datasource/livedata) val noEntries: View = findViewById(R.id.main_no_subscriptions) @@ -512,7 +513,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc dialog.setOnShowListener { dialog .getButton(AlertDialog.BUTTON_POSITIVE) - .setTextColor(ContextCompat.getColor(this, R.color.primaryDangerButtonColor)) + .setTextAppearance(R.style.DangerText) } dialog.show() } @@ -539,8 +540,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc }) // Fade status bar color - val fromColor = ContextCompat.getColor(this, R.color.primaryColor) - val toColor = ContextCompat.getColor(this, R.color.primaryDarkColor) + val fromColor = ContextCompat.getColor(this, Colors.statusBarNormal(this)) + val toColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this)) fadeStatusBarColor(window, fromColor, toColor) } @@ -568,8 +569,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc }) // Fade status bar color - val fromColor = ContextCompat.getColor(this, R.color.primaryDarkColor) - val toColor = ContextCompat.getColor(this, R.color.primaryColor) + val fromColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this)) + val toColor = ContextCompat.getColor(this, Colors.statusBarNormal(this)) fadeStatusBarColor(window, fromColor, toColor) } 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 5e69616..1ce5c33 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt @@ -5,7 +5,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.appcompat.app.AppCompatDelegate import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView @@ -102,8 +101,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)) { - val backgroundColor = if (isDarkThemeOn(context, repository)) R.color.primaryDarkSelectedRowColor else R.color.primaryLightSelectedRowColor - itemView.setBackgroundResource(backgroundColor) + itemView.setBackgroundResource(Colors.itemSelectedBackground(context)) } } } 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 1e17806..4f4daed 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/UserFragment.kt @@ -3,6 +3,7 @@ package io.heckel.ntfy.ui import android.app.AlertDialog import android.app.Dialog import android.content.Context +import android.os.Build import android.os.Bundle import android.text.Editable import android.text.TextWatcher @@ -15,7 +16,6 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.textfield.TextInputEditText import io.heckel.ntfy.R import io.heckel.ntfy.db.User -import kotlin.random.Random class UserFragment : DialogFragment() { private var user: User? = null @@ -97,9 +97,15 @@ class UserFragment : DialogFragment() { // Delete button should be red if (user != null) { - dialog - .getButton(AlertDialog.BUTTON_NEUTRAL) - .setTextColor(ContextCompat.getColor(requireContext(), R.color.primaryDangerButtonColor)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + dialog + .getButton(AlertDialog.BUTTON_NEUTRAL) + .setTextAppearance(R.style.DangerText) + } else { + dialog + .getButton(AlertDialog.BUTTON_NEUTRAL) + .setTextColor(ContextCompat.getColor(requireContext(), Colors.dangerText(requireContext()))) + } } // Validate input when typing 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 5736476..4220041 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -3,17 +3,14 @@ package io.heckel.ntfy.util import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.content.Context -import android.content.Intent import android.content.res.Configuration import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.net.Uri import android.os.Build import android.os.PowerManager import android.provider.OpenableColumns -import android.provider.Settings import android.view.Window import androidx.appcompat.app.AppCompatDelegate -import io.heckel.ntfy.R import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription @@ -206,17 +203,17 @@ fun isIgnoringBatteryOptimizations(context: Context): Boolean { } // Returns true if dark mode is on, see https://stackoverflow.com/a/60761189/1440785 -fun Context.isDarkThemeOn(): Boolean { +fun Context.systemDarkThemeOn(): Boolean { return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES } -fun isDarkThemeOn(context: Context, repository: Repository): Boolean { - val darkMode = repository.getDarkMode() +fun isDarkThemeOn(context: Context): Boolean { + val darkMode = Repository.getInstance(context).getDarkMode() if (darkMode == AppCompatDelegate.MODE_NIGHT_YES) { return true } - if (darkMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM && context.isDarkThemeOn()) { + if (darkMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM && context.systemDarkThemeOn()) { return true } return false diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 06d345a..e825332 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -139,11 +139,12 @@ android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_margin="16dp" + android:layout_margin="24dp" android:contentDescription="@string/main_add_button_description" android:src="@drawable/ic_add_black_24dp" - app:tint="@color/primaryLightTextColor" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" app:backgroundTint="@color/primaryColor"/> + app:layout_constraintEnd_toEndOf="parent" + style="@style/FloatingActionButton" + /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_add_dialog.xml b/app/src/main/res/layout/fragment_add_dialog.xml index 29da8cd..7ee8a8c 100644 --- a/app/src/main/res/layout/fragment_add_dialog.xml +++ b/app/src/main/res/layout/fragment_add_dialog.xml @@ -139,7 +139,12 @@ android:layout_height="wrap_content" android:id="@+id/add_dialog_subscribe_error_text" android:paddingStart="4dp" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_description" android:paddingEnd="4dp" android:textColor="@color/primaryDangerButtonColor" app:layout_constraintStart_toEndOf="@id/add_dialog_subscribe_error_text_image" android:layout_marginTop="5dp" tools:visibility="gone"/> + app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_description" + android:paddingEnd="4dp" + android:textAppearance="@style/DangerText" + app:layout_constraintStart_toEndOf="@id/add_dialog_subscribe_error_text_image" + android:layout_marginTop="5dp" + tools:visibility="gone"/> </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> <ScrollView @@ -197,7 +202,10 @@ android:layout_height="wrap_content" android:id="@+id/add_dialog_login_error_text" android:paddingStart="4dp" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/add_dialog_login_password" android:paddingEnd="4dp" android:textColor="@color/primaryDangerButtonColor" app:layout_constraintStart_toEndOf="@id/add_dialog_login_error_text_image"/> + app:layout_constraintTop_toBottomOf="@id/add_dialog_login_password" + android:paddingEnd="4dp" + android:textAppearance="@style/DangerText" + app:layout_constraintStart_toEndOf="@id/add_dialog_login_error_text_image"/> <ProgressBar style="?android:attr/progressBarStyle" android:layout_width="25dp" diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..2a7f99c --- /dev/null +++ b/app/src/main/res/values-night/styles.xml @@ -0,0 +1,33 @@ +<resources> + <!-- + This file contains only overrides for the dark theme. + Also see "ui/Colors.kt" for colors that have to be defined in code. + + Resources: + - https://material.io/design/color/dark-theme.html + - https://material.io/develop/android/theming/dark + - https://developer.android.com/codelabs/basic-android-kotlin-training-change-app-theme#4 + - https://developer.android.com/guide/topics/ui/look-and-feel/themes + --> + + <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <item name="colorPrimary">@color/teal_light</item> + <item name="colorAccent">@color/teal_light</item> <!-- checkboxes, text fields --> + <item name="android:colorBackground">@color/black_light</item> <!-- background --> + <item name="android:statusBarColor">@color/black_light</item> + <item name="actionModeBackground">@color/black_light</item> + + <!-- Action bar background & text color --> + <item name="colorSurface">@color/gray_dark</item> + <item name="colorOnSurface">@color/white</item> + </style> + + <style name="DangerText" parent="@android:style/TextAppearance"> + <item name="android:textColor">@color/red_light</item> + </style> + + <style name="FloatingActionButton" parent="@style/Widget.MaterialComponents.FloatingActionButton"> + <item name="tint">@color/black_light</item> + <item name="backgroundTint">@color/teal_light</item> + </style> +</resources> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 6ba7967..a03ba0e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,13 +1,16 @@ <!--?xml version="1.0" encoding="UTF-8"?--> <resources> - <color name="primaryColor">#338574</color> - <color name="primaryDangerButtonColor">#C30000</color> + <color name="black">#ff000000</color> + <color name="black_light">#121212</color> <!-- Main dark mode surface color, as per style guide --> + <color name="white">#ffffffff</color> - <color name="primaryLightColor">#338574</color> - <color name="primaryLightTextColor">#FFFFFF</color> - <color name="primaryLightSelectedRowColor">#EEEEEE</color> + <color name="teal">#338574</color> <!-- Primary color (light mode) --> + <color name="teal_light">#65b5a3</color> <!-- Primary color (dark mode) --> + <color name="teal_dark">#2a6e60</color> <!-- Action bar background in action mode (light mode) --> + <color name="red_light">#fe4d2e</color> <!-- Danger text (dark mode) --> + <color name="red_dark">#c30000</color> <!-- Danger text (light mode) --> - <color name="primaryDarkColor">#2A6E60</color> - <color name="primaryDarkSelectedRowColor">#444444</color> + <color name="gray_light">#eeeeee</color> <!-- Light theme item selection --> + <color name="gray_dark">#282828</color> <!-- Dark mode action bar & item selection --> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8118894..9b6b231 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -188,8 +188,8 @@ <string name="notification_dialog_cancel">Cancel</string> <string name="notification_dialog_save">Save</string> <string name="notification_dialog_enabled_toast_message">Notifications re-enabled</string> - <string name="notification_dialog_muted_forever_toast_message">Notifications are now paused</string> - <string name="notification_dialog_muted_until_toast_message">Notifications are paused until %1$s</string> + <string name="notification_dialog_muted_forever_toast_message">Notifications paused</string> + <string name="notification_dialog_muted_until_toast_message">Notifications paused until %1$s</string> <string name="notification_dialog_show_all">Show all notifications</string> <string name="notification_dialog_30min">30 minutes</string> <string name="notification_dialog_1h">1 hour</string> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 1a3091e..ff2c19f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,10 +1,22 @@ <resources> - <!-- DayNight mode for easy dark mode, see https://material.io/develop/android/theming/dark --> + <!-- Main app theme; dark theme styles see values-night/styles.xml --> <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> - <item name="colorPrimary">@color/primaryColor</item> - <item name="colorAccent">@color/primaryLightColor</item> - <item name="android:statusBarColor">@color/primaryColor</item> - <item name="actionModeBackground">@color/primaryDarkColor</item> + <item name="colorPrimary">@color/teal</item> + <item name="colorAccent">@color/teal</item> <!-- checkboxes, text fields --> + <item name="android:colorBackground">@color/white</item> <!-- background --> + <item name="android:statusBarColor">@color/teal</item> + <item name="actionModeBackground">@color/teal_dark</item> + </style> + + <!-- Danger buttons & text --> + <style name="DangerText" parent="@android:style/TextAppearance"> + <item name="android:textColor">@color/red_dark</item> + </style> + + <!-- Floating action button --> + <style name="FloatingActionButton" parent="@style/Widget.MaterialComponents.FloatingActionButton"> + <item name="tint">@color/white</item> + <item name="backgroundTint">@color/teal</item> </style> <!-- Rounded corners in images, see https://stackoverflow.com/a/61960983/1440785 -->