Skip to content

Feat/lib update tips live versions - #17

Merged
buivietphi merged 3 commits into
mainfrom
feat/lib-update-tips-live-versions
Jul 11, 2026
Merged

buivietphi merged 3 commits into
mainfrom
feat/lib-update-tips-live-versions

Conversation

@buivietphi

@buivietphi buivietphi commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Description

Related Issue

Type of Change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation update
  • Performance improvement
  • CI/CD or build configuration
  • Other (describe below)

Testing

  • Tested on macOS
  • Tested on Windows
  • Flutter analyze passes
  • SDK build passes (if applicable)

Screenshots (if applicable)

Summary by CodeRabbit

  • New Features
    • Added a desktop “app version” indicator with expanded details, release link opening, and retry when checks fail.
    • Updated SDK compatibility tips to show latest/update status with live checking, inline loading/error states, offline messaging, and retry.
  • Localization
    • Added translated SDK update/check labels and actions across supported languages.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5625dffb-8273-487c-8b66-55680f15c094

📥 Commits

Reviewing files that changed from the base of the PR and between 5a57ee8 and a5b930b.

📒 Files selected for processing (5)
  • lib/components/feedback/app_update_pill.dart
  • lib/components/feedback/lib_update_tips.dart
  • lib/core/providers/app_update_provider.dart
  • lib/core/providers/sdk_versions_provider.dart
  • lib/core/utils/sdk_version.dart

📝 Walkthrough

Walkthrough

Adds Riverpod-backed checks for desktop releases and SDK versions, renders app and library update indicators in the title bar, supports retry and release-link actions, and adds localized SDK status labels across supported languages.

Changes

Update and SDK status

