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 @@ + + + + + + + + + + + + + + + + + + + + + + + +