Skip to content

Instantly share code, notes, and snippets.

@mono0926
Last active December 11, 2022 03:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mono0926/9b3bb2181523f9eaafd5c617387383b2 to your computer and use it in GitHub Desktop.
Save mono0926/9b3bb2181523f9eaafd5c617387383b2 to your computer and use it in GitHub Desktop.
Responsive Navigation
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(
const ProviderScope(
child: App(),
),
);
}
enum ResponsiveMode {
desktop,
mobile,
}
final responsiveModeProvider = StateProvider((ref) => ResponsiveMode.mobile);
class App extends ConsumerWidget {
const App({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp.router(
theme: ThemeData.from(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
),
).applyCommon(),
darkTheme: ThemeData.from(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.dark,
),
).applyCommon(),
routerConfig: ref.watch(router),
builder: (context, child) {
final mode = MediaQuery.of(context).size.width > 600
? ResponsiveMode.desktop
: ResponsiveMode.mobile;
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.watch(responsiveModeProvider.notifier).update((_) => mode);
});
return child!;
},
);
}
}
final navigatorKey = Provider((ref) => GlobalKey<NavigatorState>());
final router = Provider((ref) {
final isMobile = ref.watch(
responsiveModeProvider.select((mode) => mode == ResponsiveMode.mobile),
);
ref.listenSelf((previous, current) {
final previousLocation = previous?.location;
if (previousLocation != null) {
// TODO(mono): Check if the location exists in the new router
current.go(previousLocation);
}
});
return GoRouter(
debugLogDiagnostics: true,
navigatorKey: ref.watch(navigatorKey),
routes: [
GoRoute(
path: '/',
redirect: (context, state) => '/items',
),
...isMobile
? [
GoRoute(
path: '/items',
builder: (context, state) => const ListPage(),
routes: [
GoRoute(
path: ':id',
builder: (context, state) {
return DetailPage(id: state.params['id']!);
},
),
],
)
]
: [
GoRoute(path: '/items', redirect: (_, __) => '/items/0'),
GoRoute(
path: '/items/:id',
builder: (context, state) {
return ListDetailPage(id: state.params['id']!);
},
),
]
],
);
});
class ListPage extends StatelessWidget {
const ListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('List'),
),
body: ListView(
children: List.generate(
10,
(index) => ListTile(
title: Text('Item $index'),
onTap: () => GoRouter.of(context).go('/items/$index'),
),
),
),
);
}
}
class DetailPage extends StatelessWidget {
const DetailPage({
super.key,
required this.id,
});
final String id;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Item $id'),
),
body: Center(child: Text('Item $id')),
);
}
}
class ListDetailPage extends StatelessWidget {
const ListDetailPage({
super.key,
required this.id,
});
final String id;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('ListDetail'),
),
body: Row(
children: [
Expanded(
child: ListView(
children: List.generate(
10,
(index) => ListTile(
title: Text('Item $index'),
tileColor: '$index' == id
? colorScheme.secondary.withOpacity(0.1)
: null,
onTap: () {
GoRouter.of(context).go('/items/$index');
},
),
),
),
),
const VerticalDivider(width: 0),
Expanded(
flex: 2,
child: Center(
child: Text('Item $id'),
),
),
],
),
);
}
}
extension on ThemeData {
ThemeData applyCommon() => copyWith(
appBarTheme: appBarTheme.copyWith(elevation: 4),
useMaterial3: true,
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment