This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../features/mapflow/application/place_cubit.dart';
|
||||
import '../features/mapflow/data/mapflow_api.dart';
|
||||
import '../shared/location/current_location.dart';
|
||||
import 'router/app_router.dart';
|
||||
import 'theme/mapflow_theme.dart';
|
||||
|
||||
class MapflowApp extends StatefulWidget {
|
||||
const MapflowApp({super.key});
|
||||
|
||||
@override
|
||||
State<MapflowApp> createState() => _MapflowAppState();
|
||||
}
|
||||
|
||||
class _MapflowAppState extends State<MapflowApp> {
|
||||
late final PlaceCubit _placeCubit;
|
||||
late final GoRouter _router;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_placeCubit = PlaceCubit(api: MapflowApi(), location: CurrentLocation())
|
||||
..load();
|
||||
_router = createAppRouter();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_router.dispose();
|
||||
_placeCubit.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final shortcuts =
|
||||
Map<ShortcutActivator, Intent>.of(WidgetsApp.defaultShortcuts)
|
||||
..[const SingleActivator(LogicalKeyboardKey.tab)] =
|
||||
const NextFocusIntent()
|
||||
..[const SingleActivator(LogicalKeyboardKey.tab, shift: true)] =
|
||||
const PreviousFocusIntent();
|
||||
|
||||
return BlocProvider.value(
|
||||
value: _placeCubit,
|
||||
child: MaterialApp.router(
|
||||
title: 'MapFlow',
|
||||
debugShowCheckedModeBanner: false,
|
||||
routerConfig: _router,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
scrollbars: true,
|
||||
dragDevices: {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.trackpad,
|
||||
PointerDeviceKind.stylus,
|
||||
},
|
||||
),
|
||||
theme: MapflowTheme.light(),
|
||||
builder: (context, child) {
|
||||
return Shortcuts(
|
||||
shortcuts: shortcuts,
|
||||
child: FocusTraversalGroup(
|
||||
policy: ReadingOrderTraversalPolicy(),
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../../features/mapflow/presentation/admin_voice_experiences_screen.dart';
|
||||
import '../../features/mapflow/presentation/mapflow_shell.dart';
|
||||
|
||||
class AddExperienceArgs {
|
||||
const AddExperienceArgs({
|
||||
required this.coordinate,
|
||||
required this.hasTelegramAuth,
|
||||
});
|
||||
|
||||
final LatLng? coordinate;
|
||||
final bool hasTelegramAuth;
|
||||
}
|
||||
|
||||
GoRouter createAppRouter() {
|
||||
return GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const MapflowShell(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'experience/new',
|
||||
pageBuilder: (context, state) {
|
||||
final args = state.extra as AddExperienceArgs;
|
||||
return CustomTransitionPage<void>(
|
||||
fullscreenDialog: true,
|
||||
key: state.pageKey,
|
||||
child: AddExperienceFlow(
|
||||
coordinate: args.coordinate,
|
||||
hasTelegramAuth: args.hasTelegramAuth,
|
||||
),
|
||||
transitionsBuilder: (context, animation, _, child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 1),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: 'admin/reviews',
|
||||
builder: (context, state) => const AdminVoiceExperiencesScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import 'package:flex_color_scheme/flex_color_scheme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@immutable
|
||||
class MapflowThemeTokens extends ThemeExtension<MapflowThemeTokens> {
|
||||
const MapflowThemeTokens({
|
||||
required this.mapPanel,
|
||||
required this.mapPanelBorder,
|
||||
required this.mapShadow,
|
||||
required this.danger,
|
||||
required this.onDark,
|
||||
required this.panelRadius,
|
||||
});
|
||||
|
||||
final Color mapPanel;
|
||||
final Color mapPanelBorder;
|
||||
final Color mapShadow;
|
||||
final Color danger;
|
||||
final Color onDark;
|
||||
final double panelRadius;
|
||||
|
||||
@override
|
||||
MapflowThemeTokens copyWith({
|
||||
Color? mapPanel,
|
||||
Color? mapPanelBorder,
|
||||
Color? mapShadow,
|
||||
Color? danger,
|
||||
Color? onDark,
|
||||
double? panelRadius,
|
||||
}) {
|
||||
return MapflowThemeTokens(
|
||||
mapPanel: mapPanel ?? this.mapPanel,
|
||||
mapPanelBorder: mapPanelBorder ?? this.mapPanelBorder,
|
||||
mapShadow: mapShadow ?? this.mapShadow,
|
||||
danger: danger ?? this.danger,
|
||||
onDark: onDark ?? this.onDark,
|
||||
panelRadius: panelRadius ?? this.panelRadius,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
MapflowThemeTokens lerp(
|
||||
covariant ThemeExtension<MapflowThemeTokens>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! MapflowThemeTokens) {
|
||||
return this;
|
||||
}
|
||||
return MapflowThemeTokens(
|
||||
mapPanel: Color.lerp(mapPanel, other.mapPanel, t)!,
|
||||
mapPanelBorder: Color.lerp(mapPanelBorder, other.mapPanelBorder, t)!,
|
||||
mapShadow: Color.lerp(mapShadow, other.mapShadow, t)!,
|
||||
danger: Color.lerp(danger, other.danger, t)!,
|
||||
onDark: Color.lerp(onDark, other.onDark, t)!,
|
||||
panelRadius: lerpDouble(panelRadius, other.panelRadius, t),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MapflowTheme {
|
||||
const MapflowTheme._();
|
||||
|
||||
static const tokens = MapflowThemeTokens(
|
||||
mapPanel: Color(0xFFFFFBF5),
|
||||
mapPanelBorder: Color(0xFFE0D8CA),
|
||||
mapShadow: Color(0x33000000),
|
||||
danger: Color(0xFFE11D48),
|
||||
onDark: Colors.white,
|
||||
panelRadius: 8,
|
||||
);
|
||||
|
||||
static ThemeData light() {
|
||||
return FlexThemeData.light(
|
||||
useMaterial3: true,
|
||||
colors: const FlexSchemeColor(
|
||||
primary: Color(0xFF0F766E),
|
||||
primaryContainer: Color(0xFFBFE7DF),
|
||||
secondary: Color(0xFFE11D48),
|
||||
secondaryContainer: Color(0xFFFFD9E1),
|
||||
tertiary: Color(0xFF7C3AED),
|
||||
tertiaryContainer: Color(0xFFE9DDFF),
|
||||
appBarColor: Color(0xFFF7F3EA),
|
||||
error: Color(0xFFB3261E),
|
||||
),
|
||||
scaffoldBackground: const Color(0xFFF7F3EA),
|
||||
surface: tokens.mapPanel,
|
||||
subThemesData: const FlexSubThemesData(
|
||||
defaultRadius: 8,
|
||||
inputDecoratorRadius: 8,
|
||||
cardRadius: 8,
|
||||
chipRadius: 8,
|
||||
filledButtonRadius: 8,
|
||||
),
|
||||
fontFamily: 'SF Pro Display',
|
||||
extensions: const [tokens],
|
||||
).copyWith(
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: Color(0xFFF7F3EA),
|
||||
foregroundColor: Color(0xFF17211D),
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
elevation: 0,
|
||||
color: tokens.mapPanel,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(tokens.panelRadius),
|
||||
side: BorderSide(color: tokens.mapPanelBorder),
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
shape: StadiumBorder(side: BorderSide(color: tokens.mapPanelBorder)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double lerpDouble(double a, double b, double t) => a + (b - a) * t;
|
||||
|
||||
extension MapflowThemeContext on BuildContext {
|
||||
MapflowThemeTokens get mapflowTokens {
|
||||
return Theme.of(this).extension<MapflowThemeTokens>()!;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user