chore: bootstrap flutter reader app skeleton

This commit is contained in:
2026-03-23 15:57:38 +07:00
parent f5e7813548
commit 4f202936fa
150 changed files with 6278 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../app/router/route_names.dart';
class AppShell extends StatelessWidget {
const AppShell({super.key, required this.child});
final Widget child;
int _indexForLocation(String location) {
if (location.startsWith(RouteNames.search)) return 1;
if (location.startsWith(RouteNames.bookshelf)) return 2;
if (location.startsWith(RouteNames.genres)) return 3;
if (location.startsWith(RouteNames.profile)) return 4;
return 0;
}
@override
Widget build(BuildContext context) {
final location = GoRouterState.of(context).uri.path;
final selectedIndex = _indexForLocation(location);
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: (index) {
switch (index) {
case 0:
context.go(RouteNames.home);
case 1:
context.go(RouteNames.search);
case 2:
context.go(RouteNames.bookshelf);
case 3:
context.go(RouteNames.genres);
case 4:
context.go(RouteNames.profile);
}
},
destinations: const [
NavigationDestination(icon: Icon(Icons.home_outlined), label: 'Home'),
NavigationDestination(icon: Icon(Icons.search), label: 'Tim kiem'),
NavigationDestination(icon: Icon(Icons.bookmark_border), label: 'Tu sach'),
NavigationDestination(icon: Icon(Icons.category_outlined), label: 'The loai'),
NavigationDestination(icon: Icon(Icons.person_outline), label: 'Tai khoan'),
],
),
);
}
}
@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
class FeaturePlaceholder extends StatelessWidget {
const FeaturePlaceholder({
super.key,
required this.title,
required this.description,
this.actions = const <Widget>[],
});
final String title;
final String description;
final List<Widget> actions;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: Card(
elevation: 0,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: theme.textTheme.headlineSmall),
const SizedBox(height: 10),
Text(description, style: theme.textTheme.bodyLarge),
if (actions.isNotEmpty) ...[
const SizedBox(height: 18),
Wrap(spacing: 10, runSpacing: 10, children: actions),
],
],
),
),
),
),
),
);
}
}