Skip to content

Commit 7abe220

Browse files
committed
update
1 parent 7d71dcf commit 7abe220

42 files changed

Lines changed: 2807 additions & 363 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

analysis_options.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# For dev-cetera.com
22
# Version: Flutter-1
33

4-
include: package:flutter_lints/recommended.yaml
4+
include: package:flutter_lints/flutter.yaml
55

66
## ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
77

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// The absolute minimum a df_screen-based screen needs:
2+
// - a Screen subclass (the StatefulWidget)
3+
// - a ScreenState subclass (the rendering layer)
4+
// - a ScreenController subclass (where business logic lives)
5+
//
6+
// No adaptive layouts, no side panels, just lifecycle wiring.
7+
8+
import 'package:df_screen/df_screen.dart';
9+
import 'package:flutter/material.dart';
10+
11+
void main() {
12+
runApp(const MaterialApp(home: MinimalScreen()));
13+
}
14+
15+
base class MinimalScreen extends Screen {
16+
const MinimalScreen({super.key});
17+
18+
@override
19+
State createState() => _MinimalScreenState();
20+
21+
@override
22+
ScreenController createController(Screen screen, ScreenState state) {
23+
return MinimalController(screen, state);
24+
}
25+
}
26+
27+
base class MinimalController extends ScreenController {
28+
MinimalController(super.superScreen, super.superState);
29+
30+
int taps = 0;
31+
32+
void onTap() {
33+
taps++;
34+
// ScreenState exposes rebuildScreen() for controllers to request a
35+
// rebuild without touching protected State APIs.
36+
superState!.rebuildScreen();
37+
}
38+
}
39+
40+
base class _MinimalScreenState extends ScreenState<MinimalScreen, MinimalController> {
41+
@override
42+
Widget buildWidget(BuildContext context) {
43+
return Scaffold(
44+
body: Center(
45+
child: Column(
46+
mainAxisSize: MainAxisSize.min,
47+
children: [
48+
Text(
49+
'Taps: ${c.taps}',
50+
style: Theme.of(context).textTheme.headlineMedium,
51+
),
52+
const SizedBox(height: 16),
53+
ElevatedButton(onPressed: c.onTap, child: const Text('Tap me')),
54+
],
55+
),
56+
),
57+
);
58+
}
59+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// DefaultAdaptiveScreenState gives you the opinionated set of mixins out of
2+
// the box:
3+
// - DefaultScrollableAlignScreenMixin (bouncing scroll, dismiss-on-drag)
4+
// - DefaultPaddingScreenMixin (28.sc top/sides, 112.sc bottom)
5+
// - MobileFrameWideLayoutScreenMixin (mobile-shaped frame on desktop)
6+
// - RotateIconHorizontalMobileLayoutScreenMixin (asks the user to rotate)
7+
//
8+
// You override the same hooks as on AdaptiveScreenState; the mixins just
9+
// supply richer defaults so you can implement `body()` and ship.
10+
11+
import 'package:df_screen/df_screen.dart';
12+
import 'package:flutter/material.dart';
13+
14+
void main() {
15+
runApp(const MaterialApp(home: OpinionatedScreen()));
16+
}
17+
18+
base class OpinionatedScreen extends Screen {
19+
const OpinionatedScreen({super.key});
20+
21+
@override
22+
State createState() => _State();
23+
}
24+
25+
base class _State extends DefaultAdaptiveScreenState<OpinionatedScreen, ScreenController> {
26+
// Replace the default 28.sc / 112.sc padding without forking the mixin.
27+
@override
28+
EdgeInsets get defaultPaddingInsets => const EdgeInsets.symmetric(
29+
horizontal: 16,
30+
vertical: 24,
31+
);
32+
33+
@override
34+
Widget body(BuildContext context) {
35+
// List long enough that the scrollable-align mixin shows real motion.
36+
return ListView.builder(
37+
controller: bodyScrollController,
38+
itemCount: 30,
39+
itemBuilder: (context, i) {
40+
return Card(
41+
child: ListTile(
42+
leading: CircleAvatar(child: Text('$i')),
43+
title: Text('Opinionated item $i'),
44+
subtitle: const Text('Scrolls inside the default scrollable mixin.'),
45+
),
46+
);
47+
},
48+
);
49+
}
50+
51+
@override
52+
PreferredSizeWidget topSide(BuildContext context, double topInsets) {
53+
return PreferredSize(
54+
preferredSize: Size.fromHeight(56 + topInsets),
55+
child: AppBar(
56+
title: const Text('DefaultAdaptiveScreenState'),
57+
backgroundColor: Theme.of(context).colorScheme.surface,
58+
),
59+
);
60+
}
61+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Two ways to retune adaptive layout:
2+
// 1. Set LayoutBreakpoints.global before runApp() to retune every screen.
3+
// 2. Override `layoutBreakpoints` and/or `layoutResolver` on a single
4+
// screen for hyper-local tweaks.
5+
6+
import 'package:df_screen/df_screen.dart';
7+
import 'package:flutter/material.dart';
8+
9+
void main() {
10+
// Globally: anything narrower than 720px is considered "mobile-sized".
11+
LayoutBreakpoints.global = const LayoutBreakpoints(
12+
mobileMaxShortestSide: 720,
13+
);
14+
runApp(const MaterialApp(home: TunedScreen()));
15+
}
16+
17+
base class TunedScreen extends Screen {
18+
const TunedScreen({super.key});
19+
@override
20+
State createState() => _State();
21+
}
22+
23+
base class _State extends AdaptiveScreenState<TunedScreen, ScreenController> {
24+
// Per-screen override: this screen treats anything narrower than 1000px
25+
// as NARROW, regardless of platform.
26+
@override
27+
LayoutResolver get layoutResolver => (context, size) {
28+
if (size.width >= 1200) return AppLayout.WIDE;
29+
if (size.width >= 1000) return AppLayout.NARROW;
30+
return AppLayout.MOBILE;
31+
};
32+
33+
@override
34+
Widget body(BuildContext context) {
35+
// Show what the resolver picked.
36+
final layout = AppLayout.of(context);
37+
return Center(
38+
child: Text(
39+
'Current layout (default resolver): $layout',
40+
style: Theme.of(context).textTheme.titleLarge,
41+
),
42+
);
43+
}
44+
45+
@override
46+
Widget wideBody(BuildContext context) {
47+
return _layoutLabel(context, 'WIDE — three-column desktop UI here');
48+
}
49+
50+
@override
51+
Widget narrowBody(BuildContext context) {
52+
return _layoutLabel(context, 'NARROW — two-column tablet UI here');
53+
}
54+
55+
@override
56+
Widget mobileBody(BuildContext context) {
57+
return _layoutLabel(context, 'MOBILE — single column UI here');
58+
}
59+
60+
Widget _layoutLabel(BuildContext context, String text) {
61+
return Container(
62+
alignment: Alignment.center,
63+
padding: const EdgeInsets.all(48),
64+
child: Text(
65+
text,
66+
textAlign: TextAlign.center,
67+
style: Theme.of(context).textTheme.headlineMedium,
68+
),
69+
);
70+
}
71+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Demonstrates the controller cache: setting a positive `controllerTimeout`
2+
// keeps the controller (and its state) alive across short navigations away
3+
// from the screen. Navigating back within the timeout window reuses the
4+
// same controller — the counter resumes where it was left.
5+
//
6+
// Try: tap "increment", tap "leave", tap "back" — the counter is preserved.
7+
8+
import 'package:df_screen/df_screen.dart';
9+
import 'package:flutter/material.dart';
10+
11+
void main() {
12+
runApp(const MaterialApp(home: _Host()));
13+
}
14+
15+
class _Host extends StatefulWidget {
16+
const _Host();
17+
@override
18+
State<_Host> createState() => _HostState();
19+
}
20+
21+
class _HostState extends State<_Host> {
22+
bool showCounter = true;
23+
24+
@override
25+
Widget build(BuildContext context) {
26+
return Scaffold(
27+
appBar: AppBar(title: const Text('Controller cache demo')),
28+
body: showCounter
29+
? const CounterScreen(
30+
key: ValueKey('counter'),
31+
controllerTimeout: Duration(minutes: 5),
32+
)
33+
: Center(
34+
child: ElevatedButton(
35+
onPressed: () => setState(() => showCounter = true),
36+
child: const Text('Back to counter'),
37+
),
38+
),
39+
bottomNavigationBar: SafeArea(
40+
child: Padding(
41+
padding: const EdgeInsets.all(8),
42+
child: ElevatedButton(
43+
onPressed: () => setState(() => showCounter = false),
44+
child: const Text('Leave the screen'),
45+
),
46+
),
47+
),
48+
);
49+
}
50+
}
51+
52+
base class CounterScreen extends Screen {
53+
const CounterScreen({super.key, super.controllerTimeout});
54+
55+
@override
56+
State createState() => _CounterScreenState();
57+
58+
@override
59+
ScreenController createController(Screen screen, ScreenState state) {
60+
return CounterController(screen, state);
61+
}
62+
}
63+
64+
base class CounterController extends ScreenController {
65+
CounterController(super.superScreen, super.superState);
66+
67+
// This survives navigating away + back if controllerTimeout > 0.
68+
int count = 0;
69+
70+
void increment() {
71+
count++;
72+
superState!.rebuildScreen();
73+
}
74+
}
75+
76+
base class _CounterScreenState
77+
extends ScreenState<CounterScreen, CounterController> {
78+
@override
79+
Widget buildWidget(BuildContext context) {
80+
return Center(
81+
child: Column(
82+
mainAxisSize: MainAxisSize.min,
83+
children: [
84+
Text('Count: ${c.count}',
85+
style: Theme.of(context).textTheme.headlineMedium,),
86+
const SizedBox(height: 16),
87+
ElevatedButton(onPressed: c.increment, child: const Text('Increment')),
88+
],
89+
),
90+
);
91+
}
92+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Demonstrates how to gate UI on async work in a controller.
2+
// - `initController()` is awaited internally by the framework
3+
// - `ScreenController.ready` is the Future that completes when init finishes
4+
// - errors thrown from initController surface through `ready`
5+
6+
import 'package:df_screen/df_screen.dart';
7+
import 'package:flutter/material.dart';
8+
9+
void main() {
10+
runApp(const MaterialApp(home: AsyncInitScreen()));
11+
}
12+
13+
base class AsyncInitScreen extends Screen {
14+
const AsyncInitScreen({super.key});
15+
16+
@override
17+
State createState() => _State();
18+
19+
@override
20+
ScreenController createController(Screen screen, ScreenState state) {
21+
return _AsyncController(screen, state);
22+
}
23+
}
24+
25+
base class _AsyncController extends ScreenController {
26+
_AsyncController(super.superScreen, super.superState);
27+
28+
List<String> items = const [];
29+
30+
@override
31+
Future<void> initController() async {
32+
await super.initController();
33+
// Simulate a slow fetch.
34+
await Future<void>.delayed(const Duration(seconds: 1));
35+
items = const ['Alpha', 'Beta', 'Gamma'];
36+
}
37+
}
38+
39+
base class _State extends ScreenState<AsyncInitScreen, _AsyncController> {
40+
@override
41+
Widget buildWidget(BuildContext context) {
42+
return Scaffold(
43+
appBar: AppBar(title: const Text('Async controller init')),
44+
body: FutureBuilder<void>(
45+
future: c.ready,
46+
builder: (context, snapshot) {
47+
if (snapshot.connectionState != ConnectionState.done) {
48+
return const Center(child: CircularProgressIndicator());
49+
}
50+
if (snapshot.hasError) {
51+
return Center(child: Text('Failed to load: ${snapshot.error}'));
52+
}
53+
return ListView(
54+
children: [for (final item in c.items) ListTile(title: Text(item))],
55+
);
56+
},
57+
),
58+
);
59+
}
60+
}

0 commit comments

Comments
 (0)