Skip to content

Refactor 4#80

Merged
GT-610 merged 10 commits into
mainfrom
refactor-4
Jun 8, 2026
Merged

Refactor 4#80
GT-610 merged 10 commits into
mainfrom
refactor-4

Conversation

@GT-610

@GT-610 GT-610 commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes

    • Fixed input obscured-text sync after widget updates
    • Improved directory opening URI handling on non-Windows
    • Hardened system tray and RPC error handling; safer async error reporting
  • New Features

    • Added stable task key for consistent task selection
  • Performance Improvements

    • More efficient piece-grid rendering and batched debug log updates
  • Improvements

    • Appearance and settings persistence now performed asynchronously with safer UI guards

GT-610 added 9 commits June 7, 2026 16:19
Bug fixes:
- SystemTrayService.updateMenuState now rebuilds tray context menu after
  updating labels (menu was stale until next right-click)
- BuiltinInstanceService.dispose() explicitly discards stopInstance() future
  instead of fire-and-forget, and removes duplicate _upnpService.shutdown()
  call (stopInstance() already handles UPnP shutdown)
- AppearanceDialog: await async setThemeMode/setPrimaryColor calls so
  try-catch blocks can actually catch failures; add mounted guard before
  showing SnackBar after await
- SettingsPage locale dialog: await setLocale() before Navigator.pop so
  save failures are not silently swallowed; add mounted guard
- DebugProvider: replace in-place VNode list mutation (add/removeRange/clear)
  with immutable list reassignment to maintain VNode setter contract
- Input: add didUpdateWidget to sync _obscureText when parent changes
  obscureText prop
- App: remove duplicate loadSettings() call from _ThemeProviderState.initState
  (HomeWrapper._initialize already handles loading with proper await)
Performance:
- Refactor _getConfiguredRpcPort/Secret to accept optional settings map
  parameter, eliminating 2 redundant _readSettingsSnapshot() calls in both
  _buildArgs() and getBuiltinInstanceConfig() (6 sync file reads → 4)
- Remove unreachable duplicate guards in SystemTrayService.initialize()
  (dead code after in-flight await)
Code quality:
- SettingsPage._buildSettingsGroup: remove no-op identity map
  (.map((child) => child))
- Settings._settingsFileName: change from final to static const
- Settings: inline trivial _getDataDirectory() wrapper (single caller)
- CustomAppBar/CardX: remove redundant key forwarding to inner widget
  (Flutter handles key reconciliation on the outermost widget)
- App: extract _swapListener<T> helper to deduplicate 3 near-identical
  listener swap blocks in didChangeDependencies
Cleanup:
- Remove unused dart:async import in AutoHideWindowService
- Fix _formatVersionLabel to display full version string (was showing
  only patch number, e.g. 'v3' instead of 'v1.2.3')
- Remove redundant inline comments on enum values in enums.dart
  (comments restated the value names)
- Simplify VirtualWindowFrame switch to a single conditional expression
  (first and third switch arms produced identical output)
Bug fixes:
- DebugProvider: trim widgets.value alongside lines and _widgetCounts
  (widgets list was growing unboundedly while lines was capped at 100)
- Aria2RpcClient._handleWebSocketMessage: add type guard for non-Map
  JSON responses (arrays/primitives caused NoSuchMethodError)
- Aria2RpcClient._callHttpRpc: replace _httpClient! force-unwrap with
  null check + ConnectionFailedException (prevents crash after close())
- Aria2RpcClient: store WebSocket StreamSubscription in
  _webSocketSubscription field; cancel in close() and _connectWebSocket
  to prevent callbacks firing on disposed client
- DownloadPage._pruneSelection: skip setState when no keys were removed
  (prevents unnecessary rebuild cycles on every data change)
Performance:
- DownloadPage._filterTasks: combine category filter, status/type filter,
  and search filter into a single retainWhere pass (was creating 3-5
  intermediate lists via .where().toList() per filter call)
- DownloadPage._filterTasks: inline sort key computation in comparator
  (eliminates O(n) sortKeys map allocation per sort)
- TaskDetailsBtHelpers._buildPiecesGrid: replace Wrap+List.generate with
  CustomPaint painter (renders thousands of pieces without creating
  individual Container widgets, eliminates O(n) widget allocation)
…odels

Code quality:
- Aria2RpcClient._callHttpRpc: remove redundant response.body.contains
  check for 'Unauthorized' (structured JSON check is sufficient)
- Aria2RpcClient.getVersion: delegate to getVersionInfo() to eliminate
  duplicate RPC call
- Aria2RpcClient._handleWebSocketError: always wrap errors in
  ConnectionFailedException instead of leaking raw error types
- AddTaskDialog: move outputField creation inside two-column branch
  (was allocated but unused in single-column path)
- DownloadTask: add key getter ('::') to eliminate
  duplicate _taskKey function in download_page.dart and task_list_view.dart
- FilterSelector: inline trivial _getInstanceFilterOptions wrapper
Cleanup:
- DownloadTaskService: inline trivial _stoppingSeedingTip and
  _failedToStopSeedingMessage wrappers (single-line l10n accessors)
- task_utils: use Uri.file() instead of Uri.parse('file://...') for
  correct platform-specific file URI construction (handles paths with
  spaces and special characters on Linux/macOS)
- TaskDetailsBtHelpers: add explanatory comment to catch(_) block in
  parseTorrentMetadata (best-effort parsing returns empty on malformed
  torrent data)
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 70b2dd9e-0f04-4c72-8462-58cad8111132

📥 Commits

Reviewing files that changed from the base of the PR and between 2e3d5ba and 13dad47.

📒 Files selected for processing (8)
  • lib/kit/widgets/virtual_window_frame.dart
  • lib/pages/download_page/components/task_details_bt_helpers.dart
  • lib/pages/download_page/download_page.dart
  • lib/pages/download_page/enums.dart
  • lib/pages/download_page/services/download_task_service.dart
  • lib/pages/settings_page/components/appearance_dialog.dart
  • lib/services/aria2_rpc_client.dart
  • lib/services/system_tray_service.dart
✅ Files skipped from review due to trivial changes (1)
  • lib/pages/download_page/enums.dart
🚧 Files skipped from review as they are similar to previous changes (7)
  • lib/kit/widgets/virtual_window_frame.dart
  • lib/pages/download_page/services/download_task_service.dart
  • lib/pages/download_page/components/task_details_bt_helpers.dart
  • lib/pages/settings_page/components/appearance_dialog.dart
  • lib/services/system_tray_service.dart
  • lib/pages/download_page/download_page.dart
  • lib/services/aria2_rpc_client.dart

📝 Walkthrough

Walkthrough

This PR consolidates infrastructure and refactoring improvements across the download page, app lifecycle, RPC client, and UI kit. Task selection logic is shifted to use a new composite key property; filtering and sorting pipelines are simplified; settings persistence is made asynchronous with lifecycle safety; WebSocket subscription management is improved; and various service initialization patterns are tidied up.

Changes

Download Selection and Service Improvements

