5 Commits
Author SHA1 Message Date
ajp_anton 05d682601a Release 1.0 2026-07-28 13:02:22 +00:00
ajp_anton 02b4862294 Clarify timestamp defaults and app filter overrides 2026-07-28 04:35:47 +00:00
ajp_anton bb822736ab Separate dropdown popups from settings pages 2026-07-28 04:06:32 +00:00
ajp_anton 591527750d Label storage limits in MiB 2026-07-28 04:02:17 +00:00
ajp_anton 5f6f169d3e Clarify image support in log exports 2026-07-28 04:01:10 +00:00
11 changed files with 39 additions and 17 deletions
+3 -1
View File
@@ -19,6 +19,8 @@ decisions, physical device testing, and final behaviour are still manually revie
You can choose which event types to record, set app-specific allow/block lists, You can choose which event types to record, set app-specific allow/block lists,
and suppress routine progress or timer updates. The log view can be tailored, and suppress routine progress or timer updates. The log view can be tailored,
expanded for long entries, and exported as CSV, aligned text, or an HTML ZIP. expanded for long entries, and exported as CSV, aligned text, or an HTML ZIP.
Only the HTML ZIP includes saved notification images; CSV and aligned text use
an `[image]` marker instead.
## Privacy ## Privacy
@@ -42,4 +44,4 @@ Tested Android versions:
- OneUI 8.0 (Android 16) on a Galaxy Z Fold 5 - OneUI 8.0 (Android 16) on a Galaxy Z Fold 5
- AOSP Android 14, 15, and 16 (API 34, 35, and 36) on an emulator - AOSP Android 14, 15, and 16 (API 34, 35, and 36) on an emulator
- Google APIs Android 15 and 16 (API 35 and 36) on an emulator - Google APIs Android 15 and 16 (API 35 and 36) on an emulator
- LineageOS 22 on Android 15 and LineageOS 23 on Android 16 on an emulator - LineageOS 22 on Android 15 and LineageOS 23 on Android 16 on an emulator
@@ -18,7 +18,6 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.switchmaterial.SwitchMaterial
import se.ajpanton.notificationlog.capture.SeenApps import se.ajpanton.notificationlog.capture.SeenApps
@@ -293,13 +292,13 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
} }
} }
holder.filter.contentDescription = "Edit logging events for ${app.label}" holder.filter.contentDescription = "Edit logging events for ${app.label}"
holder.filter.imageTintList = ColorStateList.valueOf( val colors = if (app.hasEventOverride) {
MaterialColors.getColor( R.color.app_filter_override_background to R.color.app_filter_override_icon
holder.filter, } else {
if (app.hasEventOverride) com.google.android.material.R.attr.colorPrimary R.color.app_filter_inherited_background to R.color.app_filter_inherited_icon
else com.google.android.material.R.attr.colorOnSurface, }
), holder.filter.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(holder.filter.context, colors.first))
) holder.filter.imageTintList = ColorStateList.valueOf(ContextCompat.getColor(holder.filter.context, colors.second))
holder.filter.setOnClickListener { onEventFilterClicked(app) } holder.filter.setOnClickListener { onEventFilterClicked(app) }
} }
if (holder is SectionTitleHolder && item is AppListItem.SectionTitle) { if (holder is SectionTitleHolder && item is AppListItem.SectionTitle) {
@@ -107,7 +107,7 @@ class LogDisplayFragment : Fragment(R.layout.fragment_log_display) {
): LinearLayout = LinearLayout(requireContext()).apply { ): LinearLayout = LinearLayout(requireContext()).apply {
orientation = LinearLayout.VERTICAL orientation = LinearLayout.VERTICAL
setPadding(dp(TIMESTAMP_CONTROLS_INDENT_DP), 0, 0, 0) setPadding(dp(TIMESTAMP_CONTROLS_INDENT_DP), 0, 0, 0)
addDropdown("Timestamp timezone", listOf("Current local timezone", "UTC", "Local timezone when event happened"), TimestampZone.entries.indexOf(currentSettings().timestampZone)) { position -> addDropdown("Timestamp timezone", listOf("Local timezone when event happened", "Current local timezone", "UTC"), TimestampZone.entries.indexOf(currentSettings().timestampZone)) { position ->
val updated = currentSettings().copy(timestampZone = TimestampZone.entries[position]) val updated = currentSettings().copy(timestampZone = TimestampZone.entries[position])
saveSettings(updated) saveSettings(updated)
} }
@@ -123,7 +123,8 @@ class LogDisplayFragment : Fragment(R.layout.fragment_log_display) {
private fun LinearLayout.addDropdown(label: String, values: List<String>, selected: Int, onSelected: (Int) -> Unit) { private fun LinearLayout.addDropdown(label: String, values: List<String>, selected: Int, onSelected: (Int) -> Unit) {
addView(TextView(context).apply { text = label }) addView(TextView(context).apply { text = label })
addView(Spinner(context).apply { addView(Spinner(context, Spinner.MODE_DROPDOWN).apply {
setPopupBackgroundDrawable(context.getDrawable(R.drawable.dropdown_popup_background))
adapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, values) adapter = ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, values)
setSelection(selected) setSelection(selected)
onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener { onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener {
@@ -89,7 +89,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) {
private fun chooseExportFormat() { private fun chooseExportFormat() {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setItems(arrayOf("CSV", "Formatted text", "HTML ZIP")) { _, which -> .setItems(arrayOf("CSV (no images)", "Formatted text (no images)", "HTML ZIP")) { _, which ->
pendingExport = ExportFormat.entries[which] pendingExport = ExportFormat.entries[which]
createDocument.launch("notification-log.${pendingExport!!.extension}") createDocument.launch("notification-log.${pendingExport!!.extension}")
} }
@@ -1,7 +1,7 @@
package se.ajpanton.notificationlog.settings package se.ajpanton.notificationlog.settings
enum class LogField { TIMESTAMP, APP_NAME, PACKAGE_NAME, ACTION, CONTENTS } enum class LogField { TIMESTAMP, APP_NAME, PACKAGE_NAME, ACTION, CONTENTS }
enum class TimestampZone { LOCAL_NOW, UTC, EVENT_LOCAL } enum class TimestampZone { EVENT_LOCAL, LOCAL_NOW, UTC }
enum class TimestampDateFormat { SYSTEM_DEFAULT, YEAR_MONTH_DAY, DAY_MONTH_YEAR, MONTH_DAY_YEAR } enum class TimestampDateFormat { SYSTEM_DEFAULT, YEAR_MONTH_DAY, DAY_MONTH_YEAR, MONTH_DAY_YEAR }
enum class TimestampClockFormat { SYSTEM_DEFAULT, HOUR_24, HOUR_12 } enum class TimestampClockFormat { SYSTEM_DEFAULT, HOUR_24, HOUR_12 }
enum class DisplayEvent { APPEARING, DISAPPEARING, EDITS } enum class DisplayEvent { APPEARING, DISAPPEARING, EDITS }
@@ -9,7 +9,7 @@ enum class DisplayEvent { APPEARING, DISAPPEARING, EDITS }
data class LogViewSettings( data class LogViewSettings(
val visibleFields: Set<LogField> = setOf(LogField.TIMESTAMP, LogField.APP_NAME, LogField.ACTION, LogField.CONTENTS), val visibleFields: Set<LogField> = setOf(LogField.TIMESTAMP, LogField.APP_NAME, LogField.ACTION, LogField.CONTENTS),
val order: List<LogField> = LogField.entries, val order: List<LogField> = LogField.entries,
val timestampZone: TimestampZone = TimestampZone.LOCAL_NOW, val timestampZone: TimestampZone = TimestampZone.EVENT_LOCAL,
val timestampDateFormat: TimestampDateFormat = TimestampDateFormat.SYSTEM_DEFAULT, val timestampDateFormat: TimestampDateFormat = TimestampDateFormat.SYSTEM_DEFAULT,
val timestampClockFormat: TimestampClockFormat = TimestampClockFormat.SYSTEM_DEFAULT, val timestampClockFormat: TimestampClockFormat = TimestampClockFormat.SYSTEM_DEFAULT,
val visibleEvents: Set<DisplayEvent> = DisplayEvent.entries.toSet(), val visibleEvents: Set<DisplayEvent> = DisplayEvent.entries.toSet(),
@@ -9,7 +9,7 @@ class LogViewSettingsStore(context: Context) {
visibleFields = prefs.getStringSet("visible", LogViewSettings().visibleFields.map { it.name }.toSet())!! visibleFields = prefs.getStringSet("visible", LogViewSettings().visibleFields.map { it.name }.toSet())!!
.map(LogField::valueOf).toSet(), .map(LogField::valueOf).toSet(),
order = prefs.getString("order", null)?.split(',')?.map(LogField::valueOf) ?: LogField.entries, order = prefs.getString("order", null)?.split(',')?.map(LogField::valueOf) ?: LogField.entries,
timestampZone = TimestampZone.valueOf(prefs.getString("zone", TimestampZone.LOCAL_NOW.name)!!), timestampZone = TimestampZone.valueOf(prefs.getString("zone", TimestampZone.EVENT_LOCAL.name)!!),
timestampDateFormat = TimestampDateFormat.valueOf(prefs.getString("date_format", TimestampDateFormat.SYSTEM_DEFAULT.name)!!), timestampDateFormat = TimestampDateFormat.valueOf(prefs.getString("date_format", TimestampDateFormat.SYSTEM_DEFAULT.name)!!),
timestampClockFormat = TimestampClockFormat.valueOf(prefs.getString("clock_format", TimestampClockFormat.SYSTEM_DEFAULT.name)!!), timestampClockFormat = TimestampClockFormat.valueOf(prefs.getString("clock_format", TimestampClockFormat.SYSTEM_DEFAULT.name)!!),
visibleEvents = prefs.getStringSet("visible_events", DisplayEvent.entries.map { it.name }.toSet())!! visibleEvents = prefs.getStringSet("visible_events", DisplayEvent.entries.map { it.name }.toSet())!!
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/dropdown_popup_background" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="@color/dropdown_popup_outline" />
</shape>
@@ -39,7 +39,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:text="Storage limits" /> android:text="Storage limits (MiB)" />
<EditText <EditText
android:id="@+id/log_limit_mib" android:id="@+id/log_limit_mib"
+6
View File
@@ -9,4 +9,10 @@
<color name="on_surface">#E3E2E9</color> <color name="on_surface">#E3E2E9</color>
<color name="window_background">#121318</color> <color name="window_background">#121318</color>
<color name="log_row_separator">#8F8D96</color> <color name="log_row_separator">#8F8D96</color>
<color name="dropdown_popup_background">#25262D</color>
<color name="dropdown_popup_outline">#C7C5CF</color>
<color name="app_filter_inherited_background">#2D2E34</color>
<color name="app_filter_inherited_icon">#000000</color>
<color name="app_filter_override_background">#4A4B53</color>
<color name="app_filter_override_icon">#B8CCFF</color>
</resources> </resources>
+6
View File
@@ -9,4 +9,10 @@
<color name="on_surface">#1B1B21</color> <color name="on_surface">#1B1B21</color>
<color name="window_background">#FBF8FF</color> <color name="window_background">#FBF8FF</color>
<color name="log_row_separator">#A5A2AB</color> <color name="log_row_separator">#A5A2AB</color>
<color name="dropdown_popup_background">#FFFFFF</color>
<color name="dropdown_popup_outline">#64646C</color>
<color name="app_filter_inherited_background">#DFDFE5</color>
<color name="app_filter_inherited_icon">#FFFFFF</color>
<color name="app_filter_override_background">#D0D0D8</color>
<color name="app_filter_override_icon">#34588E</color>
</resources> </resources>
+1 -1
View File
@@ -1 +1 @@
version=0.2 version=1.0