feat: implement auto-update system and refactor sticker page structure#29
Conversation
- Add support for Linux arm64 builds using ARM-native GitHub runners. - Implement universal Android APK generation alongside split-ABI artifacts. - Enhance build script reliability by enforcing strict error handling and dynamic path resolution for Linux bundles. - Refactor artifact upload logic to include architecture suffixes for better asset organization across all platforms. - Configure arm64 Linux builds as non-blocking to ensure workflow stability during initial rollout.
…in permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…ct staging" This reverts commit b018c82.
This commit introduces a mechanism to check for application updates by fetching the latest releases from the GitHub API. - Added `UpdateChecker` service to handle API requests, version comparison, and tracking ignored versions. - Integrated a manual update check button in the Settings page. - Added localization support for update statuses in English, Japanese, Simplified Chinese, and Traditional Chinese.
Update the CI workflow configuration to trigger on pushes and pull requests targeting the 'dev' branch. This ensures that code quality checks are performed on development branches before merging into main.
Split the monolithic StickerPage class into smaller, manageable part files for config, logic, layers, picker, and sections. Also commented out the font management icon in settings.
📝 WalkthroughWalkthroughThis pull request implements an automatic update-checking feature integrated with GitHub Releases API, adds localization strings across four languages for the update flow, and refactors the sticker editor page from a monolithic file into modular part files for maintainability. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant App as App (Startup)
participant UpdateChecker as UpdateChecker
participant GitHub as GitHub API
participant Prefs as SharedPreferences
participant Dialog as Update Dialog
App->>UpdateChecker: checkForUpdate(manual: false)
UpdateChecker->>GitHub: GET /repos/.../releases
GitHub-->>UpdateChecker: Release metadata
UpdateChecker->>UpdateChecker: Filter drafts, prereleases, ignored
UpdateChecker->>Prefs: Fetch ignored versions
Prefs-->>UpdateChecker: Ignored version list
alt Update Available
UpdateChecker-->>App: UpdateInfo
App->>Dialog: showUpdateDialog()
Dialog->>User: Display release date, notes, actions
User->>Dialog: Select action
alt Ignore This Version
Dialog->>Prefs: ignoreVersion()
Prefs-->>Dialog: Stored
else Update Later
Dialog->>App: Close
else Update Now
Dialog->>GitHub: Launch htmlUrl
GitHub->>User: Opens browser
end
else No Update
UpdateChecker-->>App: null
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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: 4
🧹 Nitpick comments (6)
lib/pages/settings.dart (1)
154-154: Remove commented-out widget code before merge.Keeping dead commented lines in UI trees adds noise and makes future diffs harder to read.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/pages/settings.dart` at line 154, Remove the dead commented widget line "leading: const Icon(Icons.font_download_outlined)," from the UI tree in the settings.dart widget (e.g., where a ListTile or similar uses the leading property) so the source contains no commented-out widget code; locate the occurrence of the commented "leading" line and delete it before merging to keep the diff clean.lib/update/update_checker.dart (1)
13-32: Consider documenting http.Client lifecycle expectations.The
UpdateCheckeraccepts an optionalhttp.Clientbut doesn't close it. If the defaulthttp.Client()is used, it may hold resources. Consider either:
- Documenting that callers are responsible for closing injected clients
- Adding a
dispose()method that closes the client if it was created internallyThis is a minor concern since the checker is likely long-lived, but worth noting for resource management.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/update/update_checker.dart` around lines 13 - 32, The UpdateChecker currently accepts an optional http.Client but never closes it; update the constructor for UpdateChecker to track ownership (e.g., set a private bool like _ownsClient = client == null when you create _client = client ?? http.Client()) and add a public dispose() method that calls _client.close() only if _ownsClient is true; additionally add a short doc comment on the constructor noting that injected clients are not closed by UpdateChecker so callers must close them if they pass one in.lib/pages/sticker/sticker_page_logic.dart (2)
74-77: Pseudo-random selection has potential bias.Using
DateTime.now().millisecond % members.lengthfor random selection creates bias for small member lists (4-6 characters). Consider usingRandom().nextInt(members.length)for better distribution if uniform randomness is desired.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/pages/sticker/sticker_page_logic.dart` around lines 74 - 77, The selection from grouped members using DateTime.now().millisecond % members.length can be biased; in the block that checks PjskGenerator.groups.contains(char) and uses PjskGenerator.groupMembers[char] to pick a replacement, replace the time-based modulus with a proper RNG: create a Random (import dart:math) and use Random().nextInt(members.length) to index members so selection is uniformly distributed; update the code that assigns char from members accordingly and ensure dart:math is imported where sticker_page_logic.dart defines this logic.
22-27: Consider awaitingprefs.remove()for consistency.
prefs.remove('layers')returns aFuture<bool>but is not awaited. While the impact is minor (cleanup of corrupted data), awaiting ensures the preference is removed before the method completes.♻️ Proposed fix
} catch (e) { if (kDebugMode) print("Error loading layers: $e"); _layers = [TextLayer(content: "わんだほーい")]; // 清理损坏的数据防止循环报错 - prefs.remove('layers'); + await prefs.remove('layers'); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/pages/sticker/sticker_page_logic.dart` around lines 22 - 27, The catch block currently calls prefs.remove('layers') without awaiting the Future; change it to await prefs.remove('layers') so the preference is actually cleared before the method completes, and ensure the enclosing function is marked async (or already async) to allow awaiting; refer to the catch in the code handling _layers (where TextLayer is assigned) and the prefs variable to locate and update the call.lib/pages/sticker/sticker_page_picker.dart (1)
43-66: Post-frame callbacks registered on every rebuild.
addPostFrameCallbackis called during_buildPickerContent, which runs on everysetModalState. This queues redundant scroll animations. Consider moving scroll-to-selection logic to a one-time initialization or guarding with a flag.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/pages/sticker/sticker_page_picker.dart` around lines 43 - 66, The post-frame scroll logic inside _buildPickerContent registers a WidgetsBinding.instance.addPostFrameCallback on every rebuild, causing redundant queued scrolls; move or guard this so it runs only once per relevant change: either relocate the scroll-to-selection block from _buildPickerContent into initState (or a listener that runs once when the modal opens) or add a boolean guard field (e.g. _didInitialScroll) and check it before calling WidgetsBinding.instance.addPostFrameCallback, then set it true after the callback runs; reference the existing keys _characterKeys, _stickerKeys, the character/_selectedSticker checks and ensure the guard resets only when selection/context legitimately changes.lib/pages/sticker.dart (1)
114-118: Consider removing or documenting commented-out code.The commented-out export button could be removed to keep the codebase clean, or add a TODO comment explaining why it's disabled and when it might be re-enabled.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/pages/sticker.dart` around lines 114 - 118, Remove the inactive export IconButton block or add a short TODO comment explaining why it's commented out and the condition for re-enabling it; specifically either delete the commented IconButton snippet referencing IconButton/Icons.share/_exportImportConfig/tooltip "分享配置" to clean up the code, or replace the commented block with a one-line TODO above it that states why it's disabled and what needs to happen to re-enable it (e.g., pending API, UX decision, or bug/issue ID).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/pages/sticker/sticker_page_config.dart`:
- Around line 26-34: When decoding layersJson, guard against an empty decoded
list before accessing _layers.first: after jsonDecode(layersJson) and mapping to
_layers, check if the resulting list isNotEmpty (similar to the guard in
_loadPreferences) and only then set _currentLayerId = _layers.first.id and
_contextController.text = _currentLayer.content; otherwise handle the empty case
by leaving _layers empty and _currentLayerId/_contextController unchanged or set
to sensible defaults (e.g., null or empty string) so no StateError is thrown.
- Around line 113-126: The code uses Platform.isAndroid inside the actions block
(where SharePlus.instance.share and ShareParams are used) which will throw on
web; update the condition to check web first (e.g. !kIsWeb &&
Platform.isAndroid) and add the necessary import for kIsWeb
(flutter/foundation.dart) so the share button is only built on Android; ensure
the conditional remains around the TextButton that calls
SharePlus.instance.share with ShareParams.
In `@lib/pages/sticker/sticker_page_logic.dart`:
- Around line 87-121: _handleImageTap uses Platform.isAndroid/Windows directly
which throws on web and currently only prints errors in debug mode; add a kIsWeb
guard at the start of _handleImageTap to handle web separately (e.g.,
early-return or web-specific share/save flow), ensure all Platform checks remain
behind kIsWeb false, and improve the catch block to show user-facing feedback
via ScaffoldMessenger.of(context).showSnackBar using mounted before showing;
reference symbols: _handleImageTap, _byteData, Platform, kIsWeb, mounted,
ScaffoldMessenger, SnackBar.
In `@lib/update/update_checker.dart`:
- Around line 41-52: The HTTP GET call using _client.get against _releasesUri
can throw on network failures; wrap the call in a try-catch inside the method
(the block around _client.get in update_checker.dart) to catch
http.ClientException and other Exceptions, log or convert them to a
domain-specific exception (or return null) and rethrow or propagate a safe error
to the caller; ensure you preserve the existing 200-status check and only throw
the current 'Failed to fetch releases' exception for non-200 responses after a
successful request, and include the caught error details in the new
thrown/logged message so callers can surface user-facing feedback.
---
Nitpick comments:
In `@lib/pages/settings.dart`:
- Line 154: Remove the dead commented widget line "leading: const
Icon(Icons.font_download_outlined)," from the UI tree in the settings.dart
widget (e.g., where a ListTile or similar uses the leading property) so the
source contains no commented-out widget code; locate the occurrence of the
commented "leading" line and delete it before merging to keep the diff clean.
In `@lib/pages/sticker.dart`:
- Around line 114-118: Remove the inactive export IconButton block or add a
short TODO comment explaining why it's commented out and the condition for
re-enabling it; specifically either delete the commented IconButton snippet
referencing IconButton/Icons.share/_exportImportConfig/tooltip "分享配置" to clean
up the code, or replace the commented block with a one-line TODO above it that
states why it's disabled and what needs to happen to re-enable it (e.g., pending
API, UX decision, or bug/issue ID).
In `@lib/pages/sticker/sticker_page_logic.dart`:
- Around line 74-77: The selection from grouped members using
DateTime.now().millisecond % members.length can be biased; in the block that
checks PjskGenerator.groups.contains(char) and uses
PjskGenerator.groupMembers[char] to pick a replacement, replace the time-based
modulus with a proper RNG: create a Random (import dart:math) and use
Random().nextInt(members.length) to index members so selection is uniformly
distributed; update the code that assigns char from members accordingly and
ensure dart:math is imported where sticker_page_logic.dart defines this logic.
- Around line 22-27: The catch block currently calls prefs.remove('layers')
without awaiting the Future; change it to await prefs.remove('layers') so the
preference is actually cleared before the method completes, and ensure the
enclosing function is marked async (or already async) to allow awaiting; refer
to the catch in the code handling _layers (where TextLayer is assigned) and the
prefs variable to locate and update the call.
In `@lib/pages/sticker/sticker_page_picker.dart`:
- Around line 43-66: The post-frame scroll logic inside _buildPickerContent
registers a WidgetsBinding.instance.addPostFrameCallback on every rebuild,
causing redundant queued scrolls; move or guard this so it runs only once per
relevant change: either relocate the scroll-to-selection block from
_buildPickerContent into initState (or a listener that runs once when the modal
opens) or add a boolean guard field (e.g. _didInitialScroll) and check it before
calling WidgetsBinding.instance.addPostFrameCallback, then set it true after the
callback runs; reference the existing keys _characterKeys, _stickerKeys, the
character/_selectedSticker checks and ensure the guard resets only when
selection/context legitimately changes.
In `@lib/update/update_checker.dart`:
- Around line 13-32: The UpdateChecker currently accepts an optional http.Client
but never closes it; update the constructor for UpdateChecker to track ownership
(e.g., set a private bool like _ownsClient = client == null when you create
_client = client ?? http.Client()) and add a public dispose() method that calls
_client.close() only if _ownsClient is true; additionally add a short doc
comment on the constructor noting that injected clients are not closed by
UpdateChecker so callers must close them if they pass one in.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 08537818-96ba-4904-938d-ab2470a57efc
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (17)
.github/workflows/push.ymllib/l10n/app_en.arblib/l10n/app_ja.arblib/l10n/app_zh.arblib/l10n/app_zh_Hant.arblib/pages/app.dartlib/pages/settings.dartlib/pages/sticker.dartlib/pages/sticker/sticker_page_config.dartlib/pages/sticker/sticker_page_layers.dartlib/pages/sticker/sticker_page_logic.dartlib/pages/sticker/sticker_page_picker.dartlib/pages/sticker/sticker_page_sections.dartlib/update/update_checker.dartlib/update/update_info.dartlib/update/update_prompt.dartpubspec.yaml
| if (layersJson != null) { | ||
| try { | ||
| final List<dynamic> list = jsonDecode(layersJson); | ||
| _layers = list.map((item) => TextLayer.fromJson(item)).toList(); | ||
| _currentLayerId = _layers.first.id; | ||
| _contextController.text = _currentLayer.content; | ||
| } catch (e) { | ||
| if (kDebugMode) print("Error reloading layers from URI: $e"); | ||
| } |
There was a problem hiding this comment.
Empty layers list causes exception when accessing .first.
If layers_json decodes to an empty list, _layers.first on line 30 will throw StateError. Add a guard similar to _loadPreferences.
🛠️ Proposed fix
if (layersJson != null) {
try {
final List<dynamic> list = jsonDecode(layersJson);
_layers = list.map((item) => TextLayer.fromJson(item)).toList();
+ if (_layers.isEmpty) {
+ _layers = [TextLayer(content: "わんだほーい")];
+ }
_currentLayerId = _layers.first.id;
_contextController.text = _currentLayer.content;
} catch (e) {
if (kDebugMode) print("Error reloading layers from URI: $e");
}
}📝 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.
| if (layersJson != null) { | |
| try { | |
| final List<dynamic> list = jsonDecode(layersJson); | |
| _layers = list.map((item) => TextLayer.fromJson(item)).toList(); | |
| _currentLayerId = _layers.first.id; | |
| _contextController.text = _currentLayer.content; | |
| } catch (e) { | |
| if (kDebugMode) print("Error reloading layers from URI: $e"); | |
| } | |
| if (layersJson != null) { | |
| try { | |
| final List<dynamic> list = jsonDecode(layersJson); | |
| _layers = list.map((item) => TextLayer.fromJson(item)).toList(); | |
| if (_layers.isEmpty) { | |
| _layers = [TextLayer(content: "わんだほーい")]; | |
| } | |
| _currentLayerId = _layers.first.id; | |
| _contextController.text = _currentLayer.content; | |
| } catch (e) { | |
| if (kDebugMode) print("Error reloading layers from URI: $e"); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/pages/sticker/sticker_page_config.dart` around lines 26 - 34, When
decoding layersJson, guard against an empty decoded list before accessing
_layers.first: after jsonDecode(layersJson) and mapping to _layers, check if the
resulting list isNotEmpty (similar to the guard in _loadPreferences) and only
then set _currentLayerId = _layers.first.id and _contextController.text =
_currentLayer.content; otherwise handle the empty case by leaving _layers empty
and _currentLayerId/_contextController unchanged or set to sensible defaults
(e.g., null or empty string) so no StateError is thrown.
| actions: [ | ||
| if (Platform.isAndroid) | ||
| TextButton( | ||
| onPressed: | ||
| () async => await SharePlus.instance.share( | ||
| ShareParams( | ||
| text: S | ||
| .of(context) | ||
| .shareEmojiText(configController.text), | ||
| title: S.of(context).shareConfigTitle, | ||
| ), | ||
| ), | ||
| child: Text(S.of(context).share), | ||
| ), |
There was a problem hiding this comment.
Missing web platform guard for Platform.isAndroid check.
Accessing Platform.isAndroid on web throws UnsupportedError. Guard with kIsWeb or use !kIsWeb && Platform.isAndroid.
🛠️ Proposed fix
actions: [
- if (Platform.isAndroid)
+ if (!kIsWeb && Platform.isAndroid)
TextButton(
onPressed:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/pages/sticker/sticker_page_config.dart` around lines 113 - 126, The code
uses Platform.isAndroid inside the actions block (where SharePlus.instance.share
and ShareParams are used) which will throw on web; update the condition to check
web first (e.g. !kIsWeb && Platform.isAndroid) and add the necessary import for
kIsWeb (flutter/foundation.dart) so the share button is only built on Android;
ensure the conditional remains around the TextButton that calls
SharePlus.instance.share with ShareParams.
| Future<void> _handleImageTap() async { | ||
| if (_byteData == null) return; | ||
| try { | ||
| final path = | ||
| Platform.isAndroid | ||
| ? '/storage/emulated/0/Pictures/pjsk_sticker/pjsk_${DateTime.now().millisecondsSinceEpoch}.png' | ||
| : 'pjsk_sticker/pjsk_${DateTime.now().millisecondsSinceEpoch}.png'; | ||
| final file = File(path); | ||
| await file.create(recursive: true); | ||
| await file.writeAsBytes(_byteData!); | ||
| if (Platform.isAndroid) { | ||
| await SharePlus.instance.share( | ||
| ShareParams(files: [XFile.fromData(_byteData!, path: file.path)]), | ||
| ); | ||
| } else if (Platform.isWindows) { | ||
| await Process.run('explorer.exe', [ | ||
| '/select,${file.path.replaceAll('/', '\\')}', | ||
| ]); | ||
| Pasteboard.writeFiles([file.path.replaceAll('/', '\\')]); | ||
| } | ||
| if (mounted) { | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
| content: Text( | ||
| Platform.isAndroid | ||
| ? S.of(context).savedToGallery | ||
| : S.of(context).copiedAndSaved, | ||
| ), | ||
| ), | ||
| ); | ||
| } | ||
| } catch (e) { | ||
| if (kDebugMode) print(e); | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing web platform guard and silent error handling.
The _handleImageTap method uses Platform.isAndroid and Platform.isWindows directly, but on web, accessing Platform throws an UnsupportedError. Per coding guidelines, use kIsWeb check first. Additionally, the catch block silently logs errors without providing user feedback via ScaffoldMessenger.
🛠️ Proposed fix
Future<void> _handleImageTap() async {
if (_byteData == null) return;
+ if (kIsWeb) {
+ // Handle web export separately (e.g., download via browser)
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(S.of(context).webExportNotSupported)),
+ );
+ return;
+ }
try {
final path =
Platform.isAndroid
? '/storage/emulated/0/Pictures/pjsk_sticker/pjsk_${DateTime.now().millisecondsSinceEpoch}.png'
: 'pjsk_sticker/pjsk_${DateTime.now().millisecondsSinceEpoch}.png';
// ... rest of the method
} catch (e) {
if (kDebugMode) print(e);
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(S.of(context).exportError)),
+ );
+ }
}
}As per coding guidelines: "Use kIsWeb and Platform.is... checks to differentiate file-saving logic between web and native platforms" and "Wrap all asset loading and file I/O operations in try-catch blocks with ScaffoldMessenger feedback to users."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/pages/sticker/sticker_page_logic.dart` around lines 87 - 121,
_handleImageTap uses Platform.isAndroid/Windows directly which throws on web and
currently only prints errors in debug mode; add a kIsWeb guard at the start of
_handleImageTap to handle web separately (e.g., early-return or web-specific
share/save flow), ensure all Platform checks remain behind kIsWeb false, and
improve the catch block to show user-facing feedback via
ScaffoldMessenger.of(context).showSnackBar using mounted before showing;
reference symbols: _handleImageTap, _byteData, Platform, kIsWeb, mounted,
ScaffoldMessenger, SnackBar.
| final http.Response response = await _client.get( | ||
| _releasesUri, | ||
| headers: <String, String>{ | ||
| 'Accept': 'application/vnd.github+json', | ||
| 'User-Agent': _userAgent, | ||
| if (manual) 'Cache-Control': 'no-cache', | ||
| }, | ||
| ); | ||
|
|
||
| if (response.statusCode != 200) { | ||
| throw Exception('Failed to fetch releases: HTTP ${response.statusCode}'); | ||
| } |
There was a problem hiding this comment.
Wrap HTTP call in try-catch to handle network failures gracefully.
The _client.get() call can throw on network failures (no connectivity, DNS resolution failure, timeout, etc.). The caller will need to handle this, but per coding guidelines, I/O operations should be wrapped with error handling and user feedback.
Consider catching http.ClientException (or broader exceptions) and either rethrowing a domain-specific exception or returning null for silent failure on automatic checks.
🛡️ Suggested approach
+ import 'package:http/http.dart' show Client, ClientException, Response;
...
Future<UpdateInfo?> checkForUpdate({required bool manual}) async {
final Version currentVersion = parseComparableVersion(
_currentVersionProvider(),
);
final bool allowPrerelease = currentVersion.isPreRelease;
final String? ignoredVersion = await _readIgnoredVersion();
+ final http.Response response;
+ try {
- final http.Response response = await _client.get(
+ response = await _client.get(
_releasesUri,
headers: <String, String>{
'Accept': 'application/vnd.github+json',
'User-Agent': _userAgent,
if (manual) 'Cache-Control': 'no-cache',
},
);
+ } on Exception {
+ // Network error - rethrow or return null for silent failure
+ rethrow;
+ }As per coding guidelines: "Wrap all asset loading and file I/O operations in try-catch blocks with ScaffoldMessenger feedback to users"
📝 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.
| final http.Response response = await _client.get( | |
| _releasesUri, | |
| headers: <String, String>{ | |
| 'Accept': 'application/vnd.github+json', | |
| 'User-Agent': _userAgent, | |
| if (manual) 'Cache-Control': 'no-cache', | |
| }, | |
| ); | |
| if (response.statusCode != 200) { | |
| throw Exception('Failed to fetch releases: HTTP ${response.statusCode}'); | |
| } | |
| final http.Response response; | |
| try { | |
| response = await _client.get( | |
| _releasesUri, | |
| headers: <String, String>{ | |
| 'Accept': 'application/vnd.github+json', | |
| 'User-Agent': _userAgent, | |
| if (manual) 'Cache-Control': 'no-cache', | |
| }, | |
| ); | |
| } on Exception { | |
| // Network error - rethrow or return null for silent failure | |
| rethrow; | |
| } | |
| if (response.statusCode != 200) { | |
| throw Exception('Failed to fetch releases: HTTP ${response.statusCode}'); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/update/update_checker.dart` around lines 41 - 52, The HTTP GET call using
_client.get against _releasesUri can throw on network failures; wrap the call in
a try-catch inside the method (the block around _client.get in
update_checker.dart) to catch http.ClientException and other Exceptions, log or
convert them to a domain-specific exception (or return null) and rethrow or
propagate a safe error to the caller; ensure you preserve the existing
200-status check and only throw the current 'Failed to fetch releases' exception
for non-200 responses after a successful request, and include the caught error
details in the new thrown/logged message so callers can surface user-facing
feedback.
Summary by CodeRabbit
Release Notes
New Features
Chores