diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index a6716c742..b6c4992a7 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -410,6 +410,16 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) } } + fun getMessageSortAscending(): Boolean { + return sharedPrefs.getBoolean(SHARED_PREFS_MESSAGE_SORT_ASCENDING, false) + } + + fun setMessageSortAscending(ascending: Boolean) { + sharedPrefs.edit { + putBoolean(SHARED_PREFS_MESSAGE_SORT_ASCENDING, ascending) + } + } + fun getBatteryOptimizationsRemindTime(): Long { return sharedPrefs.getLong(SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME, BATTERY_OPTIMIZATIONS_REMIND_TIME_ALWAYS) } @@ -634,6 +644,7 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED = "InsistentMaxPriority" const val SHARED_PREFS_RECORD_LOGS_ENABLED = "RecordLogs" const val SHARED_PREFS_MESSAGE_BAR_ENABLED = "MessageBarEnabled" + const val SHARED_PREFS_MESSAGE_SORT_ASCENDING = "MessageSortAscending" const val SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME = "BatteryOptimizationsRemindTime" const val SHARED_PREFS_WEBSOCKET_REMIND_TIME = "JsonStreamRemindTime" // "Use WebSocket" banner (used to be JSON stream deprecation banner) const val SHARED_PREFS_WEBSOCKET_RECONNECT_REMIND_TIME = "WebSocketReconnectRemindTime" 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 b2554a875..96d83eae4 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -325,8 +325,18 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet val howToLink: View = findViewById(R.id.detail_how_to_link) viewModel.listFiltered(subscriptionId).observe(this) { it?.let { notifications -> + // Sort ascending if preference is set (DB returns descending by default) + val sortedNotifications = if (repository.getMessageSortAscending()) { + notifications.sortedBy { n -> n.timestamp } + } else { + notifications.sortedByDescending { n -> n.timestamp } + } // Show list view - adapter.submitList(notifications.toMutableList()) + adapter.submitList(sortedNotifications.toMutableList()) { + if (repository.getMessageSortAscending() && sortedNotifications.isNotEmpty()) { + mainList.scrollToPosition(sortedNotifications.size - 1) + } + } if (notifications.isEmpty()) { mainListContainer.visibility = View.GONE noEntriesText.visibility = View.VISIBLE @@ -375,10 +385,10 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet val itemTouchHelper = ItemTouchHelper(itemTouchCallback) itemTouchHelper.attachToRecyclerView(mainList) - // Scroll up when new notification is added + // Scroll to top when new notification is added (descending order only; ascending is handled in submitList callback) adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0) { + if (!repository.getMessageSortAscending() && positionStart == 0) { Log.d(TAG, "$itemCount item(s) inserted at 0, scrolling to the top") mainList.scrollToPosition(positionStart) } 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 8ebf2f874..7025ef0bb 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -481,6 +481,26 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } + // Message sort order + val messageSortAscendingPrefId = context?.getString(R.string.settings_general_message_sort_ascending_key) ?: return + val messageSortAscending: SwitchPreferenceCompat? = findPreference(messageSortAscendingPrefId) + messageSortAscending?.isChecked = repository.getMessageSortAscending() + messageSortAscending?.preferenceDataStore = object : PreferenceDataStore() { + override fun putBoolean(key: String?, value: Boolean) { + repository.setMessageSortAscending(value) + } + override fun getBoolean(key: String?, defValue: Boolean): Boolean { + return repository.getMessageSortAscending() + } + } + messageSortAscending?.summaryProvider = Preference.SummaryProvider { pref -> + if (pref.isChecked) { + getString(R.string.settings_general_message_sort_ascending_summary_enabled) + } else { + getString(R.string.settings_general_message_sort_ascending_summary_disabled) + } + } + // Default Base URL val appBaseUrl = getString(R.string.app_base_url) val defaultBaseUrlPrefId = context?.getString(R.string.settings_general_default_base_url_key) ?: return diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 986a33e4e..b34918444 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -402,6 +402,9 @@ Show message bar Message bar shown at bottom of topic view Publish button shown at bottom of topic view + Sort messages oldest first + Messages sorted oldest to newest + Messages sorted newest to oldest Backup & Restore Back up to file Export config, notifications, and users diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 0b45baf59..7045790de 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -25,6 +25,7 @@ Language DynamicColors MessageBarEnabled + MessageSortAscending Backup Restore BroadcastEnabled diff --git a/app/src/main/res/xml/main_preferences.xml b/app/src/main/res/xml/main_preferences.xml index 1ef47d3ed..a6a105b23 100644 --- a/app/src/main/res/xml/main_preferences.xml +++ b/app/src/main/res/xml/main_preferences.xml @@ -62,6 +62,10 @@ app:key="@string/settings_general_message_bar_key" app:title="@string/settings_general_message_bar_title" app:defaultValue="true"/> +