0.1.1#83
Conversation
…eton hooked into AppStartup.
…droid Glance widgets - iOS SubsWidget extension: MonthlySpend, NextDue, Upcoming widgets (all sizes) - iOS Live Activity / Dynamic Island via ActivityKit + MethodChannel - Android Glance widgets: MonthlySpend, NextDue, Upcoming with receivers - Flutter WidgetUpdateService wires SubsController + SettingsController → home_widget - LiveActivityService starts activity for subs due today on app launch - Debug tile in Settings to force-start Live Activity for testing - Fix SubsDataReader key: flutter_subs_data → subs_data
- Add ReminderMode (none/onDay/dayBefore/both) to SubSlice, gating notification scheduling - Add cardLastFour field with an input sheet on the detail screen - Add sort sheet (name/amount asc/desc) and drag-to-reorder on the home list - Add monthly/yearly toggle with a liquid-glass segmented control
- Add SubStatus (active/freeTrial/paused/cancelled), note, and trialEndDate to SubSlice - Add status picker widget and badge; filter paused/cancelled subs out of home totals, analytics, and home-screen widget data via activeOnly - Schedule trial-end reminders instead of recurring ones when status is freeTrial - Refactor SubsBar/SubsPie to take slices/currency as params instead of watching providers directly - Use locale-aware DateFormat instead of hardcoded "MMM d" / "EEE" patterns - Add CLAUDE.md lint guidelines (no shrinkWrap, no inline .map() in Column, etc.)
|
Warning Review limit reached
More reviews will be available in 23 minutes and 19 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (15)
📝 WalkthroughWalkthroughThis PR updates subscription data models and serialization, adds widget/live-activity services and platform integrations, rewires analytics/home/detail/settings screens, and refreshes translations, lint config, and tests across Flutter, Android, and iOS. ChangesSubscription Experience Refresh
Sequence Diagram(s)Widget refresh flow sequenceDiagram
participant SubsController
participant WidgetUpdateService
participant HomeWidget
participant MonthlySpendWidget
participant NextDueWidget
participant UpcomingWidget
SubsController->>WidgetUpdateService: update(subs, currencySymbol)
WidgetUpdateService->>HomeWidget: saveWidgetData("subs_data")
WidgetUpdateService->>HomeWidget: updateWidget("MonthlySpendWidget")
WidgetUpdateService->>HomeWidget: updateWidget("NextDueWidget")
WidgetUpdateService->>HomeWidget: updateWidget("UpcomingWidget")
HomeWidget-->>MonthlySpendWidget: refresh
HomeWidget-->>NextDueWidget: refresh
HomeWidget-->>UpcomingWidget: refresh
Live activity start flow sequenceDiagram
participant AppStartup
participant SettingsController
participant SubsController
participant LiveActivityService
participant LiveActivityChannel
participant ActivityKit
AppStartup->>SettingsController: resolve settings
AppStartup->>SubsController: load subscriptions
AppStartup->>LiveActivityService: startIfDueToday(subs, currencySymbol)
LiveActivityService->>LiveActivityChannel: invokeMethod("startActivity")
LiveActivityChannel->>ActivityKit: request Activity<SubsDueAttributes>
ActivityKit-->>LiveActivityChannel: currentActivity
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
🧹 Nitpick comments (14)
android/app/src/main/kotlin/io/devopen/subzilla/widget/SubsGlanceWidget.kt (1)
140-161: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffWidget copy is hardcoded English; not localized for the
trlocale.Strings like
"Add a subscription","No subscriptions","THIS MONTH","NEXT DUE","UPCOMING","Today", and the"subscription"/"subscriptions"pluralization are inlined. Since the app ships antrlocale, these home-screen widgets stay English for Turkish users. Consider sourcing them from Android string resources (selected via the user's locale) so the widget matches the in-app language.As per coding guidelines: supported locales are
enandtr.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@android/app/src/main/kotlin/io/devopen/subzilla/widget/SubsGlanceWidget.kt` around lines 140 - 161, The widget copy in SubsGlanceWidget is hardcoded in English, so Turkish users won’t see localized text; move all visible labels and pluralized subscription text to Android string resources and read them from the widget using the current locale. Update the strings used in SubsGlanceWidget’s EmptyState and main content (including “THIS MONTH”, “NEXT DUE”, “UPCOMING”, “Today”, and singular/plural “subscription(s)”) to source from resources for the supported en and tr locales.Source: Coding guidelines
lib/widgets/glass_nav_bar.dart (1)
75-87: 🩺 Stability & Availability | 🔵 TrivialUpdate
liquid_glass_rendererusage for Impeller-only support and API verification.The
liquid_glass_rendererpackage version0.2.0-dev.4relies strictly on the Impeller rendering engine and currently does not support Web, Windows, or Linux platforms. Ensure your target devices are compatible (iOS/Android with Impeller enabled) or plan to implement a fallback. Additionally, confirm that parametersglassColor,ambientStrength, andsaturationare still valid in this specific pre-release build, as parameter names and validation logic frequently shift between milestones.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/widgets/glass_nav_bar.dart` around lines 75 - 87, The LiquidGlassLayer/LiquidGlass setup here must be verified against liquid_glass_renderer 0.2.0-dev.4, which is Impeller-only and not supported on Web, Windows, or Linux. Update the GlassNavBar implementation to either guard this widget behind an iOS/Android Impeller-only check or provide a fallback path for unsupported platforms, and confirm the LiquidGlassSettings fields used in LiquidGlassLayer—especially glassColor, ambientStrength, and saturation—still match the current pre-release API.lib/screens/settings_screen.dart (1)
511-531: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor: add a
constconstructor to_TestLiveActivityTile.Other tiles are instantiated with
const; this one (line 115) is not, and the class lacks a const constructor. Debug-only, so the hardcoded strings are acceptable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/settings_screen.dart` around lines 511 - 531, The _TestLiveActivityTile widget should match the other const-instantiated tiles by adding a const constructor to the _TestLiveActivityTile class and updating its usage so it can be created with const. Locate the StatelessWidget named _TestLiveActivityTile in settings_screen.dart and make it constructible at compile time without changing its existing hardcoded debug strings or onTap behavior.lib/screens/sub_detail_screen.dart (1)
476-521: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winDispose the local
TextEditingControllers.
_showCardInputand_showNoteInputallocate aTextEditingControllerthat is never disposed when the sheet closes, leaking the controller (and its listeners) each time the sheet opens. Wrap the body in aStatefulWidget/hook or dispose in the sheet's lifecycle.Also applies to: 523-563
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/sub_detail_screen.dart` around lines 476 - 521, The local TextEditingController created in _showCardInput is never disposed, so the bottom sheet leaks controller resources each time it opens. Update _showCardInput (and the matching _showNoteInput referenced in the comment) to manage the controller lifecycle by using a StatefulWidget/hook or otherwise disposing it when the sheet is dismissed, while keeping the existing submit/save behavior tied to the controller and onSelected callback.CLAUDE.md (1)
18-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate directive.
dont use git commands.appears on both Line 18 and Line 20. Drop one.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CLAUDE.md` around lines 18 - 20, The CLAUDE.md guidance contains a duplicated “dont use git commands.” directive, so remove one of the repeated entries and keep only a single instance in the instruction list. Update the surrounding bullet text in the document so the guidance remains clean and non-redundant, preserving the unique “UI/UX Excellence” directive and the single git restriction.lib/main.dart (1)
39-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTODO: merge light/dark theme providers. Tracked inline only.
Want me to open an issue to track consolidating
lightThemeProvider/darkThemeProviderinto a single provider?🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/main.dart` at line 39, The theme setup still uses separate lightThemeProvider and darkThemeProvider providers; consolidate them into one shared provider in main.dart that returns both themes together as a single value (tuple/class) and update any consumers to read from that combined source. Keep the existing theme-building logic in the new unified provider so the app still exposes both light and dark themes through one entry point.lib/services/live_activity_service.dart (1)
46-54: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueUnbounded day-by-day loop for old daily subscriptions.
For
Frequency.dailythe loop advances one day at a time fromstartDate, so a daily sub started years ago iterates hundreds/thousands of times on every startup call. Bounded but wasteful on the startup path; consider normalizing daily/weekly directly to the first candidate ≥ today via arithmetic.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/services/live_activity_service.dart` around lines 46 - 54, The _isDueToday method in LiveActivityService uses an unbounded day-by-day loop to reach today, which is too expensive for old daily subscriptions on startup. Update the candidate normalization logic in _isDueToday (and, if needed, _advance) to compute the first occurrence on or after today directly with date arithmetic for Frequency.daily and Frequency.weekly instead of iterating one day at a time. Keep the behavior equivalent for determining whether the normalized candidate matches todayDate.lib/screens/app_startup.dart (1)
24-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueFire-and-forget async in
initState.
UpdateService.instance.checkAndUpdate()returns aFuturethat is discarded; wrap withunawaited(...)(already imported viadart:async) to make the intent explicit and satisfy lint rules.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/screens/app_startup.dart` around lines 24 - 28, The initState method in AppStartup is calling UpdateService.instance.checkAndUpdate() without handling its returned Future, so make the fire-and-forget intent explicit by wrapping that call with unawaited(...) in initState; use the existing dart:async import and keep the change limited to the AppStartup lifecycle method.lib/providers/subs_controller.dart (3)
59-59: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
scheduleNotification()is fire-and-forget without error handling.In
build()it isawaited, but inaddSlice/removeAt/updateAt/reorderSlices/sortSlices/clearthe returnedFutureis discarded bare. If scheduling throws (plugin/permission failure), it surfaces as an unhandled async error rather than being contained, unlike the adjacent widget update which is wrapped inunawaited(...)and already swallows errors internally. Wrap consistently (and ideally guard the call) to avoid unobserved failures.♻️ Suggested change
- scheduleNotification(); + unawaited(scheduleNotification());Applies to the other mutation methods (
removeAt,updateAt,reorderSlices,sortSlices,clear) as well.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/providers/subs_controller.dart` at line 59, The fire-and-forget call to scheduleNotification() in SubsController mutations is not safely handled, so async failures can surface as unhandled errors. Update the mutation methods that call scheduleNotification() (addSlice, removeAt, updateAt, reorderSlices, sortSlices, clear) to treat it consistently with the build() path by awaiting or wrapping it in unawaited with proper internal error containment, and ensure the scheduling call is guarded so failures do not escape.
52-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the repeated currency-read + widget-update block.
The identical three-line pattern (read currency from settings,
unawaited(WidgetUpdateService.instance.update(...))) is duplicated across six methods. Consolidate into a private helper to reduce drift risk.♻️ Proposed helper
+ void _refreshWidgets() { + final currency = + ref.read(settingsControllerProvider).value?.currency.symbol ?? ''; + unawaited(WidgetUpdateService.instance.update(state.value ?? [], currency)); + }Then replace each duplicated block with
_refreshWidgets();.Also applies to: 60-61, 195-196, 202-203, 213-214, 221-222
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/providers/subs_controller.dart` around lines 52 - 53, The currency-read plus widget update sequence is duplicated in SubsController across multiple methods, so extract it into a private helper to avoid drift. Create a helper in SubsController that reads the currency from settings and calls WidgetUpdateService.instance.update with the current state list, then replace each repeated block in the affected methods with a single _refreshWidgets() call.
62-67: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReview-prompt cadence may exceed store throttling.
Requesting an in-app review on every third subscription (
count % 3 == 0→ 3, 6, 9, …) fires repeatedly. Both App Store and Play throttle prompts (iOS caps at ~3/year and silently drops extras), so triggering this often is wasted at best and can feel spammy at the boundary. Consider gating on a one-time milestone or persisting a "review requested" flag instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/providers/subs_controller.dart` around lines 62 - 67, The review prompt logic in subs_controller.dart is firing on every multiple of three subscriptions via the count check, which can repeatedly trigger InAppReview.instance.requestReview(). Update the subscription milestone logic so SubsController only requests a review once per intended milestone, or persist a “review requested” flag and check it before calling InAppReview.instance.isAvailable/requestReview, using the existing state.value count path to locate the change.ios/SubsWidget/NextDueDateHelper.swift (1)
8-36: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueLogic is correct; consider guarding the force unwraps.
The next-due computation is sound (future
startDatereturns as-is; past advances to the next occurrence with month-end clamping handled). The forced unwraps ondateComponents(...).day!/.weekOfYear!/.month!/.year!andcal.date(byAdding:)!are safe for the Gregorian calendar but will crash if a component request returnsnil. Optional fallbacks (?? 0/ early return) would harden this widget path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ios/SubsWidget/NextDueDateHelper.swift` around lines 8 - 36, The next-due calculation in NextDueDateHelper.nextDueDate is fine, but the forced unwraps on Calendar.dateComponents(...).day/weekOfYear/month/year and Calendar.date(byAdding:) can crash if a component is unavailable. Update the logic to use safe optional handling or early returns around those Calendar calls, keeping the existing daily/weekly/monthly/yearly behavior and month-end/year-end clamping intact.ios/SubsWidget/SubsWidgetBundle.swift (1)
17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the leftover
// add thisscaffolding comment.Proposed tweak
- SubsDueWidgetLiveActivity() // add this + SubsDueWidgetLiveActivity()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ios/SubsWidget/SubsWidgetBundle.swift` at line 17, Remove the leftover scaffolding comment from SubsWidgetBundle so the SubsDueWidgetLiveActivity() entry is kept clean and only the actual widget declaration remains. Update the SubsWidgetBundle.swift snippet to eliminate the inline “// add this” text and leave the SubsDueWidgetLiveActivity() call unchanged.ios/SubsWidget/WidgetBackground.swift (1)
9-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDouble padding on the pre-iOS 17 path.
Callers (e.g.
upcomingListLine 63-64) already apply.padding()before.widgetBackground(); the additional.padding()here compounds it only on iOS < 16/17, producing inconsistent insets vs. thecontainerBackgroundbranch. Consider dropping the inner.padding()so spacing is owned by the caller.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ios/SubsWidget/WidgetBackground.swift` around lines 9 - 12, The pre-iOS 17 fallback in WidgetBackground is applying extra padding on top of padding already added by callers like upcomingList, causing inconsistent spacing compared with the containerBackground branch. Update WidgetBackground so the fallback path only sets the background and does not add its own padding, leaving spacing to the caller and keeping both branches aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@analysis_options.yaml`:
- Around line 12-14: The dependency pin for riverpod_lint is still on the
prerelease channel, so update the version in analysis_options.yaml from the dev
build to the stable release. Change the existing riverpod_lint entry under
plugins to use ^3.1.4, keeping the surrounding plugin configuration intact.
In `@android/app/src/main/AndroidManifest.xml`:
- Line 43: The widget receiver declarations are currently exported, which allows
other apps to send APPWIDGET_UPDATE broadcasts to them. Update the receiver
entries in AndroidManifest.xml for the widget components to be non-exported, or
protect them with an explicit permission, and make sure the same fix is applied
to each affected receiver declaration. Locate the relevant receiver tags by
their android:exported setting and adjust the manifest so only the system can
invoke them.
In `@android/app/src/main/kotlin/io/devopen/subzilla/widget/SubsGlanceWidget.kt`:
- Around line 114-115: The amount formatting in SubsGlanceWidget uses
String.format without an explicit Locale, so the decimal separator can change
with the device locale; update all four formatting sites to pass a pinned Locale
explicitly. Fix the Text render paths in SubsGlanceWidget by choosing a
consistent locale (for example Locale.US) in each String.format("%.2f", ...)
call so the currency amount displays uniformly regardless of device language.
In `@ios/LiveActivityChannel.swift`:
- Around line 34-41: startActivity(subsJson:) in LiveActivityChannel is too
permissive and can crash or silently fail; update it to safely compute the
staleDate without any force unwrap, explicitly guard against starting when
currentActivity is already set, and replace the try? Activity.request flow with
explicit error handling so failures can be reported back to Flutter. If you need
to replace an existing activity, do it in a coordinated way with endActivity()
before requesting a new one, and avoid overwriting currentActivity until the old
activity has been cleaned up.
In `@ios/Runner.xcodeproj/project.pbxproj`:
- Around line 16-18: Remove the redundant PBXBuildFile entries for
SubsWidgetExtension that are already covered by fileSystemSynchronizedGroups in
the Xcode project. In project.pbxproj, delete the orphaned build file references
associated with SubsDueAttributes.swift, SubsDueWidget.swift, ColorHelper.swift,
and the other duplicate entry mentioned in the review, and ensure the
SubsWidgetExtension target relies only on the synchronized group configuration.
In `@ios/Runner/AppDelegate.swift`:
- Around line 8-18: `AppDelegate.swift` is missing the
`UNUserNotificationCenterDelegate` setup needed by
`flutter_local_notifications`, so foreground notifications and tap callbacks
won’t be handled. Update `AppDelegate` to conform to
`UNUserNotificationCenterDelegate`, assign
`UNUserNotificationCenter.current().delegate = self` inside
`application(_:didFinishLaunchingWithOptions:)` after
`GeneratedPluginRegistrant.register(with:)`, and add the
`userNotificationCenter(_:willPresent:withCompletionHandler:)` handler on
`AppDelegate` so notifications are presented while the app is in the foreground.
In `@ios/SubsWidget/MonthlySpendWidget.swift`:
- Line 8: The small widget’s empty-state logic in MonthlySpendSmallView is
missing the same non-empty subscription check used by MonthlySpendMediumView, so
it can render a zero-state summary instead of emptyState. Update the
MonthlySpendSmallView condition around entry.widgetData to also require that
data.subs is not empty, matching the guard used in MonthlySpendMediumView so
both views handle empty data consistently.
In `@ios/SubsWidget/SubsDueWidget.swift`:
- Line 43: The ForEach identity in SubsDueWidget is using \.name, which can
collide when multiple subscriptions share the same name and cause unstable row
rendering; update the ForEach in SubsDueWidget (including the other matching
ForEach mentioned in the review) to use a stable unique identifier such as an
existing id field, or key by Array.enumerated() offset if no unique model id
exists, so each row has a consistent identity.
In `@ios/SubsWidget/SubsTimelineProvider.swift`:
- Around line 27-36: The parseDate helper currently uses a brittle DateFormatter
fallback with a fixed millisecond pattern, which is unnecessary for the ISO 8601
strings being produced upstream. Simplify SubsTimelineProvider.parseDate to rely
only on ISO8601DateFormatter configured with .withInternetDateTime and
.withFractionalSeconds, and remove the DateFormatter branch entirely so all date
parsing is handled consistently with timezone support.
In `@ios/SubsWidget/UpcomingWidget.swift`:
- Line 35: The UpcomingWidget ForEach is using sub.name as the identity, which
can merge duplicate subscription names and collapse rows. Update the ForEach in
UpcomingWidget.swift to use a stable unique key for each item, or switch to
iterating Array(items.enumerated()) and key by the offset since the order is
fixed. Make sure the identity used in ForEach uniquely distinguishes each
subscription row.
In `@lib/screens/app_startup.dart`:
- Around line 34-47: The live activity startup guard in the settings provider
branch is being flipped too early in app_startup.dart, which can block startup
if subscriptions are not yet loaded. Update the logic around
_liveActivityStarted, ref.read(subsControllerProvider), and the whenData
callback so the flag is set only inside the whenData callback after subs is
available and LiveActivityService.instance.startIfDueToday is actually
scheduled. Keep the post-frame callback and settings load flow the same, but
ensure the one-time guard is applied only once both settings and subscription
data are ready.
In `@lib/screens/home_screen.dart`:
- Line 136: The row key in the reorderable list is using the item index, which
makes identities change after reordering and can mix up row state. Update the
key in the home screen row builder to use a stable identifier from the slice
itself in the widget that currently uses ValueKey('${slice.name}-$index'), so
each row keeps the same identity regardless of position changes.
- Line 431: The sorting in home_screen is using raw amount in
notifier.sortSlices, but the screen’s ranking/totals are based on monthlyAmount,
so mixed-frequency slices can be ordered incorrectly. Update the sort logic in
the relevant home screen sorting paths (including the notifier.sortSlices calls
used for the amount ordering) to compare normalized monthlyAmount instead of
amount, and keep the existing comparator structure around the affected slice
ranking code.
In `@lib/screens/settings_screen.dart`:
- Around line 98-101: The version shown in the settings/about item is hardcoded
to an outdated value, so update the SettingsScreen about entry to source the app
version dynamically instead of using a literal. Use the existing settings item
in settings_screen.dart and replace the fixed subtitle in that widget with a
value from package_info_plus or another pubspec-derived version source so it
stays in sync with the app release.
In `@lib/screens/sub_detail_screen.dart`:
- Around line 453-474: The trial-end picker in _showTrialEndPicker currently
saves on any dismissal because onSelected(selected) is invoked from the modal
future, so cancel/tap-outside also commits a value. Update this flow to be
confirm-driven like the other pickers in sub_detail_screen.dart: add an explicit
Done/Cancel action (or another confirmation mechanism) and only call onSelected
from the confirm action, keeping dismissal from writing DateTime.now() when
current is null.
In `@lib/services/live_activity_service.dart`:
- Around line 62-66: The monthly branch in `_advance` is drifting because it
clamps against the current `date.day` instead of the original subscription
anchor day. Update `_advance` in `live_activity_service.dart` so the monthly
case re-clamps from the original `startDate.day` (or an equivalent preserved
anchor) on every cycle, matching `SubsController._clampDay` behavior and keeping
dates like the 31st from getting stuck on shorter months. Ensure the fix
preserves the existing `Frequency.monthly` flow and uses the same day-of-month
source consistently across notifications and live activity calculations.
In `@lib/widgets/edit_subs_dialog.dart`:
- Line 340: The frequency label lookup in the widget that renders subscription
frequencies uses the inconsistent frequency_names namespace; update the
translation key in edit_subs_dialog.dart’s frequency display logic to match the
same frequency pattern used by add_subs_dialog.dart and sub_detail_screen.dart,
using the lowercased enum name via f.name so the app shows consistent
sentence-case labels.
In `@lib/widgets/sub_zilla_app_bar.dart`:
- Around line 17-25: The AppBar leading logic in SubZillaAppBar is tied to
rootScaffoldKey.currentState, which can be null during the first build and hide
the drawer button even when a drawer exists. Update the leading condition to
depend on a stable drawer-availability signal instead of currentState timing,
and keep the IconButton/openDrawer behavior in the SubZillaAppBar widget so the
menu icon still renders when the Scaffold state is not yet ready.
---
Nitpick comments:
In `@android/app/src/main/kotlin/io/devopen/subzilla/widget/SubsGlanceWidget.kt`:
- Around line 140-161: The widget copy in SubsGlanceWidget is hardcoded in
English, so Turkish users won’t see localized text; move all visible labels and
pluralized subscription text to Android string resources and read them from the
widget using the current locale. Update the strings used in SubsGlanceWidget’s
EmptyState and main content (including “THIS MONTH”, “NEXT DUE”, “UPCOMING”,
“Today”, and singular/plural “subscription(s)”) to source from resources for the
supported en and tr locales.
In `@CLAUDE.md`:
- Around line 18-20: The CLAUDE.md guidance contains a duplicated “dont use git
commands.” directive, so remove one of the repeated entries and keep only a
single instance in the instruction list. Update the surrounding bullet text in
the document so the guidance remains clean and non-redundant, preserving the
unique “UI/UX Excellence” directive and the single git restriction.
In `@ios/SubsWidget/NextDueDateHelper.swift`:
- Around line 8-36: The next-due calculation in NextDueDateHelper.nextDueDate is
fine, but the forced unwraps on
Calendar.dateComponents(...).day/weekOfYear/month/year and
Calendar.date(byAdding:) can crash if a component is unavailable. Update the
logic to use safe optional handling or early returns around those Calendar
calls, keeping the existing daily/weekly/monthly/yearly behavior and
month-end/year-end clamping intact.
In `@ios/SubsWidget/SubsWidgetBundle.swift`:
- Line 17: Remove the leftover scaffolding comment from SubsWidgetBundle so the
SubsDueWidgetLiveActivity() entry is kept clean and only the actual widget
declaration remains. Update the SubsWidgetBundle.swift snippet to eliminate the
inline “// add this” text and leave the SubsDueWidgetLiveActivity() call
unchanged.
In `@ios/SubsWidget/WidgetBackground.swift`:
- Around line 9-12: The pre-iOS 17 fallback in WidgetBackground is applying
extra padding on top of padding already added by callers like upcomingList,
causing inconsistent spacing compared with the containerBackground branch.
Update WidgetBackground so the fallback path only sets the background and does
not add its own padding, leaving spacing to the caller and keeping both branches
aligned.
In `@lib/main.dart`:
- Line 39: The theme setup still uses separate lightThemeProvider and
darkThemeProvider providers; consolidate them into one shared provider in
main.dart that returns both themes together as a single value (tuple/class) and
update any consumers to read from that combined source. Keep the existing
theme-building logic in the new unified provider so the app still exposes both
light and dark themes through one entry point.
In `@lib/providers/subs_controller.dart`:
- Line 59: The fire-and-forget call to scheduleNotification() in SubsController
mutations is not safely handled, so async failures can surface as unhandled
errors. Update the mutation methods that call scheduleNotification() (addSlice,
removeAt, updateAt, reorderSlices, sortSlices, clear) to treat it consistently
with the build() path by awaiting or wrapping it in unawaited with proper
internal error containment, and ensure the scheduling call is guarded so
failures do not escape.
- Around line 52-53: The currency-read plus widget update sequence is duplicated
in SubsController across multiple methods, so extract it into a private helper
to avoid drift. Create a helper in SubsController that reads the currency from
settings and calls WidgetUpdateService.instance.update with the current state
list, then replace each repeated block in the affected methods with a single
_refreshWidgets() call.
- Around line 62-67: The review prompt logic in subs_controller.dart is firing
on every multiple of three subscriptions via the count check, which can
repeatedly trigger InAppReview.instance.requestReview(). Update the subscription
milestone logic so SubsController only requests a review once per intended
milestone, or persist a “review requested” flag and check it before calling
InAppReview.instance.isAvailable/requestReview, using the existing state.value
count path to locate the change.
In `@lib/screens/app_startup.dart`:
- Around line 24-28: The initState method in AppStartup is calling
UpdateService.instance.checkAndUpdate() without handling its returned Future, so
make the fire-and-forget intent explicit by wrapping that call with
unawaited(...) in initState; use the existing dart:async import and keep the
change limited to the AppStartup lifecycle method.
In `@lib/screens/settings_screen.dart`:
- Around line 511-531: The _TestLiveActivityTile widget should match the other
const-instantiated tiles by adding a const constructor to the
_TestLiveActivityTile class and updating its usage so it can be created with
const. Locate the StatelessWidget named _TestLiveActivityTile in
settings_screen.dart and make it constructible at compile time without changing
its existing hardcoded debug strings or onTap behavior.
In `@lib/screens/sub_detail_screen.dart`:
- Around line 476-521: The local TextEditingController created in _showCardInput
is never disposed, so the bottom sheet leaks controller resources each time it
opens. Update _showCardInput (and the matching _showNoteInput referenced in the
comment) to manage the controller lifecycle by using a StatefulWidget/hook or
otherwise disposing it when the sheet is dismissed, while keeping the existing
submit/save behavior tied to the controller and onSelected callback.
In `@lib/services/live_activity_service.dart`:
- Around line 46-54: The _isDueToday method in LiveActivityService uses an
unbounded day-by-day loop to reach today, which is too expensive for old daily
subscriptions on startup. Update the candidate normalization logic in
_isDueToday (and, if needed, _advance) to compute the first occurrence on or
after today directly with date arithmetic for Frequency.daily and
Frequency.weekly instead of iterating one day at a time. Keep the behavior
equivalent for determining whether the normalized candidate matches todayDate.
In `@lib/widgets/glass_nav_bar.dart`:
- Around line 75-87: The LiquidGlassLayer/LiquidGlass setup here must be
verified against liquid_glass_renderer 0.2.0-dev.4, which is Impeller-only and
not supported on Web, Windows, or Linux. Update the GlassNavBar implementation
to either guard this widget behind an iOS/Android Impeller-only check or provide
a fallback path for unsupported platforms, and confirm the LiquidGlassSettings
fields used in LiquidGlassLayer—especially glassColor, ambientStrength, and
saturation—still match the current pre-release API.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 914be5d3-c86c-4b75-b0b0-bb37a06e7ab1
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (106)
.impeccable/critique/2026-06-07T13-10-40Z__lib-screens-analytics-screen-dart.md.metadataCLAUDE.mdall_lint_rules.yamlanalysis_options.yamlanalysis_options_without_plugins.yamlandroid/app/build.gradle.ktsandroid/app/proguard-rules.proandroid/app/src/main/AndroidManifest.xmlandroid/app/src/main/kotlin/io/devopen/subzilla/widget/NextDueDateHelper.ktandroid/app/src/main/kotlin/io/devopen/subzilla/widget/SubsDataReader.ktandroid/app/src/main/kotlin/io/devopen/subzilla/widget/SubsGlanceWidget.ktandroid/app/src/main/kotlin/io/devopen/subzilla/widget/WidgetRefreshWorker.ktandroid/app/src/main/res/xml/monthly_spend_widget_info.xmlandroid/app/src/main/res/xml/next_due_widget_info.xmlandroid/app/src/main/res/xml/upcoming_widget_info.xmlassets/translations/en.jsonassets/translations/tr.jsonios/LiveActivity/ColorHelper.swiftios/LiveActivity/SubsDueAttributes.swiftios/LiveActivity/SubsDueWidget.swiftios/LiveActivityChannel.swiftios/Runner.xcodeproj/project.pbxprojios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcschemeios/Runner/AppDelegate.swiftios/Runner/Info.plistios/Runner/Runner.entitlementsios/Runner/SceneDelegate.swiftios/SubsWidget/Assets.xcassets/AccentColor.colorset/Contents.jsonios/SubsWidget/Assets.xcassets/AppIcon.appiconset/Contents.jsonios/SubsWidget/Assets.xcassets/Contents.jsonios/SubsWidget/Assets.xcassets/WidgetBackground.colorset/Contents.jsonios/SubsWidget/ColorHelper.swiftios/SubsWidget/Info.plistios/SubsWidget/MonthlySpendWidget.swiftios/SubsWidget/NextDueDateHelper.swiftios/SubsWidget/NextDueWidget.swiftios/SubsWidget/SubsDataReader.swiftios/SubsWidget/SubsDueAttributes.swiftios/SubsWidget/SubsDueWidget.swiftios/SubsWidget/SubsEntry.swiftios/SubsWidget/SubsModels.swiftios/SubsWidget/SubsTimelineProvider.swiftios/SubsWidget/SubsWidgetBundle.swiftios/SubsWidget/UpcomingWidget.swiftios/SubsWidget/WidgetBackground.swiftios/SubsWidgetExtension.entitlementslib/config/fade_extension.dartlib/config/router_config.dartlib/config/router_config.g.dartlib/layout/root_layout.dartlib/main.dartlib/models/settings_view_model.dartlib/models/sub_slice.dartlib/models/sub_slice.freezed.dartlib/models/sub_slice.g.dartlib/providers/brands_provider.dartlib/providers/brands_provider.g.dartlib/providers/settings_controller.dartlib/providers/settings_controller.g.dartlib/providers/subs_controller.dartlib/providers/subs_controller.g.dartlib/providers/theme_provider.dartlib/screens/analytics_screen.dartlib/screens/app_startup.dartlib/screens/calendar_screen.dartlib/screens/color_scheme_page.dartlib/screens/home_screen.dartlib/screens/intro_page.dartlib/screens/onboarding_screen.dartlib/screens/popular_subs_page.dartlib/screens/profile_page.dartlib/screens/settings_screen.dartlib/screens/sub_detail_screen.dartlib/screens/user_name_page.dartlib/services/live_activity_service.dartlib/services/widget_update_service.dartlib/utils/brand_utils.dartlib/utils/color_utils.dartlib/utils/notification_service.dartlib/utils/update_service.dartlib/widgets/action_text_form_field.dartlib/widgets/add_subs_dialog.dartlib/widgets/bar_chart.dartlib/widgets/bottom_controls.dartlib/widgets/brand_logo.dartlib/widgets/color_scheme_picker.dartlib/widgets/edit_subs_dialog.dartlib/widgets/edit_user_profile.dartlib/widgets/floating_sub_card.dartlib/widgets/glass_nav_bar.dartlib/widgets/menu_bar.dartlib/widgets/pie_chart.dartlib/widgets/status_picker.dartlib/widgets/sub_zilla_app_bar.dartpackages/simple_icons/analysis_options.yamlpubspec.yamltest/models/sub_slice_test.darttest/providers/subs_controller_test.darttest/screens/calendar_screen_test.darttest/services/widget_update_service_test.darttest/utils/date_locale_test.darttest/widget_test.darttest/widgets/add_subs_dialog_test.darttest/widgets/color_scheme_settings_test.darttest/widgets/intro_page_test.dart
💤 Files with no reviewable changes (2)
- packages/simple_icons/analysis_options.yaml
- lib/widgets/floating_sub_card.dart
| 1265D5522FD6B5EA003DE5DE /* SubsDueAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1265D54C2FD6B5EA003DE5DE /* SubsDueAttributes.swift */; }; | ||
| 1265D5532FD6B5EA003DE5DE /* SubsDueWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1265D54D2FD6B5EA003DE5DE /* SubsDueWidget.swift */; }; | ||
| 1265D5542FD6B5EA003DE5DE /* ColorHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1265D54B2FD6B5EA003DE5DE /* ColorHelper.swift */; }; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm widget-extension Sources phase membership and locate duplicate Swift sources.
fd -t f 'SubsDueWidget.swift|SubsDueAttributes.swift|ColorHelper.swift|LiveActivityChannel.swift' ios
echo '--- pbxproj Sources phases ---'
rg -n 'in Sources' ios/Runner.xcodeproj/project.pbxprojRepository: DevOpen-io/SubZilla
Length of output: 2847
Remove redundant manual BuildFile entries for SubsWidgetExtension.
The SubsWidgetExtension target utilizes fileSystemSynchronizedGroups to automatically include source files from the SubsWidget directory. The static PBXBuildFile entries at lines 16–18 and 20 (1265D552, 1265D553, 1265D554, 1265D557) effectively duplicate the definitions already resolved by the synchronization mechanism. Delete these orphaned entries to clean up the project configuration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@ios/Runner.xcodeproj/project.pbxproj` around lines 16 - 18, Remove the
redundant PBXBuildFile entries for SubsWidgetExtension that are already covered
by fileSystemSynchronizedGroups in the Xcode project. In project.pbxproj, delete
the orphaned build file references associated with SubsDueAttributes.swift,
SubsDueWidget.swift, ColorHelper.swift, and the other duplicate entry mentioned
in the review, and ensure the SubsWidgetExtension target relies only on the
synchronized group configuration.
| title: 'settings.version'.tr(), | ||
| subtitle: '1.0.0', | ||
| onTap: null, | ||
| ), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Hardcoded version 1.0.0 is stale.
This PR targets 0.1.1, but the About section shows 1.0.0. Consider sourcing it from package_info_plus/pubspec instead of a literal to avoid drift.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/screens/settings_screen.dart` around lines 98 - 101, The version shown in
the settings/about item is hardcoded to an outdated value, so update the
SettingsScreen about entry to source the app version dynamically instead of
using a literal. Use the existing settings item in settings_screen.dart and
replace the fixed subtitle in that widget with a value from package_info_plus or
another pubspec-derived version source so it stays in sync with the app release.
| leading: rootScaffoldKey.currentState?.hasDrawer ?? false | ||
| ? IconButton( | ||
| icon: Icon(CupertinoIcons.bars), | ||
| icon: const Icon(CupertinoIcons.bars), | ||
| onPressed: () { | ||
| rootScaffoldKey.currentState?.openDrawer(); | ||
| }, | ||
| ) | ||
| : null, | ||
| middle: Text("settings.app_name".tr()), | ||
| middle: Text('settings.app_name'.tr()), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Drawer leading can disappear due to nullable currentState timing.
Line 17 gates leading on rootScaffoldKey.currentState, which can be null during initial build. That can leave the menu button missing even when a drawer exists.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/widgets/sub_zilla_app_bar.dart` around lines 17 - 25, The AppBar leading
logic in SubZillaAppBar is tied to rootScaffoldKey.currentState, which can be
null during the first build and hide the drawer button even when a drawer
exists. Update the leading condition to depend on a stable drawer-availability
signal instead of currentState timing, and keep the IconButton/openDrawer
behavior in the SubZillaAppBar widget so the menu icon still renders when the
Scaffold state is not yet ready.
- Android: protect widget receivers with BIND_APPWIDGET permission; pin Locale.US in String.format calls - iOS: remove force-unwrap in LiveActivityChannel staleDate; guard against double-starting activity; add UNUserNotificationCenterDelegate for foreground notifications; fix MonthlySpendSmallView empty-state guard; use enumerated ForEach ids in SubsDueWidget and UpcomingWidget; remove stale comment from SubsWidgetBundle - Dart: fix monthly drift in live_activity_service by anchoring to startDate.day; sort slices by monthlyAmount not raw amount; use stable ValueKey(slice.name) for reorderable list; trial-end picker now requires explicit Save to commit; dispose TextEditingControllers in card/note inputs; fix frequency label namespace in edit_subs_dialog (ALLCAPS → sentence-case); mark fire-and-forget calls with unawaited; bump riverpod_lint to stable ^3.1.4
Summary by CodeRabbit
New Features
Bug Fixes