flutter_page_kit 1.0.0
flutter_page_kit: ^1.0.0 copied to clipboard
Page controllers, shells, and loading for Lemsa Flutter apps. Riverpod-free PageData mixins; AsyncView via a secondary export.
import 'package:flutter/material.dart';
import 'package:flutter_page_kit/flutter_page_kit.dart';
import 'package:lemsa_core_kit/lemsa_core_kit.dart';
void main() {
runApp(
MaterialApp(
scaffoldMessengerKey: _messengerKey,
home: DemoPage(
repo: FakeTaskRepo(),
notices: ExampleNotices(_messengerKey),
nav: ExampleNavigator(),
),
),
);
}
final GlobalKey<ScaffoldMessengerState> _messengerKey =
GlobalKey<ScaffoldMessengerState>();
/// Tiny ScaffoldMessenger stub (production impl → flutter_app_kit).
class ExampleNotices implements Notices {
ExampleNotices(this._key);
final GlobalKey<ScaffoldMessengerState> _key;
void _show(String message) {
if (message.isEmpty) {
return;
}
_key.currentState
?..hideCurrentSnackBar()
..showSnackBar(SnackBar(content: Text(message)));
}
@override
void info(String message) => _show(message);
@override
void warn(String message) => _show(message);
@override
void error(String message) => _show(message);
@override
void success(String message) => _show(message);
@override
void showFailure(AppFailure? failure) {
if (failure == null || failure is CancelledFailure) {
return;
}
// App would localize via failureText(failure); example shows type only.
error(failure.runtimeType.toString());
}
}
class ExampleNavigator implements PageNavigator {
@override
Future<R?> push<R>(Object route) async => null;
@override
void pop<R>([R? result]) {}
@override
void replaceAll(List<Object> routes) {}
@override
void replaceNamed(String path) {}
@override
bool get canPop => false;
}
class FakeTaskRepo {
Future<void> save(String title) async {
await Future<void>.delayed(const Duration(milliseconds: 200));
if (title == 'fail') {
throw const NetworkFailure();
}
}
}
class DemoPage extends StatefulWidget {
const DemoPage({
required this.repo,
required this.notices,
required this.nav,
super.key,
});
final FakeTaskRepo repo;
final Notices notices;
final PageNavigator nav;
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage>
with PageData<DemoPage>, _DemoData {
@override
FakeTaskRepo get repo => widget.repo;
@override
Notices get notices => widget.notices;
@override
PageNavigator get nav => widget.nav;
@override
Widget build(BuildContext context) {
return PageScope(
data: this,
child: FormPage(
title: 'Demo task',
actions: actions,
failure: failure,
child: Padding(
padding: const EdgeInsets.all(16),
child: TextField(
controller: title.controller,
enabled: !busy.of('save'),
decoration: InputDecoration(
labelText: 'Title',
helperText: 'Try "fail" to toast a NetworkFailure',
errorText: showFieldErrors ? title.errorCode : null,
),
),
),
),
);
}
}
mixin _DemoData on State<DemoPage>, PageData<DemoPage> {
FakeTaskRepo get repo;
Notices get notices;
PageNavigator get nav;
late final FieldText title = text();
@override
List<Validatable> get validated => [title];
List<PageAction> get actions => [
PageAction(
id: 'save',
label: 'Save',
busyKey: 'save',
isPrimary: true,
onPressed: submit,
),
];
Future<void> submit() => run(
key: 'save',
action: () async {
// Inline only — never notices.warn for invalid form.
if (!await validateForm()) {
return;
}
try {
await repo.save(title.value);
notices.success('Saved');
nav.pop<void>();
} on AppFailure catch (f) {
notices.showFailure(f);
rethrow;
}
},
);
}