Feat/lib update tips live versions - #17
Conversation
…ngs for version check UI
…y users of available updates
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds 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. ChangesUpdate and SDK status
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
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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; | |
| } |
| Future<void> _bootstrap() async { | ||
| final cur = await _ref.read(appVersionProvider.future); | ||
| if (!mounted) return; | ||
| state = AppReleaseState(currentVersion: cur); | ||
| await _refreshNow(); | ||
| } |
There was a problem hiding this comment.
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(),
);
}
}| 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 |
There was a problem hiding this comment.
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.
| 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 |
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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 | |
| } | |
| } |
| 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, | ||
| ); | ||
| } |
There was a problem hiding this comment.
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;
}
}There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
lib/core/providers/sdk_versions_provider.dart (1)
119-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsolidate
_fetchNpmand_fetchPubto eliminate duplication.Both methods are identical except for the URL, JSON extraction path, and missing-field error message. Extracting a shared
_fetchhelper 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 valueAnimationController runs continuously even when not loading.
_loadingSpin..repeat()starts ininitStateand runs perpetually, even when the row status isloadedorerror. TheRotationTransitionis only in the widget tree duringloading, so the controller wastes cycles for nothing. Start/stop it based onstatuschanges.♻️ 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()frominitState:_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
⛔ Files ignored due to path filters (4)
macos/Flutter/GeneratedPluginRegistrant.swiftis excluded by none and included by nonepubspec.yamlis excluded by none and included by nonewindows/flutter/generated_plugin_registrant.ccis excluded by none and included by nonewindows/flutter/generated_plugins.cmakeis excluded by none and included by none
📒 Files selected for processing (20)
lib/components/feedback/app_update_pill.dartlib/components/feedback/lib_update_tips.dartlib/core/providers/app_update_provider.dartlib/core/providers/app_version_provider.dartlib/core/providers/sdk_versions_provider.dartlib/core/routes/app_shell.dartlib/core/utils/sdk_version.dartlib/l10n/app_en.arblib/l10n/app_fr.arblib/l10n/app_ja.arblib/l10n/app_localizations.dartlib/l10n/app_localizations_en.dartlib/l10n/app_localizations_fr.dartlib/l10n/app_localizations_ja.dartlib/l10n/app_localizations_vi.dartlib/l10n/app_localizations_zh.dartlib/l10n/app_vi.arblib/l10n/app_zh.arblib/l10n/app_zh_CN.arblib/l10n/app_zh_TW.arb
| class _AppUpdatePillState extends ConsumerState<AppUpdatePill> { | ||
| bool _hovered = false; | ||
|
|
||
| static const _accent = Color(0xFFFBBF24); // amber — advisory |
There was a problem hiding this comment.
📐 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', |
There was a problem hiding this comment.
📐 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 arbRepository: 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 || trueRepository: 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 | |||
There was a problem hiding this comment.
📐 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
| "sdkTipsReactNative": "React Native", | ||
| "sdkTipsAndroid": "Android", | ||
| "sdkTipsVersionLabel": "v{version}", | ||
| "sdkTipsLatestLabel": "Récent", |
There was a problem hiding this comment.
🎯 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.
| "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.
| @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'; |
There was a problem hiding this comment.
🎯 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.
| @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
Description
Related Issue
Type of Change
Testing
Screenshots (if applicable)
Summary by CodeRabbit