diff --git a/.impeccable/critique/2026-06-07T13-10-40Z__lib-screens-analytics-screen-dart.md b/.impeccable/critique/2026-06-07T13-10-40Z__lib-screens-analytics-screen-dart.md
new file mode 100644
index 0000000..3fcc978
--- /dev/null
+++ b/.impeccable/critique/2026-06-07T13-10-40Z__lib-screens-analytics-screen-dart.md
@@ -0,0 +1,76 @@
+---
+target: lib/screens/analytics_screen.dart
+total_score: 23
+p0_count: 0
+p1_count: 2
+timestamp: 2026-06-07T13-10-40Z
+slug: lib-screens-analytics-screen-dart
+---
+## Analytics Screen — Design Critique
+
+### Design Health Score
+
+| # | Heuristic | Score | Key Issue |
+|---|-----------|-------|-----------|
+| 1 | Visibility of System Status | 3 | Loading states present; chart doesn't visually react to period change |
+| 2 | Match System / Real World | 2 | Period toggle changes summary totals but leaves chart data untouched — broken contract |
+| 3 | User Control and Freedom | 3 | All toggles freely reversible; no traps |
+| 4 | Consistency and Standards | 2 | Period applies to summary card but not to SubsBar/SubsPie — same control, inconsistent scope |
+| 5 | Error Prevention | 3 | Few error vectors; empty state handled; no destructive inputs |
+| 6 | Recognition Rather Than Recall | 2 | `$`/`%` symbols require recall; no label or tooltip |
+| 7 | Flexibility and Efficiency | 2 | 3 period presets, 2 chart modes; no sort, filter, or export |
+| 8 | Aesthetic and Minimalist Design | 2 | Chart container double-boxed; N-identical breakdown cards |
+| 9 | Error Recovery | 3 | Generic error string present; graceful empty state |
+| 10 | Help and Documentation | 1 | Zero contextual help; no tooltips; no explanation of toggle scope |
+| **Total** | | **23/40** | **Acceptable — significant improvements needed** |
+
+### Anti-Patterns Verdict
+
+Deterministic scan: clean (0 findings). No slop patterns detected. Design is coherent — coral accent restrained, border-radius 12px correct, no gradient text, no side-stripe borders.
+
+### Overall Impression
+
+Structurally sound but analytically misleading. The period toggle is the most prominent control and it only does half its job — charts always show monthly data regardless of period selected. That's the single biggest trust problem. Everything else is refinement.
+
+### What's Working
+
+1. Color-coded breakdown items — meaningful color, not decoration; semanticsLabel on progress bars is a deliberate accessibility choice.
+2. Summary card data density — compact two-column layout with coral accent on total is clean and effective.
+3. SegmentedButton reuse — consistent component for both controls; users learn it once.
+
+### Priority Issues
+
+**[P1] Period filter doesn't apply to chart data**
+SubsBar and SubsPie always render `monthlyAmount` with no multiplier. Summary card says "Total Yearly: $1,200." Chart still shows monthly bars. Creates a misleading mental model. Fix: pass multiplier to chart widgets as a constructor parameter.
+
+**[P1] Chart section has no contextual label**
+Chart container begins directly with SegmentedButton — no heading, no sub-title. Users don't know what temporal scope the chart represents. Fix: add a text heading or subtitle tying the chart to the active period.
+
+**[P2] `$`/`%` chart toggle labels too terse**
+Symbols tell you the unit but not the chart type. First-time users have no way to infer `$` = bar chart, `%` = pie chart. Fix: use "Amount" / "Share" labels, or add icons.
+
+**[P2] N-identical breakdown cards, no visual rhythm**
+ListView emits N copies of the same structure. On 5+ subscriptions this creates visual fatigue. Fix: differentiate the top item; add grouping by price tier or size variation.
+
+**[P3] Chart container double-boxes the chart widget**
+Outer Container with surfaceContainer fill + 16px padding wraps SubsBar which already has its own data area. Fix: remove container fill color or reduce padding.
+
+### Persona Red Flags
+
+**Alex (Power User):** Selects "Year", summary updates, chart doesn't change → trust erosion. No sort, filter, date range, or export. Two controls total for an analytics screen.
+
+**Sam (Accessibility-Dependent):** SegmentedButton and LinearProgressIndicator semantics are good. fl_chart widgets expose no semantic descriptions of chart data — screen reader users hear nothing on the chart region.
+
+**Casey (Distracted Mobile User):** No loading skeleton — full-screen spinner on cold start. Period toggles sit above fold (good). State loss on backgrounding if period is non-default.
+
+### Minor Observations
+
+- `IntrinsicHeight` in `_SummaryCard` triggers double layout pass — cheaper to use fixed-height column children.
+- `SubsPie` appends `SizedBox(height: 12)` inside already-padded chart container — ~28px dead space.
+- `_coral` in `_SummaryCard` hardcodes `Color(0xFFD83434)` — same value as `kCoralAccent` in `app_theme.dart`; single source preferred.
+
+### Questions to Consider
+
+- "What does 'analytics' mean to a user who's tracked subscriptions for 6 months — 'what am I spending?' or 'what changed?'"
+- "The period selector and chart mode toggle are the only two interactions — if you had to cut one, which gives more value?"
+- "What if the chart were removed and the breakdown list did more heavy lifting — larger numbers, trend arrows, category grouping?"
diff --git a/.metadata b/.metadata
index af5cda8..344170c 100644
--- a/.metadata
+++ b/.metadata
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: "edada7c56edf4a183c1735310e123c7f923584f1"
+ revision: "559ffa3f75e7402d65a8def9c28389a9b2e6fe42"
channel: "stable"
project_type: app
@@ -13,26 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: edada7c56edf4a183c1735310e123c7f923584f1
- base_revision: edada7c56edf4a183c1735310e123c7f923584f1
- - platform: android
- create_revision: edada7c56edf4a183c1735310e123c7f923584f1
- base_revision: edada7c56edf4a183c1735310e123c7f923584f1
+ create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
+ base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
- platform: ios
- create_revision: edada7c56edf4a183c1735310e123c7f923584f1
- base_revision: edada7c56edf4a183c1735310e123c7f923584f1
- - platform: linux
- create_revision: edada7c56edf4a183c1735310e123c7f923584f1
- base_revision: edada7c56edf4a183c1735310e123c7f923584f1
- - platform: macos
- create_revision: edada7c56edf4a183c1735310e123c7f923584f1
- base_revision: edada7c56edf4a183c1735310e123c7f923584f1
- - platform: web
- create_revision: edada7c56edf4a183c1735310e123c7f923584f1
- base_revision: edada7c56edf4a183c1735310e123c7f923584f1
- - platform: windows
- create_revision: edada7c56edf4a183c1735310e123c7f923584f1
- base_revision: edada7c56edf4a183c1735310e123c7f923584f1
+ create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
+ base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42
# User provided section
diff --git a/CLAUDE.md b/CLAUDE.md
index e58a71f..8c66fb1 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Communication Style
-Use caveman skill. Terse responses. Drop filler, articles, hedging. Fragments OK. Technical terms exact. Code unchanged.
+Must use caveman skill. Terse responses. Drop filler, articles, hedging. Fragments OK. Technical terms exact. Code unchanged.
**Prefer MCP over raw terminal** for tooling — especially **Dart/Flutter**: use the configured Dart MCP server (analysis, fixes, project queries) before defaulting to CLI-only workflows when the MCP tool covers the task.
@@ -17,7 +17,10 @@ Use caveman skill. Terse responses. Drop filler, articles, hedging. Fragments OK
- **MCP over CLI** for Dart/Flutter tooling (analysis, fixes, project queries) when the MCP tool covers the task.
- dont use git commands.
- **UI/UX Excellence:** Exhibit impeccable skill in frontend design. Deliver modern, polished, and pixel-perfect layouts with exceptional attention to spacing, typography, and visual hierarchy.
-- dont use git commands.
+- NEVER use `shrinkWrap: true` — use `ConstrainedBox(maxHeight: N)` or `Expanded`/`Flexible`
+- NEVER spread `.map()` results inline in a Column — use `ListView.builder` or `GridView.builder`
+- NEVER use raw `Map` in the presentation layer — wrap in typed model classes
+- Prefer widget classes (`StatelessWidget`/`StatefulWidget`/`ConsumerWidget`) over widget functions or inline builders
## 1. Think Before Coding
diff --git a/all_lint_rules.yaml b/all_lint_rules.yaml
new file mode 100644
index 0000000..0ed53c3
--- /dev/null
+++ b/all_lint_rules.yaml
@@ -0,0 +1,237 @@
+linter:
+ rules:
+ - always_declare_return_types
+ - always_put_control_body_on_new_line
+ - always_put_required_named_parameters_first
+ - always_specify_types
+ - always_use_package_imports
+ - annotate_overrides
+ - annotate_redeclares
+ - avoid_annotating_with_dynamic
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_catches_without_on_clauses
+ - avoid_catching_errors
+ - avoid_classes_with_only_static_members
+ - avoid_double_and_int_checks
+ - avoid_dynamic_calls
+ - avoid_empty_else
+ - avoid_equals_and_hash_code_on_mutable_classes
+ - avoid_escaping_inner_quotes
+ - avoid_field_initializers_in_const_classes
+ - avoid_final_parameters
+ - avoid_function_literals_in_foreach_calls
+ - avoid_futureor_void
+ - avoid_implementing_value_types
+ - avoid_init_to_null
+ - avoid_js_rounded_ints
+ - avoid_multiple_declarations_per_line
+ - avoid_null_checks_in_equality_operators
+ - avoid_positional_boolean_parameters
+ - avoid_print
+ - avoid_private_typedef_functions
+ - avoid_redundant_argument_values
+ - avoid_relative_lib_imports
+ - avoid_renaming_method_parameters
+ - avoid_return_types_on_setters
+ - avoid_returning_null_for_void
+ - avoid_returning_this
+ - avoid_setters_without_getters
+ - avoid_shadowing_type_parameters
+ - avoid_single_cascade_in_expression_statements
+ - avoid_slow_async_io
+ - avoid_type_to_string
+ - avoid_types_as_parameter_names
+ - avoid_types_on_closure_parameters
+ - avoid_unnecessary_containers
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - avoid_web_libraries_in_flutter
+ - await_only_futures
+ - camel_case_extensions
+ - camel_case_types
+ - cancel_subscriptions
+ - cascade_invocations
+ - cast_nullable_to_non_nullable
+ - close_sinks
+ - collection_methods_unrelated_type
+ - combinators_ordering
+ - comment_references
+ - conditional_uri_does_not_exist
+ - constant_identifier_names
+ - control_flow_in_finally
+ - curly_braces_in_flow_control_structures
+ - dangling_library_doc_comments
+ - depend_on_referenced_packages
+ - deprecated_consistency
+ - deprecated_member_use_from_same_package
+ - diagnostic_describe_all_properties
+ - directives_ordering
+ - discarded_futures
+ - do_not_use_environment
+ - document_ignores
+ - empty_catches
+ - empty_constructor_bodies
+ - empty_statements
+ - eol_at_end_of_file
+ - exhaustive_cases
+ - file_names
+ - flutter_style_todos
+ - hash_and_equals
+ - implementation_imports
+ - implicit_call_tearoffs
+ - implicit_reopen
+ - invalid_case_patterns
+ - invalid_runtime_check_with_js_interop_types
+ - join_return_with_assignment
+ - leading_newlines_in_multiline_strings
+ - library_annotations
+ - library_names
+ - library_prefixes
+ - library_private_types_in_public_api
+ - lines_longer_than_80_chars
+ - literal_only_boolean_expressions
+ - matching_super_parameters
+ - missing_code_block_language_in_doc_comment
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_default_cases
+ - no_duplicate_case_values
+ - no_leading_underscores_for_library_prefixes
+ - no_leading_underscores_for_local_identifiers
+ - no_literal_bool_comparisons
+ - no_logic_in_create_state
+ - no_runtimeType_toString
+ - no_self_assignments
+ - no_wildcard_variable_uses
+ - non_constant_identifier_names
+ - noop_primitive_operations
+ - null_check_on_nullable_type_parameter
+ - null_closures
+ - omit_local_variable_types
+ - omit_obvious_local_variable_types
+ - omit_obvious_property_types
+ - one_member_abstracts
+ - only_throw_errors
+ - overridden_fields
+ - package_names
+ - package_prefixed_library_names
+ - parameter_assignments
+ - prefer_adjacent_string_concatenation
+ - prefer_asserts_in_initializer_lists
+ - prefer_asserts_with_message
+ - prefer_collection_literals
+ - prefer_conditional_assignment
+ - prefer_const_constructors
+ - prefer_const_constructors_in_immutables
+ - prefer_const_declarations
+ - prefer_const_literals_to_create_immutables
+ - prefer_constructors_over_static_methods
+ - prefer_contains
+ - prefer_double_quotes
+ - prefer_expression_function_bodies
+ - prefer_final_fields
+ - prefer_final_in_for_each
+ - prefer_final_locals
+ - prefer_final_parameters
+ - prefer_for_elements_to_map_fromIterable
+ - prefer_foreach
+ - prefer_function_declarations_over_variables
+ - prefer_generic_function_type_aliases
+ - prefer_if_elements_to_conditional_expressions
+ - prefer_if_null_operators
+ - prefer_initializing_formals
+ - prefer_inlined_adds
+ - prefer_int_literals
+ - prefer_interpolation_to_compose_strings
+ - prefer_is_empty
+ - prefer_is_not_empty
+ - prefer_is_not_operator
+ - prefer_iterable_whereType
+ - prefer_mixin
+ - prefer_null_aware_method_calls
+ - prefer_null_aware_operators
+ - prefer_relative_imports
+ - prefer_single_quotes
+ - prefer_spread_collections
+ - prefer_typing_uninitialized_variables
+ - prefer_void_to_null
+ - provide_deprecation_message
+ - public_member_api_docs
+ - recursive_getters
+ - remove_deprecations_in_breaking_versions
+ - require_trailing_commas
+ - secure_pubspec_urls
+ - sized_box_for_whitespace
+ - sized_box_shrink_expand
+ - slash_for_doc_comments
+ - sort_child_properties_last
+ - sort_constructors_first
+ - sort_pub_dependencies
+ - sort_unnamed_constructors_first
+ - specify_nonobvious_local_variable_types
+ - specify_nonobvious_property_types
+ - strict_top_level_inference
+ - switch_on_type
+ - test_types_in_equals
+ - throw_in_finally
+ - tighten_type_of_initializing_formals
+ - type_annotate_public_apis
+ - type_init_formals
+ - type_literal_in_constant_pattern
+ - unawaited_futures
+ - unintended_html_in_doc_comment
+ - unnecessary_async
+ - unnecessary_await_in_return
+ - unnecessary_brace_in_string_interps
+ - unnecessary_breaks
+ - unnecessary_const
+ - unnecessary_constructor_name
+ - unnecessary_final
+ - unnecessary_getters_setters
+ - unnecessary_ignore
+ - unnecessary_lambdas
+ - unnecessary_late
+ - unnecessary_library_directive
+ - unnecessary_library_name
+ - unnecessary_new
+ - unnecessary_null_aware_assignments
+ - unnecessary_null_aware_operator_on_extension_on_nullable
+ - unnecessary_null_checks
+ - unnecessary_null_in_if_null_operators
+ - unnecessary_nullable_for_final_variable_declarations
+ - unnecessary_overrides
+ - unnecessary_parenthesis
+ - unnecessary_raw_strings
+ - unnecessary_statements
+ - unnecessary_string_escapes
+ - unnecessary_string_interpolations
+ - unnecessary_this
+ - unnecessary_to_list_in_spreads
+ - unnecessary_unawaited
+ - unnecessary_underscores
+ - unreachable_from_main
+ - unrelated_type_equality_checks
+ - unsafe_variance
+ - use_build_context_synchronously
+ - use_colored_box
+ - use_decorated_box
+ - use_enums
+ - use_full_hex_values_for_flutter_colors
+ - use_function_type_syntax_for_parameters
+ - use_if_null_to_convert_nulls_to_bools
+ - use_is_even_rather_than_modulo
+ - use_key_in_widget_constructors
+ - use_late_for_private_fields_and_variables
+ - use_named_constants
+ - use_null_aware_elements
+ - use_raw_strings
+ - use_rethrow_when_possible
+ - use_setters_to_change_properties
+ - use_string_buffers
+ - use_string_in_part_of_directives
+ - use_super_parameters
+ - use_test_throws_matchers
+ - use_to_and_as_if_applicable
+ - use_truncating_division
+ - valid_regexps
+ - void_checks
\ No newline at end of file
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 0682a3e..e8c0ab9 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -7,9 +7,16 @@
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
-include: package:flutter_lints/flutter.yaml
+include: analysis_options_without_plugins.yaml
+
+plugins:
+ #! DONT REMOVE: its false positive and riverpod lints dont work without it
+ riverpod_lint: ^3.1.4
+
analyzer:
+ errors:
+ public_member_api_docs: ignore
exclude:
- '**/*.g.dart' # Exclude generated files
- '**/*.freezed.dart' # Exclude freezed files
diff --git a/analysis_options_without_plugins.yaml b/analysis_options_without_plugins.yaml
new file mode 100644
index 0000000..304950d
--- /dev/null
+++ b/analysis_options_without_plugins.yaml
@@ -0,0 +1,115 @@
+include: all_lint_rules.yaml
+analyzer:
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+ errors:
+ # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts.
+ # We explicitly enabled even conflicting rules and are fixing the conflict
+ # in this file
+ included_file_warning: ignore
+ # false positive when using Freezed
+ invalid_annotation_target: ignore
+ # I prefer specifying a parameter on a widget even if they are unused (such as Key)
+ # for the sake of consistency.
+ unused_element_parameter: false
+ unrecognized_error_code: ignore
+ # Remove once we require analyzer 8.0.0
+ # We currently support both 7.0 and 8.0, so we can't migrate yet.
+ deprecated_member_use: ignore
+ # Analyzer API is experimental, but kind of necessary for analyzer plugins
+ experimental_member_use: ignore
+
+linter:
+ rules:
+ plugins_in_inner_options: false
+ # No good way to "fix" the diagnostic. Cf https://github.com/dart-lang/sdk/issues/61976
+ unsafe_variance: false
+
+ # Conflicts with having to specify types on public properties
+ omit_obvious_property_types: false
+
+ # Using it on purpose for signaling that an API can be both async and sync
+ avoid_futureor_void: false
+
+ # I hate it
+ specify_nonobvious_property_types: false
+ specify_nonobvious_local_variable_types: false
+
+ # Clashes with newer dartfmt
+ require_trailing_commas: false
+
+ # Maybe later. Too many to fix right now.
+ document_ignores: false
+
+ # Not an issue if using dartfmt
+ no_adjacent_strings_in_list: false
+
+ # False positive for custom enum-like classes (such as Flutter's "Colors")
+ avoid_classes_with_only_static_members: false
+
+ # False positive when the future is returned by the function
+ discarded_futures: false
+
+ # Low value and lacks a quick fix
+ combinators_ordering: false
+
+ # Conflicts with unused variables
+ no_leading_underscores_for_local_identifiers: false
+
+ # false positive
+ one_member_abstracts: false
+
+ # too verbose
+ prefer_final_parameters: false
+
+ # Too verbose with little value, and this is taken care of by the Flutter devtool anyway.
+ diagnostic_describe_all_properties: false
+
+ # Personal preference. I prefer "if (bool) return;" over having it in multiple lines
+ always_put_control_body_on_new_line: false
+
+ # Personal preference. I don't find it more readable
+ cascade_invocations: false
+
+ # Conflicts with `prefer_single_quotes`
+ # Single quotes are easier to type and don't compromise on readability.
+ prefer_double_quotes: false
+
+ # Conflicts with `omit_local_variable_types` and other rules.
+ # As per Dart guidelines, we want to avoid unnecessary types to make the code
+ # more readable.
+ # See https://dart.dev/guides/language/effective-dart/design#avoid-type-annotating-initialized-local-variables
+ always_specify_types: false
+
+ # Incompatible with `prefer_final_locals`
+ # Having immutable local variables makes larger functions more predictable
+ # so we will use `prefer_final_locals` instead.
+ unnecessary_final: false
+
+ # Not quite suitable for Flutter, which may have a `build` method with a single
+ # return, but that return is still complex enough that a "body" is worth it.
+ prefer_expression_function_bodies: false
+
+ # Conflicts with the convention used by flutter, which puts `Key key`
+ # and `@required Widget child` last.
+ always_put_required_named_parameters_first: false
+
+ # This project doesn't use Flutter-style todos
+ flutter_style_todos: false
+
+ # There are situations where we voluntarily want to catch everything,
+ # especially as a library.
+ avoid_catches_without_on_clauses: false
+
+ # Boring as it sometimes force a line of 81 characters to be split in two.
+ # As long as we try to respect that 80 characters limit, going slightly
+ # above is fine.
+ lines_longer_than_80_chars: false
+
+ # Conflicts with disabling `implicit-dynamic`
+ avoid_annotating_with_dynamic: false
+
+ # conflicts with `prefer_relative_imports`
+ always_use_package_imports: false
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 275b9c9..d2d91a7 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -4,6 +4,7 @@ import java.io.FileInputStream
plugins {
id("com.android.application")
id("dev.flutter.flutter-gradle-plugin")
+ id("org.jetbrains.kotlin.plugin.compose") version "2.2.20"
}
val keystoreProperties = Properties()
@@ -18,6 +19,10 @@ android {
compileSdk = 36
ndkVersion = flutter.ndkVersion
+ buildFeatures {
+ compose = true
+ }
+
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_11
@@ -80,6 +85,9 @@ dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
implementation("androidx.window:window:1.0.0")
implementation("androidx.window:window-java:1.0.0")
+ implementation("androidx.glance:glance-appwidget:1.1.1")
+ implementation("androidx.glance:glance-material3:1.1.1")
+ implementation("androidx.work:work-runtime-ktx:2.11.2")
}
flutter {
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
new file mode 100644
index 0000000..1faaabe
--- /dev/null
+++ b/android/app/proguard-rules.pro
@@ -0,0 +1,4 @@
+-keep class androidx.glance.** { *; }
+-keep class io.devopen.subzilla.widget.** { *; }
+-keepclassmembers class * extends androidx.glance.appwidget.GlanceAppWidget { *; }
+-keepclassmembers class * extends androidx.glance.appwidget.GlanceAppWidgetReceiver { *; }
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 96b4878..58fe1fa 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -36,6 +36,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+