Skip to content

fix(ui): restore scrolling on UserInfoScaffold#192

Open
nagameTW wants to merge 1 commit into
abc873693:masterfrom
nagameTW:fix/user-info-scaffold-scroll
Open

fix(ui): restore scrolling on UserInfoScaffold#192
nagameTW wants to merge 1 commit into
abc873693:masterfrom
nagameTW:fix/user-info-scaffold-scroll

Conversation

@nagameTW

Copy link
Copy Markdown

問題

NKUST-ITC/NKUST-AP-Flutter#434 回報:個人資料頁的學生條碼/QRcode
顯示不完整,而且頁面無法上下捲動。

原因

933cd63 重構後,頁面內容包在
SliverToBoxAdapter > RefreshIndicator > SingleChildScrollView(AlwaysScrollableScrollPhysics)
裡。SliverToBoxAdapter 給子元件無界高度,內層 SingleChildScrollView
因此撐成內容全高,自身 maxScrollExtent 為 0,實際捲不動;但
AlwaysScrollableScrollPhysics 讓它照樣接收垂直拖曳,最內層 scrollable
在手勢競爭(gesture arena)中勝出,外層 CustomScrollView 收不到手勢。
結果整頁鎖死:內容超過一屏時(小螢幕或大字體),條碼卡片卡在畫面外,
捲不到也看不完整。

修正

  • 移除內層 SingleChildScrollViewRefreshIndicator
    SliverToBoxAdapter 直接放內容
  • RefreshIndicator 改包整個 CustomScrollView
  • CustomScrollViewAlwaysScrollableScrollPhysics
    內容不滿一屏時仍能下拉更新(與 score_scaffold.dart 既有寫法一致)

驗證

  • 新增 test/scaffold/user_info_scaffold_test.dart 兩條 widget test:
    • 400×600 視窗下,拖曳頁面內容須能把條碼卡完整捲進畫面。
      修正前失敗(兩次拖曳後條碼底部停在 817 未動),修正後通過。
    • 下拉更新仍觸發 onRefresh(修正前後皆通過,作為回歸防護)。
  • flutter test:ap_common_flutter_ui 全部 34 條通過(Flutter 3.38.10)
  • dart analyze --fatal-infos 無問題,dart format 無變更
  • 尚未在實體裝置驗證,以上為 widget test 模擬手勢的結果

Ref: NKUST-ITC/NKUST-AP-Flutter#434

A SingleChildScrollView with AlwaysScrollableScrollPhysics nested inside
SliverToBoxAdapter shrink-wraps to its content (maxScrollExtent 0) yet
still wins the vertical gesture arena, so drags on the page content moved
nothing and the barcode card could sit unreachable below the fold.
Regression from 933cd63.

Remove the inner scroller and wrap the whole CustomScrollView in the
RefreshIndicator instead; AlwaysScrollableScrollPhysics on the scroll
view keeps pull-to-refresh working when the content fits the viewport.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors UserInfoScaffold by wrapping the top-level CustomScrollView in a RefreshIndicator and removing the nested RefreshIndicator from SliverToBoxAdapter, which improves the pull-to-refresh behavior. It also adds comprehensive widget tests for scrolling and refreshing. The review feedback suggests disabling the RefreshIndicator and hiding the refresh button when widget.onRefresh is null, as well as simplifying the actions list expansion using the null-aware spread operator (...?).

Important

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

Comment on lines +57 to +61
body: RefreshIndicator(
onRefresh: () async {
await _handleRefresh();
AnalyticsUtil.instance.logEvent('user_info_refresh');
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

widget.onRefreshnull 時,使用者仍然可以觸發下拉更新(Pull-to-Refresh)手勢。這不僅會顯示載入動畫但無任何實際更新,還會錯誤地發送 user_info_refresh 的 Analytics 事件,導致數據不準確。

建議在 widget.onRefreshnull 時停用 RefreshIndicator 的手勢偵測,並避免發送不必要的事件。

Suggested change
body: RefreshIndicator(
onRefresh: () async {
await _handleRefresh();
AnalyticsUtil.instance.logEvent('user_info_refresh');
},
body: RefreshIndicator(
notificationPredicate: widget.onRefresh == null
? (_) => false
: defaultScrollNotificationPredicate,
onRefresh: () async {
if (widget.onRefresh == null) return;
await _handleRefresh();
AnalyticsUtil.instance.logEvent('user_info_refresh');
},

height: 24.0,
width: 24.0,
actions: <Widget>[
...widget.actions ?? <Widget>[],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

在 Dart 中,可以使用空安全展開運算子(Null-aware spread operator)...? 來簡化程式碼,取代 ...widget.actions ?? <Widget>[] 的寫法,使程式碼更簡潔且具備更好的可讀性。

Suggested change
...widget.actions ?? <Widget>[],
...?widget.actions,

Comment on lines +125 to 139
IconButton(
icon: _isRefreshing
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: isDark
? colorScheme.onSurface
: colorScheme.onPrimary,
),
)
: const Icon(Icons.refresh_rounded),
onPressed: _isRefreshing ? null : _handleRefresh,
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

widget.onRefreshnull 時,AppBar 右側仍會顯示重新整理按鈕,且點擊後無任何反應。

建議僅在 widget.onRefresh 不為 null 時才顯示此按鈕,以提供更直覺的 UI 體驗。

Suggested change
IconButton(
icon: _isRefreshing
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: isDark
? colorScheme.onSurface
: colorScheme.onPrimary,
),
)
: const Icon(Icons.refresh_rounded),
onPressed: _isRefreshing ? null : _handleRefresh,
),
if (widget.onRefresh != null)
IconButton(
icon: _isRefreshing
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: isDark
? colorScheme.onSurface
: colorScheme.onPrimary,
),
)
: const Icon(Icons.refresh_rounded),
onPressed: _isRefreshing ? null : _handleRefresh,
),

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