Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/analyze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ jobs:
run: flutter pub get

- name: Analyze project source
run: flutter analyze
run: flutter analyze lib example test
2 changes: 1 addition & 1 deletion .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ jobs:
run: flutter pub get

- name: Verify formatting
run: dart format --output=none --set-exit-if-changed .
run: dart format --output=none --set-exit-if-changed lib example test
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

* `TabData`
* Added `listenable` and `textProvider` for reactive UI updates.
* Added `labelBuilder` to allow custom label widget creation instead of using the internally generated widget from `text`.
* `TabsAreaThemeData`
* Added `padding` property.
* Added `lastVisibleTabBehavior` to configure how the last remaining tab behaves when there is no space available.
* `LastVisibleTabBehavior`
* New enum with two modes: hide (moves the tab to the overflow menu) and shrink (keeps the tab visible by reducing its width).
Expand Down
2 changes: 1 addition & 1 deletion dev_workbench/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import 'package:flutter/material.dart';
import 'src/dev_workbench_app.dart';

void main() {
runApp(const DevWorkbenchApp());
runApp(DevWorkbenchApp());
}
69 changes: 0 additions & 69 deletions dev_workbench/lib/src/controlled_layout.dart

This file was deleted.

241 changes: 211 additions & 30 deletions dev_workbench/lib/src/dev_workbench_app.dart
Original file line number Diff line number Diff line change
@@ -1,69 +1,250 @@
import 'package:dev_workbench/src/scenario.dart';
import 'package:dev_workbench/src/scenario_list.dart';
import 'package:dev_workbench/src/scenario_widget_builder.dart';
import 'package:dev_workbench/src/scenarios/scenario_configurator.dart';
import 'package:dev_workbench/src/scenarios/scenario_screen.dart';
import 'package:dev_workbench/src/scenario_configurator.dart';
import 'package:dev_workbench/src/scenario_screen.dart';
import 'package:flutter/material.dart';

import 'controlled_layout.dart';

class DevWorkbenchApp extends StatelessWidget {
const DevWorkbenchApp({super.key});
DevWorkbenchApp({super.key});

final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();

@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: _scaffoldMessengerKey,
debugShowCheckedModeBanner: false,
home: const _HomePage(),
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
),
home: _HomePage(scaffoldMessengerKey: _scaffoldMessengerKey),
);
}
}

class _HomePage extends StatefulWidget {
const _HomePage();
const _HomePage({required this.scaffoldMessengerKey});

final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey;

@override
State<_HomePage> createState() => _HomePageState();
}

class _Key extends LocalKey {
const _Key({required this.scenario, required this.build});

final Scenario scenario;
final int build;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _Key &&
runtimeType == other.runtimeType &&
scenario == other.scenario &&
build == other.build;

@override
int get hashCode => Object.hash(scenario, build);
}

class _HomePageState extends State<_HomePage> {
Scenario _selectedScenario = Scenario.tabHeaderRow;
Scenario _selectedScenario = Scenario.theme;
int _build = 1;

@override
Widget build(BuildContext context) {
(ScenarioConfigurator? c, ScenarioScreen) scenario =
ScenarioWidgetBuilder.build(_selectedScenario);

List<Widget> rows = [
SizedBox(
width: 200,
child: ScenarioList(
selectedScenario: _selectedScenario,
onScenarioSelected: (scenario) {
(ScenarioConfigurator? c, ScenarioScreen) scenario = _selectedScenario
.builder
.call(scaffoldMessengerKey: widget.scaffoldMessengerKey);

List<Widget> rows = [];

final ScenarioConfigurator? configurator = scenario.$1;

rows.add(
_SideBar(
selectedScenario: _selectedScenario,
onScenarioSelected: (scenario) {
if (_selectedScenario != scenario) {
setState(() {
_selectedScenario = scenario;
});
},
),
}
},
onReset: () {
setState(() {
_build++;
});
},
configurator: configurator ?? Container(),
),
];

final ScenarioConfigurator? configurator = scenario.$1;
if (configurator != null) {
rows.add(configurator);
}
);

rows.add(
Expanded(
child: ControlledLayout(
key: ValueKey(_selectedScenario),
child: _ResizableLayout(
key: _Key(scenario: _selectedScenario, build: _build),
child: scenario.$2,
),
),
);

return Scaffold(
backgroundColor: Colors.white,
body: Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: rows),
);
}
}

class _ResizableLayout extends StatefulWidget {
final Widget child;

const _ResizableLayout({super.key, required this.child});

@override
State<_ResizableLayout> createState() => _ResizableLayoutState();
}

class _ResizableLayoutState extends State<_ResizableLayout> {
double _horizontalScale = 0.8;
double _verticalScale = 0.8;

@override
Widget build(BuildContext context) {
return Container(
color: Colors.blue.shade50,
child: Column(
children: [
Row(
children: [
Expanded(
child: Slider(
value: _horizontalScale,
onChanged: (v) => setState(() => _horizontalScale = v),
),
),
const SizedBox(width: 48),
],
),
Expanded(
child: Row(
children: [
Expanded(
child: Center(
child: FractionallySizedBox(
widthFactor: _horizontalScale,
heightFactor: _verticalScale,
child: Container(child: widget.child),
),
),
),
SizedBox(
width: 48,
child: RotatedBox(
quarterTurns: 1,
child: Slider(
value: _verticalScale,
onChanged: (v) => setState(() => _verticalScale = v),
),
),
),
],
),
),
],
),
);
}
}

class _SideBar extends StatelessWidget {
const _SideBar({
required this.selectedScenario,
required this.onScenarioSelected,
required this.onReset,
required this.configurator,
});

final Scenario selectedScenario;
final ValueChanged<Scenario> onScenarioSelected;
final VoidCallback onReset;
final Widget configurator;

@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(right: BorderSide(color: Colors.grey.shade600)),
),
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_ScenarioChooser(
selectedScenario: selectedScenario,
onScenarioSelected: onScenarioSelected,
),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.only(left: 16),
child: TextButton(onPressed: onReset, child: Text('Reset')),
),
),
Divider(),
configurator,
],
),
),
);
}
}

class _ScenarioChooser extends StatelessWidget {
const _ScenarioChooser({
required this.selectedScenario,
required this.onScenarioSelected,
});

final Scenario selectedScenario;
final ValueChanged<Scenario> onScenarioSelected;

@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16),
child: DropdownMenu<Scenario>(
initialSelection: selectedScenario,
requestFocusOnTap: false,
label: const Text('Scenario'),
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12),
isDense: true,
),
onSelected: (Scenario? newValue) {
if (newValue != null) {
onScenarioSelected(newValue);
}
},
dropdownMenuEntries: Scenario.values.map<DropdownMenuEntry<Scenario>>((
Scenario scenario,
) {
return DropdownMenuEntry<Scenario>(
value: scenario,
label: scenario.text,
style: MenuItemButton.styleFrom(
visualDensity: VisualDensity.compact,
),
);
}).toList(),
),
);
}
}
Loading
Loading