Layer / File(s) Summary
Task key property and enum formatting
lib/pages/download_page/models/download_task.dart, lib/pages/download_page/enums.dart
DownloadTask now exports a computed key getter combining instanceId and id; enum definitions are reformatted to single-line style.
Task selection system refactoring
lib/pages/download_page/components/task_list_view.dart, lib/pages/download_page/download_page.dart
Selection logic throughout DownloadPage is updated to use task.key instead of the removed composite _taskKey helper; _pruneSelection now only triggers setState when selection count changes.
Filtering and sorting refactoring
lib/pages/download_page/download_page.dart
_filterTasks consolidates category and search matching into predicate functions applied via retainWhere, and replaces precomputed sortKeys with inline per-compare key calculation.
Download page UI component updates
lib/pages/download_page/components/add_task_dialog.dart, lib/pages/download_page/components/filter_selector.dart, lib/pages/download_page/components/task_details_bt_helpers.dart, lib/pages/download_page/services/download_task_service.dart
Add-task-dialog widget construction is deferred to relevant layout paths; filter selector maps directly over instance IDs; pieces grid switches from per-piece containers to LayoutBuilder + CustomPaint; localization helper methods were removed in favor of direct AppLocalizations calls for SnackBars.
Settings initialization and model simplification
lib/app.dart, lib/models/settings.dart
_ThemeProviderState.initState no longer loads settings; _settingsFileName becomes static const; settings file path helper calls getAppDataDirectory() directly.
Debug provider batching and UI kit widget updates
lib/kit/provider/debug.dart, lib/kit/widgets/appbar.dart, lib/kit/widgets/card.dart, lib/kit/widgets/input.dart, lib/kit/widgets/virtual_window_frame.dart
Debug provider batches log UI updates via local list; AppBar and Card key arguments removed from constructor calls; Input syncs internal obscureText via didUpdateWidget; VirtualWindowFrame layout uses conditional expression instead of switch.
Main window listener management
lib/app.dart
_MainWindowState.didChangeDependencies refactored to use a reusable _swapListener helper for Provider listener changes and retains the post-frame _applyShellSettings() scheduling.
Asynchronous settings persistence handlers
lib/pages/settings_page/components/appearance_dialog.dart, lib/pages/settings_page/settings_page.dart, lib/pages/download_page/utils/task_utils.dart
Theme mode, color, and locale selection handlers become async and await Settings setters; mounted guards prevent snackbars on unmounted widgets; version label uses full version string; URI construction uses Uri.file() on non-Windows platforms.
RPC client WebSocket and error handling improvements
lib/services/aria2_rpc_client.dart
Dedicated _webSocketSubscription tracking with cancellation on reconnect and close; HTTP Unauthorized detection via structured JSON check; WebSocket error mapping simplifies to ConnectionFailedException default; getVersion() calls getVersionInfo(); incoming WebSocket messages validated as Map<String, dynamic>.
Service initialization and lifecycle improvements
lib/services/builtin_instance_service.dart, lib/services/system_tray_service.dart, lib/services/auto_hide_window_service.dart
RPC port/secret helpers accept optional settings snapshot to avoid redundant reads; tray menu update errors are logged instead of propagated; dispose() explicitly fire-and-forgets async shutdown; unused dart:async import removed.

Possibly Related PRs

  • GT-610/setsuna#77: Both modify the same UI kit refactoring checkpoint, including DebugProvider batching, CustomAppBar/CardX key handling, and Input state synchronization.
  • GT-610/setsuna#78: Both update VirtualWindowFrame caption rendering logic and DownloadPage _pruneSelection behavior.
  • GT-610/setsuna#72: Related changes around getAppDataDirectory() usage and settings file path construction.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Refactor 4' is vague and generic, providing no meaningful information about the specific changes in this pull request. Replace with a descriptive title that summarizes the primary refactoring focus, such as 'Refactor task selection key handling and settings lifecycle' or 'Refactor RPC client and improve async UI handlers'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
lib/pages/download_page/services/download_task_service.dart (1)

1-669: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Apply Dart formatting to pass CI.

flutter analysis indicates this file would be changed by formatter. Please run dart format lib/pages/download_page/services/download_task_service.dart (or flutter format .).

As per coding guidelines, "Use flutter format . for automatic code formatting with max line length of 80 characters."

🤖 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/pages/download_page/services/download_task_service.dart` around lines 1 -
669, Run the Dart formatter on the file to satisfy CI: execute `dart format
lib/pages/download_page/services/download_task_service.dart` (or `flutter format
.`) to apply the project's formatting rules (max line length 80); this will
reflow imports, line breaks and indentation for the DownloadTaskService class
and its functions (e.g. deleteTaskWithClient, _deleteDownloadedFiles,
_cleanupEmptyDirectories, getStatusInfo) so no manual code changes are
needed—commit the formatted file.

Sources: Coding guidelines, Pipeline failures

lib/pages/download_page/download_page.dart (1)

1-1034: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Format this file to satisfy the analysis check.

The pipeline indicates dart format would modify this file. Please run dart format lib/pages/download_page/download_page.dart (or flutter format .).

As per coding guidelines, "Use flutter format . for automatic code formatting with max line length of 80 characters."

🤖 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/pages/download_page/download_page.dart` around lines 1 - 1034, This file
fails the formatter; run the Flutter/Dart formatter to fix style issues across
DownloadPage and its state (e.g., class DownloadPage, DownloadPageState, and
_SelectionToolbar) — run "flutter format ." (or "dart format
lib/pages/download_page/download_page.dart") in the repo root to apply automatic
formatting with the project's line-length rules (max 80 chars), then re-run the
analysis checks and commit the formatted changes.

Sources: Coding guidelines, Pipeline failures

lib/pages/download_page/components/task_details_bt_helpers.dart (1)

1-524: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run formatter for this file before merge.

The pipeline reports this file is not formatted. Please run dart format lib/pages/download_page/components/task_details_bt_helpers.dart (or flutter format .).

As per coding guidelines, "Use flutter format . for automatic code formatting with max line length of 80 characters."

🤖 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/pages/download_page/components/task_details_bt_helpers.dart` around lines
1 - 524, The file containing TaskDetailsBtHelpers (classes TaskDetailsBtHelpers,
_PiecesGridPainter, TaskDetailsTorrentOverviewMetadata and methods like
buildBitfieldVisualization, buildPeersView, parseTorrentMetadata,
_formatPeerVersion) is not formatted; run the project formatter (flutter format
. or dart format) to apply the standard style (max line length 80) and re-run
the pipeline so the spacing, imports and line breaks around these symbols are
normalized before merging.

Sources: Coding guidelines, Pipeline failures

lib/pages/download_page/enums.dart (1)

1-31: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run formatter to unblock CI.

flutter analysis reports this file would be reformatted. Please run dart format lib/pages/download_page/enums.dart (or flutter format .) before merge.

As per coding guidelines, "Use flutter format . for automatic code formatting with max line length of 80 characters."

🤖 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/pages/download_page/enums.dart` around lines 1 - 31, This file's
formatting is off; run the Dart/Flutter formatter to fix style for the enum
declarations (DownloadStatus, CategoryType, FilterOption, TaskSortOption) — run
`dart format lib/pages/download_page/enums.dart` or `flutter format .` (with
project max line length 80) and commit the reformatted file so CI passes.

Sources: Coding guidelines, Pipeline failures

lib/pages/settings_page/components/appearance_dialog.dart (1)

269-309: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add error handling to the G/B async color-save callbacks.

The G and B onChangeEnd handlers await setPrimaryColor without try/catch, unlike the R handler. This leaves async failures unhandled in UI callbacks.

Proposed fix
                         onChangeEnd: (value) async {
                           final newColor = Color.fromRGBO(
                             (_selectedColor.r * 255.0).round() & 0xff,
                             value.toInt(),
                             (_selectedColor.b * 255.0).round() & 0xff,
                             1.0,
                           );
-                          await widget.settings.setPrimaryColor(
-                            newColor,
-                            isCustom: true,
-                          );
+                          try {
+                            await widget.settings.setPrimaryColor(
+                              newColor,
+                              isCustom: true,
+                            );
+                          } catch (e) {
+                            if (!mounted) return;
+                            ScaffoldMessenger.of(context).showSnackBar(
+                              SnackBar(
+                                content: Text(
+                                  l10n.failedToSetCustomThemeColor('$e'),
+                                ),
+                              ),
+                            );
+                          }
                         },
@@
                         onChangeEnd: (value) async {
                           final newColor = Color.fromRGBO(
                             (_selectedColor.r * 255.0).round() & 0xff,
                             (_selectedColor.g * 255.0).round() & 0xff,
                             value.toInt(),
                             1.0,
                           );
-                          await widget.settings.setPrimaryColor(
-                            newColor,
-                            isCustom: true,
-                          );
+                          try {
+                            await widget.settings.setPrimaryColor(
+                              newColor,
+                              isCustom: true,
+                            );
+                          } catch (e) {
+                            if (!mounted) return;
+                            ScaffoldMessenger.of(context).showSnackBar(
+                              SnackBar(
+                                content: Text(
+                                  l10n.failedToSetCustomThemeColor('$e'),
+                                ),
+                              ),
+                            );
+                          }
                         },

As per coding guidelines: "Use async/await for asynchronous code instead of raw Futures, and handle async errors with try-catch."

🤖 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/pages/settings_page/components/appearance_dialog.dart` around lines 269 -
309, The G and B slider onChangeEnd handlers call await
widget.settings.setPrimaryColor without error handling; wrap each async call in
a try/catch to mirror the R handler: inside the onChangeEnd for the 'G' and for
the 'B' _buildColorSlider callbacks, perform the await
widget.settings.setPrimaryColor(newColor, isCustom: true) inside a try { ... }
catch (e, st) { widget.settingsLogger?.error(...) or print/handle the error }
and (optionally) restore UI state or show a user-facing error if needed;
reference the _selectedColor, widget.settings.setPrimaryColor, and the
onChangeEnd closures to locate the exact spots to modify.

Source: Coding guidelines

lib/pages/settings_page/settings_page.dart (1)

726-751: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wrap async locale persistence in try/catch before closing the dialog.

These three async onTap handlers await setLocale but don't catch errors. Failures can escape the callback and skip user feedback.

Proposed fix
                 onTap: () async {
-                  await settings.setLocale(null);
+                  try {
+                    await settings.setLocale(null);
+                  } catch (e, stackTrace) {
+                    this.e('Failed to set locale', error: e, stackTrace: stackTrace);
+                    if (!context.mounted) return;
+                    _showErrorSnackBar(l10n.saveSettingsFailed);
+                    return;
+                  }
                   if (!context.mounted) return;
                   Navigator.pop(context);
                 },
@@
                 onTap: () async {
-                  await settings.setLocale(const Locale('en'));
+                  try {
+                    await settings.setLocale(const Locale('en'));
+                  } catch (e, stackTrace) {
+                    this.e('Failed to set locale', error: e, stackTrace: stackTrace);
+                    if (!context.mounted) return;
+                    _showErrorSnackBar(l10n.saveSettingsFailed);
+                    return;
+                  }
                   if (!context.mounted) return;
                   Navigator.pop(context);
                 },
@@
                 onTap: () async {
-                  await settings.setLocale(const Locale('zh'));
+                  try {
+                    await settings.setLocale(const Locale('zh'));
+                  } catch (e, stackTrace) {
+                    this.e('Failed to set locale', error: e, stackTrace: stackTrace);
+                    if (!context.mounted) return;
+                    _showErrorSnackBar(l10n.saveSettingsFailed);
+                    return;
+                  }
                   if (!context.mounted) return;
                   Navigator.pop(context);
                 },

As per coding guidelines: "Use async/await for asynchronous code instead of raw Futures, and handle async errors with try-catch."

🤖 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/pages/settings_page/settings_page.dart` around lines 726 - 751, Each
async onTap handler that calls settings.setLocale (the three closures containing
await settings.setLocale(...)) should wrap the await in a try/catch so errors
are caught before closing the dialog; call await settings.setLocale(...) inside
try, check if (!context.mounted) return, then Navigator.pop(context) on success,
and handle the error in catch (e.g., show an error UI or log) without allowing
the exception to escape; reference the onTap closures, the settings.setLocale
calls, context.mounted checks, and Navigator.pop to locate and update all three
handlers.

Source: Coding guidelines

lib/services/system_tray_service.dart (1)

1-345: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run dart format on this file before merge.

CI is currently failing because formatting is out of sync for this file.

As per coding guidelines, "Use flutter format . for automatic code formatting with max line length of 80 characters."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/services/system_tray_service.dart` around lines 1 - 345, The file
lib/services/system_tray_service.dart is out of formatter sync; run the project
formatter (e.g., flutter format . or dart format .) and reformat this file
(containing the SystemTrayService class) to comply with the project's 80-column
rule, then stage and commit the formatted changes so CI passes.

Sources: Coding guidelines, Pipeline failures

🤖 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/kit/widgets/virtual_window_frame.dart`:
- Around line 25-34: The file fails dart formatting; run the Dart formatter
(flutter format . or dart format) targeting this file to fix line breaks and max
line length (80) so the conditional assignment creating content (involving
CustomAppBar.sysStatusBarHeight, WindowFrameConfig.showCaption, _WindowCaption
and Expanded) matches project style; ensure the resulting code compiles and
update the PR with the formatted file.

In `@lib/pages/download_page/components/task_details_bt_helpers.dart`:
- Around line 428-437: Guard against an empty pieces list inside the
LayoutBuilder builder before computing geometry: at the top of the builder
closure (before calculating pieceSize/spacing/cols/rows/gridHeight) check if
pieces.isEmpty and immediately return a zero-size widget (e.g.,
SizedBox.shrink()) so you don't call .clamp(1, pieces.length) with pieces.length
== 0 or compute rows via division by zero; update the closure that defines
pieceSize, spacing, cols, rows, gridHeight to be after this early-return guard.

In `@lib/services/aria2_rpc_client.dart`:
- Around line 198-209: In _connectWebSocket(), await the previous subscription
cancellation before tearing down and creating a new listener: call and await the
Future returned by _webSocketSubscription?.cancel() (or assign it to a variable
and await it) prior to setting _webSocketSubscription/_webSocket to null and
before calling _webSocket!.listen(_handleWebSocketMessage); this ensures any
pending onDone/onError handlers (_handleWebSocketDone/_handleWebSocketError)
from the old subscription finish before the new subscription is attached and
prevents race conditions clearing _webSocket or _pendingRequests for the new
connection.
- Around line 80-85: Guard decoded JSON shapes and await websocket cancellation:
in _callHttpRpc, after jsonDecode(response.body) verify the decoded value is a
Map<String, dynamic> before using containsKey or indexing into data['error'],
and ensure data['error'] is a Map with a String 'message' key before comparing
to 'Unauthorized' so malformed/non-map payloads don't throw; map unexpected
shapes to a generic RPC/parse error or rethrow wrapped in UnauthorizedException
where appropriate. In _handleWebSocketMessage, check that the parsed data is a
Map and that data['error'] is a Map with a 'message' String before accessing
data['error']['message'], and ensure any path that can leave the related
Completer unresolved instead completesError or completes with an error to avoid
leaked pending completers. In _connectWebSocket, await the result of
_webSocketSubscription?.cancel() (i.e., await the Future) before replacing the
subscription to avoid races when swapping listeners.

---

Outside diff comments:
In `@lib/pages/download_page/components/task_details_bt_helpers.dart`:
- Around line 1-524: The file containing TaskDetailsBtHelpers (classes
TaskDetailsBtHelpers, _PiecesGridPainter, TaskDetailsTorrentOverviewMetadata and
methods like buildBitfieldVisualization, buildPeersView, parseTorrentMetadata,
_formatPeerVersion) is not formatted; run the project formatter (flutter format
. or dart format) to apply the standard style (max line length 80) and re-run
the pipeline so the spacing, imports and line breaks around these symbols are
normalized before merging.

In `@lib/pages/download_page/download_page.dart`:
- Around line 1-1034: This file fails the formatter; run the Flutter/Dart
formatter to fix style issues across DownloadPage and its state (e.g., class
DownloadPage, DownloadPageState, and _SelectionToolbar) — run "flutter format ."
(or "dart format lib/pages/download_page/download_page.dart") in the repo root
to apply automatic formatting with the project's line-length rules (max 80
chars), then re-run the analysis checks and commit the formatted changes.

In `@lib/pages/download_page/enums.dart`:
- Around line 1-31: This file's formatting is off; run the Dart/Flutter
formatter to fix style for the enum declarations (DownloadStatus, CategoryType,
FilterOption, TaskSortOption) — run `dart format
lib/pages/download_page/enums.dart` or `flutter format .` (with project max line
length 80) and commit the reformatted file so CI passes.

In `@lib/pages/download_page/services/download_task_service.dart`:
- Around line 1-669: Run the Dart formatter on the file to satisfy CI: execute
`dart format lib/pages/download_page/services/download_task_service.dart` (or
`flutter format .`) to apply the project's formatting rules (max line length
80); this will reflow imports, line breaks and indentation for the
DownloadTaskService class and its functions (e.g. deleteTaskWithClient,
_deleteDownloadedFiles, _cleanupEmptyDirectories, getStatusInfo) so no manual
code changes are needed—commit the formatted file.

In `@lib/pages/settings_page/components/appearance_dialog.dart`:
- Around line 269-309: The G and B slider onChangeEnd handlers call await
widget.settings.setPrimaryColor without error handling; wrap each async call in
a try/catch to mirror the R handler: inside the onChangeEnd for the 'G' and for
the 'B' _buildColorSlider callbacks, perform the await
widget.settings.setPrimaryColor(newColor, isCustom: true) inside a try { ... }
catch (e, st) { widget.settingsLogger?.error(...) or print/handle the error }
and (optionally) restore UI state or show a user-facing error if needed;
reference the _selectedColor, widget.settings.setPrimaryColor, and the
onChangeEnd closures to locate the exact spots to modify.

In `@lib/pages/settings_page/settings_page.dart`:
- Around line 726-751: Each async onTap handler that calls settings.setLocale
(the three closures containing await settings.setLocale(...)) should wrap the
await in a try/catch so errors are caught before closing the dialog; call await
settings.setLocale(...) inside try, check if (!context.mounted) return, then
Navigator.pop(context) on success, and handle the error in catch (e.g., show an
error UI or log) without allowing the exception to escape; reference the onTap
closures, the settings.setLocale calls, context.mounted checks, and
Navigator.pop to locate and update all three handlers.

In `@lib/services/system_tray_service.dart`:
- Around line 1-345: The file lib/services/system_tray_service.dart is out of
formatter sync; run the project formatter (e.g., flutter format . or dart format
.) and reformat this file (containing the SystemTrayService class) to comply
with the project's 80-column rule, then stage and commit the formatted changes
so CI passes.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 52523a9c-6f93-4422-ad80-b919d11f36be

📥 Commits

Reviewing files that changed from the base of the PR and between b2d285b and 2e3d5ba.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • lib/app.dart
  • lib/kit/provider/debug.dart
  • lib/kit/widgets/appbar.dart
  • lib/kit/widgets/card.dart
  • lib/kit/widgets/input.dart
  • lib/kit/widgets/virtual_window_frame.dart
  • lib/models/settings.dart
  • lib/pages/download_page/components/add_task_dialog.dart
  • lib/pages/download_page/components/filter_selector.dart
  • lib/pages/download_page/components/task_details_bt_helpers.dart
  • lib/pages/download_page/components/task_list_view.dart
  • lib/pages/download_page/download_page.dart
  • lib/pages/download_page/enums.dart
  • lib/pages/download_page/models/download_task.dart
  • lib/pages/download_page/services/download_task_service.dart
  • lib/pages/download_page/utils/task_utils.dart
  • lib/pages/settings_page/components/appearance_dialog.dart
  • lib/pages/settings_page/settings_page.dart
  • lib/services/aria2_rpc_client.dart
  • lib/services/auto_hide_window_service.dart
  • lib/services/builtin_instance_service.dart
  • lib/services/system_tray_service.dart
💤 Files with no reviewable changes (3)
  • lib/kit/widgets/appbar.dart
  • lib/services/auto_hide_window_service.dart
  • lib/kit/widgets/card.dart

Comment thread lib/kit/widgets/virtual_window_frame.dart Outdated
Comment thread lib/pages/download_page/components/task_details_bt_helpers.dart
Comment thread lib/services/aria2_rpc_client.dart
Comment thread lib/services/aria2_rpc_client.dart
Bug fixes:
- TaskDetailsBtHelpers._buildPiecesGrid: add empty pieces guard to
  prevent ArgumentError from .clamp(1, 0) when pieces list is empty
- Aria2RpcClient._callHttpRpc: guard data['error'] is Map before
  accessing ['message'] to prevent TypeError on malformed responses
- Aria2RpcClient._handleWebSocketMessage: same guard for data['error']
  is Map before accessing ['message']
- AppearanceDialog: add try-catch to G and B slider onChangeEnd handlers
  (R slider already had error handling, G/B were missing)

Formatting:
- Run dart format on 7 files to comply with project 80-column style:
  virtual_window_frame, task_details_bt_helpers, download_page, enums,
  download_task_service, system_tray_service, aria2_rpc_client

Skipped findings (not valid):
- _connectWebSocket await subscription cancel: cancel() returns
  synchronously for non-pending operations, no race condition risk
- settings_page setLocale try-catch: already has await + context.mounted
  guard, setLocale failures propagate as unhandled which is acceptable
@GT-610 GT-610 merged commit 0624ca5 into main Jun 8, 2026
2 checks passed
@GT-610 GT-610 deleted the refactor-4 branch June 8, 2026 01:54
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