Layer / File(s) Summary
App release state and fetching
lib/core/providers/app_update_provider.dart, lib/core/providers/app_version_provider.dart
Loads the installed app version, fetches GitHub release metadata, derives update state, handles errors, and refreshes periodically.
App update pill and title-bar integration
lib/components/feedback/app_update_pill.dart, lib/core/routes/app_shell.dart
Adds collapsed and hover-expanded release UI, release-link fallback behavior, retry actions, and platform-specific title-bar positioning.
SDK version fetching and comparison
lib/core/providers/sdk_versions_provider.dart, lib/core/utils/sdk_version.dart
Fetches Flutter and React Native versions independently, preserves values across retries, tracks fetch states, refreshes periodically, and compares versions.
SDK tip states and localization
lib/components/feedback/lib_update_tips.dart, lib/l10n/*
Connects provider state to SDK rows, renders loading/error/loaded indicators and retry controls, and adds localized status strings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AppShell
  participant AppUpdatePill
  participant AppUpdateNotifier
  participant GitHub
  AppShell->>AppUpdatePill: render title-bar update control
  AppUpdatePill->>AppUpdateNotifier: watch release state
  AppUpdateNotifier->>GitHub: fetch latest release
  GitHub-->>AppUpdateNotifier: release version and URL
  AppUpdateNotifier-->>AppUpdatePill: update availability state
  AppUpdatePill->>GitHub: open release URL
Loading

Possibly related PRs

Poem

A tiny pill watches releases fly,
SDK versions shimmer nearby.
Retry when clouds obscure the view,
Localized labels make updates new.
Hover, click, and onward glide—
Fresh builds waiting just outside.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clearly related to the main change: live version checks for lib update tips and related UI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lib-update-tips-live-versions

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an app update checking system, including an AppUpdatePill widget, providers to fetch the latest desktop app release from GitHub, and live SDK version checks from npm and pub.dev. The feedback focuses on improving robustness and error handling: resolving a bug in version comparison when pre-release tags are present, wrapping platform channel reads and URI launching in try-catch blocks to prevent unhandled exceptions, and guarding against concurrent network requests during manual retries.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +11 to +21
int compareSdkVersions(String a, String b) {
final pa = a.split('.');
final pb = b.split('.');
final length = pa.length > pb.length ? pa.length : pb.length;
for (var i = 0; i < length; i++) {
final ai = i < pa.length ? int.tryParse(pa[i]) ?? 0 : 0;
final bi = i < pb.length ? int.tryParse(pb[i]) ?? 0 : 0;
if (ai != bi) return ai - bi;
}
return 0;
} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of compareSdkVersions splits the version string by . directly. If a version string contains a pre-release tag (e.g., 1.2.3-beta.1), pa[2] becomes 3-beta. int.tryParse('3-beta') will fail and return null, defaulting to 0.

This causes incorrect comparisons. For example, compareSdkVersions('1.2.3-beta.1', '1.2.2') will compare 0 against 2 at the patch level, incorrectly concluding that 1.2.3-beta.1 is older than 1.2.2.

To fix this, strip any pre-release suffix (everything after -) before splitting the version components.

Suggested change
int compareSdkVersions(String a, String b) {
final pa = a.split('.');
final pb = b.split('.');
final length = pa.length > pb.length ? pa.length : pb.length;
for (var i = 0; i < length; i++) {
final ai = i < pa.length ? int.tryParse(pa[i]) ?? 0 : 0;
final bi = i < pb.length ? int.tryParse(pb[i]) ?? 0 : 0;
if (ai != bi) return ai - bi;
}
return 0;
}
int compareSdkVersions(String a, String b) {
final cleanA = a.split('-').first;
final cleanB = b.split('-').first;
final pa = cleanA.split('.');
final pb = cleanB.split('.');
final length = pa.length > pb.length ? pa.length : pb.length;
for (var i = 0; i < length; i++) {
final ai = i < pa.length ? int.tryParse(pa[i]) ?? 0 : 0;
final bi = i < pb.length ? int.tryParse(pb[i]) ?? 0 : 0;
if (ai != bi) return ai - bi;
}
return 0;
}

Comment on lines +90 to +95
Future<void> _bootstrap() async {
final cur = await _ref.read(appVersionProvider.future);
if (!mounted) return;
state = AppReleaseState(currentVersion: cur);
await _refreshNow();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In _bootstrap(), await _ref.read(appVersionProvider.future) is called outside of any try-catch block. If appVersionProvider throws an exception (for example, if PackageInfo.fromPlatform() fails on an unsupported platform or during unit tests), the exception will be unhandled because _bootstrap() is called asynchronously in the constructor without error handling.

Wrapping this call in a try-catch block ensures the provider initializes gracefully even if the platform channel fails.

  Future<void> _bootstrap() async {
    try {
      final cur = await _ref.read(appVersionProvider.future);
      if (!mounted) return;
      state = AppReleaseState(currentVersion: cur);
      await _refreshNow();
    } catch (e) {
      if (!mounted) return;
      state = AppReleaseState(
        error: 'Failed to load app version: $e',
        fetchedAt: DateTime.now(),
      );
    }
  }

Comment on lines +97 to +106
Future<void> _refreshNow() async {
// Make sure we have the running version before checking
// `hasUpdate`. Subsequent fetches after the first will already
// have it cached in state.
final cur = state.currentVersion ??
await _ref.read(appVersionProvider.future);
if (!mounted) return;

try {
final resp = await http

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In _refreshNow(), the call to _ref.read(appVersionProvider.future) is executed outside of the try-catch block. If it throws an error, it will result in an unhandled exception. Wrapping it in a try-catch block ensures any platform channel errors are caught and handled gracefully.

Suggested change
Future<void> _refreshNow() async {
// Make sure we have the running version before checking
// `hasUpdate`. Subsequent fetches after the first will already
// have it cached in state.
final cur = state.currentVersion ??
await _ref.read(appVersionProvider.future);
if (!mounted) return;
try {
final resp = await http
Future<void> _refreshNow() async {
String? cur;
try {
cur = state.currentVersion ??
await _ref.read(appVersionProvider.future);
} catch (e) {
if (!mounted) return;
state = AppReleaseState(
error: 'Failed to load app version: $e',
fetchedAt: DateTime.now(),
);
return;
}
if (!mounted) return;
try {
final resp = await http

Comment on lines +111 to +123
Future<void> _openRelease(String? url) async {
final target = url ?? _releasePageFallback;
final uri = Uri.parse(target);
// `externalApplication` asks the OS to open in the default browser
// (Safari on macOS). Falls back to in-app webview if no handler.
final ok = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (!ok && mounted) {
// Last-ditch: try the in-app handler so the click is never
// a dead-end. If that also fails we silently bail — the
// hover panel stays visible with the URL it tried.
await launchUrl(uri, mode: LaunchMode.inAppWebView);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In _openRelease, Uri.parse(target) is used to parse the URL. If the URL returned by the GitHub API is somehow malformed or empty, Uri.parse will throw a FormatException. It is safer to use Uri.tryParse and handle the null case.

Additionally, launchUrl can throw a PlatformException on some platforms if there is no app configured to handle the scheme or if the scheme is not whitelisted in the platform configuration (e.g., LSApplicationQueriesSchemes in macOS/iOS Info.plist). Wrapping the calls in a try-catch block prevents unhandled platform exceptions.

Suggested change
Future<void> _openRelease(String? url) async {
final target = url ?? _releasePageFallback;
final uri = Uri.parse(target);
// `externalApplication` asks the OS to open in the default browser
// (Safari on macOS). Falls back to in-app webview if no handler.
final ok = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (!ok && mounted) {
// Last-ditch: try the in-app handler so the click is never
// a dead-end. If that also fails we silently bail — the
// hover panel stays visible with the URL it tried.
await launchUrl(uri, mode: LaunchMode.inAppWebView);
}
}
Future<void> _openRelease(String? url) async {
final target = url ?? _releasePageFallback;
final uri = Uri.tryParse(target);
if (uri == null) return;
try {
// `externalApplication` asks the OS to open in the default browser
// (Safari on macOS). Falls back to in-app webview if no handler.
final ok = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (!ok && mounted) {
// Last-ditch: try the in-app handler so the click is never
// a dead-end. If that also fails we silently bail — the
// hover panel stays visible with the URL it tried.
await launchUrl(uri, mode: LaunchMode.inAppWebView);
}
} catch (_) {
// Silently fail or handle the error gracefully
}
}

Comment on lines +85 to +117
Timer? _refresh;

SdkVersionsNotifier() : super(SdkLatestVersions.empty) {
// Kick off the first fetch asynchronously so the StateNotifier
// constructor returns immediately (Riverpod expects sync init).
// ignore: discarded_futures
_refreshNow();
_refresh = Timer.periodic(_ttl, (_) => _refreshNow());
}

/// Manually re-fetch. UI surfaces this as a "Retry" affordance when
/// a previous fetch errored out (offline / timeout / 5xx).
Future<void> refresh() => _refreshNow();

Future<void> _refreshNow() async {
// Run both fetches in parallel. Each is wrapped in its own
// try/catch so a failure on one platform doesn't poison the other.
final results = await Future.wait([_fetchNpm(), _fetchPub()]);
// Guard against "use after dispose": the widget tree holding us
// could tear down (user closes the panel mid-fetch). Without this
// check, the `state =` below would throw "Bad state: Cannot use a
// StateNotifier after its dispose()".
if (!mounted) return;
final npm = results[0]; // _fetchNpm() -> reactNative
final pub = results[1]; // _fetchPub() -> flutter
state = SdkLatestVersions(
flutter: pub.value,
reactNative: npm.value,
fetchedAt: DateTime.now(),
flutterError: pub.error,
reactNativeError: npm.error,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is currently no guard against concurrent or overlapping fetches in _refreshNow(). If a user triggers refresh() manually (e.g., by clicking "Retry" multiple times) while a fetch is already in progress, multiple concurrent HTTP requests will be fired. This can lead to redundant network traffic and potential race conditions where an older request completes after a newer one, overwriting the state with stale data.

Adding a simple boolean flag (e.g., _isLoading) to guard against concurrent execution of _refreshNow() would make the provider more efficient and robust.

  Timer? _refresh;
  bool _isLoading = false;

  SdkVersionsNotifier() : super(SdkLatestVersions.empty) {
    // Kick off the first fetch asynchronously so the StateNotifier
    // constructor returns immediately (Riverpod expects sync init).
    // ignore: discarded_futures
    _refreshNow();
    _refresh = Timer.periodic(_ttl, (_) => _refreshNow());
  }

  /// Manually re-fetch. UI surfaces this as a "Retry" affordance when
  /// a previous fetch errored out (offline / timeout / 5xx).
  Future<void> refresh() => _refreshNow();

  Future<void> _refreshNow() async {
    if (_isLoading) return;
    _isLoading = true;
    try {
      // Run both fetches in parallel. Each is wrapped in its own
      // try/catch so a failure on one platform doesn't poison the other.
      final results = await Future.wait([_fetchNpm(), _fetchPub()]);
      // Guard against "use after dispose": the widget tree holding us
      // could tear down (user closes the panel mid-fetch). Without this
      // check, the `state =` below would throw "Bad state: Cannot use a
      // StateNotifier after its dispose()".
      if (!mounted) return;
      final npm = results[0]; // _fetchNpm() -> reactNative
      final pub = results[1]; // _fetchPub() -> flutter
      state = SdkLatestVersions(
        flutter: pub.value,
        reactNative: npm.value,
        fetchedAt: DateTime.now(),
        flutterError: pub.error,
        reactNativeError: npm.error,
      );
    } finally {
      _isLoading = false;
    }
  }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
lib/core/providers/sdk_versions_provider.dart (1)

119-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate _fetchNpm and _fetchPub to eliminate duplication.

Both methods are identical except for the URL, JSON extraction path, and missing-field error message. Extracting a shared _fetch helper removes ~35 lines of copy-paste and makes adding a third registry a one-liner.

♻️ Proposed refactor
-  Future<_Result> _fetchNpm() async {
-    try {
-      final resp =
-          await http.get(Uri.parse(_npmUrl)).timeout(_timeout);
-      if (resp.statusCode != 200) {
-        return _Result.error('HTTP ${resp.statusCode}');
-      }
-      final json = jsonDecode(resp.body);
-      final version = json is Map<String, dynamic> ? json['version'] : null;
-      if (version is String && version.isNotEmpty) return _Result.ok(version);
-      return _Result.error('missing "version" field');
-    } on TimeoutException {
-      return _Result.error('timeout');
-    } catch (e) {
-      return _Result.error(e.toString());
-    }
-  }
-
-  Future<_Result> _fetchPub() async {
-    try {
-      final resp =
-          await http.get(Uri.parse(_pubUrl)).timeout(_timeout);
-      if (resp.statusCode != 200) {
-        return _Result.error('HTTP ${resp.statusCode}');
-      }
-      final json = jsonDecode(resp.body);
-      final latest = json is Map<String, dynamic> ? json['latest'] : null;
-      final version = latest is Map<String, dynamic> ? latest['version'] : null;
-      if (version is String && version.isNotEmpty) return _Result.ok(version);
-      return _Result.error('missing "latest.version" field');
-    } on TimeoutException {
-      return _Result.error('timeout');
-    } catch (e) {
-      return _Result.error(e.toString());
-    }
-  }
+  Future<_Result> _fetchNpm() => _fetch(
+        _npmUrl,
+        (json) => json['version'],
+        'missing "version" field',
+      );
+
+  Future<_Result> _fetchPub() => _fetch(
+        _pubUrl,
+        (json) => (json['latest'] as Map<String, dynamic>?)?['version'],
+        'missing "latest.version" field',
+      );
+
+  Future<_Result> _fetch(
+    String url,
+    String? Function(Map<String, dynamic> json) extractVersion,
+    String missingError,
+  ) async {
+    try {
+      final resp = await http.get(Uri.parse(url)).timeout(_timeout);
+      if (resp.statusCode != 200) return _Result.error('HTTP ${resp.statusCode}');
+      final json = jsonDecode(resp.body);
+      final version =
+          json is Map<String, dynamic> ? extractVersion(json) : null;
+      if (version is String && version.isNotEmpty) return _Result.ok(version);
+      return _Result.error(missingError);
+    } on TimeoutException {
+      return _Result.error('timeout');
+    } catch (e) {
+      return _Result.error(e.toString());
+    }
+  }
🤖 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/core/providers/sdk_versions_provider.dart` around lines 119 - 154,
Consolidate the duplicated HTTP, timeout, status, JSON decoding, and exception
handling in _fetchNpm and _fetchPub into a shared _fetch helper. Parameterize
the helper with the registry URL, version extraction callback, and missing-field
error message, then have each method delegate to it while preserving their
existing extraction paths and result messages.
lib/components/feedback/lib_update_tips.dart (1)

330-333: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

AnimationController runs continuously even when not loading.

_loadingSpin..repeat() starts in initState and runs perpetually, even when the row status is loaded or error. The RotationTransition is only in the widget tree during loading, so the controller wastes cycles for nothing. Start/stop it based on status changes.

♻️ Proposed refactor
   `@override`
+  void didUpdateWidget(covariant _SdkRow oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.status == SdkVersionFetch.loading) {
+      if (!_loadingSpin.isAnimating) _loadingSpin.repeat();
+    } else {
+      if (_loadingSpin.isAnimating) _loadingSpin.stop();
+    }
+  }
+
+  `@override`
   Widget build(BuildContext context) {

And remove ..repeat() from initState:

     _loadingSpin = AnimationController(
       vsync: this,
       duration: const Duration(milliseconds: 1400),
-    )..repeat();
+    );
+    if (widget.status == SdkVersionFetch.loading) _loadingSpin.repeat();
🤖 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/components/feedback/lib_update_tips.dart` around lines 330 - 333, Update
the _loadingSpin AnimationController lifecycle so initState only creates the
controller without starting repetition. Start repeating it when the row status
becomes loading, and stop it when status changes to loaded or error, using the
existing status-update logic and disposing the controller as before.
🤖 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 `@lib/components/feedback/app_update_pill.dart`:
- Line 39: Replace the hardcoded color constants in _AppUpdatePillState and the
accent, background, and success-state usages with the appropriate ColorTokens
entries. Update the four other widget classes that reference
_AppUpdatePillState._accent to use the shared token directly, removing the
cross-class coupling while preserving the existing visual colors.
- Line 236: Localize all hardcoded update-pill UI text in the app update pill
component, including UPDATE, Update available, You are up to date, Live check
unavailable, Installed, Latest, View release, and Retry. Add corresponding
entries to the project’s existing localization resources and replace each
literal in the update pill implementation with the generated/localized string
accessors, preserving the current labels and behavior.

In `@lib/components/feedback/lib_update_tips.dart`:
- Line 46: Replace the hardcoded colors in the feedback update-tips component,
including the accent color and the colors at the referenced success/status
usages, with the corresponding ColorTokens values. Update all four hex color
references while preserving their existing visual roles and behavior.
- Around line 353-356: Update the isUpToDate calculation in the version status
logic to use compareSdkVersions(e.version, latest) == 0 instead of string
equality, while preserving the existing latest != null guard and hasUpdate
behavior.

In `@lib/core/providers/app_update_provider.dart`:
- Around line 116-167: Preserve the previously fetched release when constructing
error states in the update-fetch flow. Update each error branch after the HTTP
response, payload validation, missing-field validation, TimeoutException
handler, and catch-all handler to carry forward the existing
AppReleaseState.release while retaining the current error, version, and
timestamp values.

In `@lib/core/providers/sdk_versions_provider.dart`:
- Around line 97-117: Update refresh() to clear the existing Flutter and React
Native error state before invoking _refreshNow(), while preserving any currently
cached version values so fetchStateFor reports loading during the retry. Keep
_refreshNow() responsible for applying the fetched results and errors afterward.

In `@lib/l10n/app_fr.arb`:
- Line 183: Update the sdkTipsLatestLabel translation from “Récent” to a French
equivalent of “Latest,” such as “Dernière” or “Dernière version,” while
preserving the existing localization key.

In `@lib/l10n/app_localizations_fr.dart`:
- Around line 666-679: Update the sdkTipsLatestLabel getter in the French
localizations class to return “Dernière version” (or the approved equivalent)
instead of “Récent”, while leaving the neighboring SDK tip translations
unchanged.

---

Nitpick comments:
In `@lib/components/feedback/lib_update_tips.dart`:
- Around line 330-333: Update the _loadingSpin AnimationController lifecycle so
initState only creates the controller without starting repetition. Start
repeating it when the row status becomes loading, and stop it when status
changes to loaded or error, using the existing status-update logic and disposing
the controller as before.

In `@lib/core/providers/sdk_versions_provider.dart`:
- Around line 119-154: Consolidate the duplicated HTTP, timeout, status, JSON
decoding, and exception handling in _fetchNpm and _fetchPub into a shared _fetch
helper. Parameterize the helper with the registry URL, version extraction
callback, and missing-field error message, then have each method delegate to it
while preserving their existing extraction paths and result messages.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 301c07e0-9c17-4034-b1dd-9316de7ed3a0

📥 Commits

Reviewing files that changed from the base of the PR and between 8cb710b and 5a57ee8.

⛔ Files ignored due to path filters (4)
  • macos/Flutter/GeneratedPluginRegistrant.swift is excluded by none and included by none
  • pubspec.yaml is excluded by none and included by none
  • windows/flutter/generated_plugin_registrant.cc is excluded by none and included by none
  • windows/flutter/generated_plugins.cmake is excluded by none and included by none
📒 Files selected for processing (20)
  • lib/components/feedback/app_update_pill.dart
  • lib/components/feedback/lib_update_tips.dart
  • lib/core/providers/app_update_provider.dart
  • lib/core/providers/app_version_provider.dart
  • lib/core/providers/sdk_versions_provider.dart
  • lib/core/routes/app_shell.dart
  • lib/core/utils/sdk_version.dart
  • lib/l10n/app_en.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_ja.arb
  • lib/l10n/app_localizations.dart
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_fr.dart
  • lib/l10n/app_localizations_ja.dart
  • lib/l10n/app_localizations_vi.dart
  • lib/l10n/app_localizations_zh.dart
  • lib/l10n/app_vi.arb
  • lib/l10n/app_zh.arb
  • lib/l10n/app_zh_CN.arb
  • lib/l10n/app_zh_TW.arb

class _AppUpdatePillState extends ConsumerState<AppUpdatePill> {
bool _hovered = false;

static const _accent = Color(0xFFFBBF24); // amber — advisory

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Replace hardcoded hex colors with ColorTokens.

The file hardcodes five distinct hex values (0xFFFBBF24, 0xFF1F242B, 0xFF4ADE80, 0xFF16A34A) across the accent, background, and success-state colors. _accent is also accessed from four other widget classes via _AppUpdatePillState._accent, creating unnecessary coupling. Centralizing these in ColorTokens would resolve both the violation and the coupling.

As per coding guidelines, "Use ColorTokens for colors, never hardcode hex values" for lib/components/** files.

Also applies to: 66-70, 202-204

🤖 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/components/feedback/app_update_pill.dart` at line 39, Replace the
hardcoded color constants in _AppUpdatePillState and the accent, background, and
success-state usages with the appropriate ColorTokens entries. Update the four
other widget classes that reference _AppUpdatePillState._accent to use the
shared token directly, removing the cross-class coupling while preserving the
existing visual colors.

Source: Path instructions

borderRadius: BorderRadius.circular(3),
),
child: const Text(
'UPDATE',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for existing localization keys related to app update pill strings
rg -n 'updateAvailable|upToDate|liveCheckUnavailable|viewRelease|retry|update.*available|installed|latest' lib/l10n/ --type-add 'arb:*.arb' -t arb

Repository: ridelinktechs/devconnect-manage-kit

Length of output: 308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app_update_pill.dart outline =="
ast-grep outline lib/components/feedback/app_update_pill.dart --view expanded || true

echo
echo "== relevant lines in app_update_pill.dart =="
sed -n '220,350p' lib/components/feedback/app_update_pill.dart

echo
echo "== localization files containing likely update-pill strings =="
rg -n --glob 'lib/l10n/*.arb' '"(updateAvailable|upToDate|liveCheckUnavailable|viewRelease|retry|installed|latest|UPDATE)"' lib/l10n || true

echo
echo "== all localization keys near version-check terms =="
rg -n --glob 'lib/l10n/*.arb' '"[^"]*version[^"]*"|"[^"]*update[^"]*"|"[^"]*release[^"]*"|"[^"]*retry[^"]*"' lib/l10n || true

Repository: ridelinktechs/devconnect-manage-kit

Length of output: 5028


Localize the update pill strings. lib/components/feedback/app_update_pill.dart:236,280-291,299,315,326-337 still hardcodes UI text (UPDATE, Update available, You are up to date, Live check unavailable, Installed, Latest, View release, Retry). Add localized strings for these labels so non-English locales don’t fall back to English.

🤖 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/components/feedback/app_update_pill.dart` at line 236, Localize all
hardcoded update-pill UI text in the app update pill component, including
UPDATE, Update available, You are up to date, Live check unavailable, Installed,
Latest, View release, and Retry. Add corresponding entries to the project’s
existing localization resources and replace each literal in the update pill
implementation with the generated/localized string accessors, preserving the
current labels and behavior.

@@ -35,6 +44,10 @@ class _LibUpdateTipsState extends State<LibUpdateTips> {
final isDark = theme.brightness == Brightness.dark;
final loc = S.of(context);
final accent = const Color(0xFFFBBF24); // amber — advisory, never error

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use ColorTokens instead of hardcoded hex values.

Lines 46 (0xFFFBBF24), 67 (0xFF1F242B), and 571–572 (0xFF4ADE80, 0xFF16A34A) hardcode hex colors. As per path instructions, files under lib/components/** must use ColorTokens for colors and never hardcode hex values.

Also applies to: 67-67, 571-572

🤖 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/components/feedback/lib_update_tips.dart` at line 46, Replace the
hardcoded colors in the feedback update-tips component, including the accent
color and the colors at the referenced success/status usages, with the
corresponding ColorTokens values. Update all four hex color references while
preserving their existing visual roles and behavior.

Source: Path instructions

Comment thread lib/components/feedback/lib_update_tips.dart Outdated
Comment thread lib/core/providers/app_update_provider.dart
Comment thread lib/core/providers/sdk_versions_provider.dart
Comment thread lib/l10n/app_fr.arb
"sdkTipsReactNative": "React Native",
"sdkTipsAndroid": "Android",
"sdkTipsVersionLabel": "v{version}",
"sdkTipsLatestLabel": "Récent",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

"Récent" ne traduit pas fidèlement "Latest".

"Récent" signifie "Recent", pas "Latest". Dans le contexte de versions logicielles, "Dernière version" ou simplement "Dernière" serait plus précis pour sdkTipsLatestLabel.

🔧 Proposed fix
-  "sdkTipsLatestLabel": "Récent",
+  "sdkTipsLatestLabel": "Dernière",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"sdkTipsLatestLabel": "Récent",
"sdkTipsLatestLabel": "Dernière",
🤖 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/l10n/app_fr.arb` at line 183, Update the sdkTipsLatestLabel translation
from “Récent” to a French equivalent of “Latest,” such as “Dernière” or
“Dernière version,” while preserving the existing localization key.

Comment on lines +666 to +679
@override
String get sdkTipsLatestLabel => 'Récent';

@override
String get sdkTipsUpdate => 'Mettre à jour';

@override
String get sdkTipsChecking => 'Vérification';

@override
String get sdkTipsOffline => 'Vérification indisponible';

@override
String get sdkTipsRetry => 'Réessayer';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a French translation for “latest version.”

Line 667 uses Récent, which means “recent” and reads awkwardly before the rendered version (Récent v…). Use Dernière version (or another product-approved equivalent) so the label matches the English “Latest” state.

Suggested fix
-  String get sdkTipsLatestLabel => 'Récent';
+  String get sdkTipsLatestLabel => 'Dernière version';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@override
String get sdkTipsLatestLabel => 'Récent';
@override
String get sdkTipsUpdate => 'Mettre à jour';
@override
String get sdkTipsChecking => 'Vérification';
@override
String get sdkTipsOffline => 'Vérification indisponible';
@override
String get sdkTipsRetry => 'Réessayer';
`@override`
String get sdkTipsLatestLabel => 'Dernière version';
`@override`
String get sdkTipsUpdate => 'Mettre à jour';
`@override`
String get sdkTipsChecking => 'Vérification';
`@override`
String get sdkTipsOffline => 'Vérification indisponible';
`@override`
String get sdkTipsRetry => 'Réessayer';
🤖 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/l10n/app_localizations_fr.dart` around lines 666 - 679, Update the
sdkTipsLatestLabel getter in the French localizations class to return “Dernière
version” (or the approved equivalent) instead of “Récent”, while leaving the
neighboring SDK tip translations unchanged.

…ndling, and state management in update providers
@buivietphi
buivietphi merged commit f232683 into main Jul 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant