Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion modules/ensemble/lib/layout/tab/scrollable_tab_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,19 @@ class ScrollableTabBarState extends BaseTabBarState {
}

void _initializeTabController() {
final tabLength =
effectiveTabControllerLength(widget.controller.items.length);
tabController = TabController(
length: widget.controller.items.length,
length: tabLength,
vsync: this,
);
}

void _reinitializeTabController() {
tabController.dispose();
if (widget._controller.selectedIndex >= widget._controller.items.length) {
widget._controller.selectedIndex = 0;
}
_initializeTabController();
}

Expand Down
9 changes: 9 additions & 0 deletions modules/ensemble/lib/layout/tab/tab_bar_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ import 'package:ensemble/layout/tab_bar.dart';
import 'package:ensemble/util/utils.dart';
import 'package:ensemble/widget/helpers/controllers.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:yaml/yaml.dart';

/// [TabController] requires `length >= 1`. When conditional `visible` bindings
/// hide every tab, the UI shows nothing but the controller still needs a valid length.
@visibleForTesting
int effectiveTabControllerLength(int visibleTabCount) {
assert(visibleTabCount >= 0);
return visibleTabCount == 0 ? 1 : visibleTabCount;
}

// the Controller for the TabBar
class TabBarController extends BoxController {
String? tabPosition;
Expand Down
11 changes: 8 additions & 3 deletions modules/ensemble/lib/layout/tab_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,17 @@ class TabBarState extends BaseTabBarState {
if (widget.controller.useIndexedTab) {
_cache = List<Widget?>.filled(widget.controller.items.length, null);
}
final tabLength =
effectiveTabControllerLength(widget.controller.items.length);
tabController = TabController(
initialIndex: safeIndex,
length: widget.controller.items.length,
initialIndex:
widget.controller.items.isEmpty ? 0 : safeIndex.clamp(0, tabLength - 1),
length: tabLength,
vsync: this,
);
tabController.addListener(notifyListener);
if (widget.controller.items.isNotEmpty) {
tabController.addListener(notifyListener);
}
}

int _getValidInitialIndex() {
Expand Down
15 changes: 15 additions & 0 deletions modules/ensemble/test/effective_tab_controller_length_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:ensemble/layout/tab/tab_bar_controller.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('effectiveTabControllerLength', () {
test('uses placeholder length 1 when no tabs are visible', () {
expect(effectiveTabControllerLength(0), 1);
});

test('matches visible tab count when at least one tab is visible', () {
expect(effectiveTabControllerLength(1), 1);
expect(effectiveTabControllerLength(4), 4);
});
});
}
Loading