diff --git a/lib/src/routes/index.dart b/lib/src/routes/index.dart index 024e500..9ec44e9 100644 --- a/lib/src/routes/index.dart +++ b/lib/src/routes/index.dart @@ -1,13 +1,33 @@ import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; import 'package:furman_now/src/screens/events/index.dart'; import 'package:furman_now/src/screens/home/home_header.dart'; import 'package:furman_now/src/screens/home/index.dart'; import 'package:furman_now/src/screens/info/index.dart'; import 'package:furman_now/src/screens/map/index.dart'; +import 'package:furman_now/src/screens/map/map_category.dart'; +import 'package:furman_now/src/screens/map/map_home.dart'; import 'package:furman_now/src/screens/student_id/index.dart'; +import 'package:furman_now/src/utils/translucent_route.dart'; import '../layouts/main/index.dart'; +Route mapRouteBuilder(BuildContext context, Widget child, CustomPage page){ + return TranslucentRoute( + settings: page, + transitionDuration: const Duration(milliseconds: 200), + transitionBuilder: (context, animation, secondaryAnimation, child) => + FadeTransition( + opacity: animation, + child: FadeTransition( + opacity: secondaryAnimation.drive(Tween(begin: 1, end: 0)), + child: child, + ), + ), + pageBuilder: (context) => child, + ); +} + @MaterialAutoRouter( replaceInRouteName: 'Screen,Route', routes: [ @@ -25,7 +45,18 @@ import '../layouts/main/index.dart'; transitionsBuilder: TransitionsBuilders.fadeIn, ), ]), - AutoRoute(path: "map", page: MapScreen), + AutoRoute(path: "map", page: MapScreen, children: [ + CustomRoute( + path: "", + page: MapHomeScreen, + customRouteBuilder: mapRouteBuilder, + ), + CustomRoute( + path: "category/:id", + page: MapCategoryScreen, + customRouteBuilder: mapRouteBuilder, + ), + ]), AutoRoute(path: "events", page: EventsScreen), AutoRoute(path: "info", page: InfoScreen), ]), diff --git a/lib/src/routes/index.gr.dart b/lib/src/routes/index.gr.dart index d1bc88a..bbb2765 100644 --- a/lib/src/routes/index.gr.dart +++ b/lib/src/routes/index.gr.dart @@ -11,8 +11,8 @@ // ignore_for_file: type=lint // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i8; -import 'package:flutter/material.dart' as _i9; +import 'package:auto_route/auto_route.dart' as _i10; +import 'package:flutter/material.dart' as _i11; import '../layouts/main/index.dart' as _i1; import '../screens/events/index.dart' as _i4; @@ -20,76 +20,106 @@ import '../screens/home/home_header.dart' as _i6; import '../screens/home/index.dart' as _i2; import '../screens/info/index.dart' as _i5; import '../screens/map/index.dart' as _i3; +import '../screens/map/map_category.dart' as _i9; +import '../screens/map/map_home.dart' as _i8; +import '../screens/map/state.dart' as _i13; import '../screens/student_id/index.dart' as _i7; +import 'index.dart' as _i12; -class AppRouter extends _i8.RootStackRouter { - AppRouter([_i9.GlobalKey<_i9.NavigatorState>? navigatorKey]) +class AppRouter extends _i10.RootStackRouter { + AppRouter([_i11.GlobalKey<_i11.NavigatorState>? navigatorKey]) : super(navigatorKey); @override - final Map pagesMap = { + final Map pagesMap = { MainLayout.name: (routeData) { - return _i8.MaterialPageX( + return _i10.MaterialPageX( routeData: routeData, child: const _i1.MainLayout()); }, HomePageRouter.name: (routeData) { - return _i8.MaterialPageX( + return _i10.MaterialPageX( routeData: routeData, child: const _i2.HomeScreen()); }, MapRoute.name: (routeData) { - return _i8.MaterialPageX( + return _i10.MaterialPageX( routeData: routeData, child: const _i3.MapScreen()); }, EventsRoute.name: (routeData) { - return _i8.MaterialPageX( + return _i10.MaterialPageX( routeData: routeData, child: const _i4.EventsScreen()); }, InfoRoute.name: (routeData) { - return _i8.MaterialPageX( + return _i10.MaterialPageX( routeData: routeData, child: const _i5.InfoScreen()); }, HomeRoute.name: (routeData) { - return _i8.CustomPage( + return _i10.CustomPage( routeData: routeData, child: const _i6.HomePageHeader(), - transitionsBuilder: _i8.TransitionsBuilders.fadeIn, + transitionsBuilder: _i10.TransitionsBuilders.fadeIn, opaque: true, barrierDismissible: false); }, StudentIdRoute.name: (routeData) { - return _i8.CustomPage( + return _i10.CustomPage( routeData: routeData, child: const _i7.StudentIdScreen(), - transitionsBuilder: _i8.TransitionsBuilders.fadeIn, + transitionsBuilder: _i10.TransitionsBuilders.fadeIn, + opaque: true, + barrierDismissible: false); + }, + MapHomeRoute.name: (routeData) { + return _i10.CustomPage( + routeData: routeData, + child: const _i8.MapHomeScreen(), + customRouteBuilder: _i12.mapRouteBuilder, + opaque: true, + barrierDismissible: false); + }, + MapCategoryRoute.name: (routeData) { + final args = routeData.argsAs(); + return _i10.CustomPage( + routeData: routeData, + child: _i9.MapCategoryScreen(category: args.category, key: args.key), + customRouteBuilder: _i12.mapRouteBuilder, opaque: true, barrierDismissible: false); } }; @override - List<_i8.RouteConfig> get routes => [ - _i8.RouteConfig(MainLayout.name, path: '/', children: [ - _i8.RouteConfig(HomePageRouter.name, + List<_i10.RouteConfig> get routes => [ + _i10.RouteConfig(MainLayout.name, path: '/', children: [ + _i10.RouteConfig(HomePageRouter.name, path: 'home', parent: MainLayout.name, children: [ - _i8.RouteConfig(HomeRoute.name, + _i10.RouteConfig(HomeRoute.name, path: '', parent: HomePageRouter.name), - _i8.RouteConfig(StudentIdRoute.name, + _i10.RouteConfig(StudentIdRoute.name, path: 'student-id', parent: HomePageRouter.name) ]), - _i8.RouteConfig(MapRoute.name, path: 'map', parent: MainLayout.name), - _i8.RouteConfig(EventsRoute.name, + _i10.RouteConfig(MapRoute.name, + path: 'map', + parent: MainLayout.name, + children: [ + _i10.RouteConfig(MapHomeRoute.name, + path: '', parent: MapRoute.name), + _i10.RouteConfig(MapCategoryRoute.name, + path: 'category/:id', parent: MapRoute.name) + ]), + _i10.RouteConfig(EventsRoute.name, path: 'events', parent: MainLayout.name), - _i8.RouteConfig(InfoRoute.name, path: 'info', parent: MainLayout.name) + _i10.RouteConfig(InfoRoute.name, + path: 'info', parent: MainLayout.name) ]) ]; } /// generated route for /// [_i1.MainLayout] -class MainLayout extends _i8.PageRouteInfo { - const MainLayout({List<_i8.PageRouteInfo>? children}) +class MainLayout extends _i10.PageRouteInfo { + const MainLayout({List<_i10.PageRouteInfo>? children}) : super(MainLayout.name, path: '/', initialChildren: children); static const String name = 'MainLayout'; @@ -97,8 +127,8 @@ class MainLayout extends _i8.PageRouteInfo { /// generated route for /// [_i2.HomeScreen] -class HomePageRouter extends _i8.PageRouteInfo { - const HomePageRouter({List<_i8.PageRouteInfo>? children}) +class HomePageRouter extends _i10.PageRouteInfo { + const HomePageRouter({List<_i10.PageRouteInfo>? children}) : super(HomePageRouter.name, path: 'home', initialChildren: children); static const String name = 'HomePageRouter'; @@ -106,15 +136,16 @@ class HomePageRouter extends _i8.PageRouteInfo { /// generated route for /// [_i3.MapScreen] -class MapRoute extends _i8.PageRouteInfo { - const MapRoute() : super(MapRoute.name, path: 'map'); +class MapRoute extends _i10.PageRouteInfo { + const MapRoute({List<_i10.PageRouteInfo>? children}) + : super(MapRoute.name, path: 'map', initialChildren: children); static const String name = 'MapRoute'; } /// generated route for /// [_i4.EventsScreen] -class EventsRoute extends _i8.PageRouteInfo { +class EventsRoute extends _i10.PageRouteInfo { const EventsRoute() : super(EventsRoute.name, path: 'events'); static const String name = 'EventsRoute'; @@ -122,7 +153,7 @@ class EventsRoute extends _i8.PageRouteInfo { /// generated route for /// [_i5.InfoScreen] -class InfoRoute extends _i8.PageRouteInfo { +class InfoRoute extends _i10.PageRouteInfo { const InfoRoute() : super(InfoRoute.name, path: 'info'); static const String name = 'InfoRoute'; @@ -130,7 +161,7 @@ class InfoRoute extends _i8.PageRouteInfo { /// generated route for /// [_i6.HomePageHeader] -class HomeRoute extends _i8.PageRouteInfo { +class HomeRoute extends _i10.PageRouteInfo { const HomeRoute() : super(HomeRoute.name, path: ''); static const String name = 'HomeRoute'; @@ -138,8 +169,40 @@ class HomeRoute extends _i8.PageRouteInfo { /// generated route for /// [_i7.StudentIdScreen] -class StudentIdRoute extends _i8.PageRouteInfo { +class StudentIdRoute extends _i10.PageRouteInfo { const StudentIdRoute() : super(StudentIdRoute.name, path: 'student-id'); static const String name = 'StudentIdRoute'; } + +/// generated route for +/// [_i8.MapHomeScreen] +class MapHomeRoute extends _i10.PageRouteInfo { + const MapHomeRoute() : super(MapHomeRoute.name, path: ''); + + static const String name = 'MapHomeRoute'; +} + +/// generated route for +/// [_i9.MapCategoryScreen] +class MapCategoryRoute extends _i10.PageRouteInfo { + MapCategoryRoute({required _i13.MapCategory category, _i11.Key? key}) + : super(MapCategoryRoute.name, + path: 'category/:id', + args: MapCategoryRouteArgs(category: category, key: key)); + + static const String name = 'MapCategoryRoute'; +} + +class MapCategoryRouteArgs { + const MapCategoryRouteArgs({required this.category, this.key}); + + final _i13.MapCategory category; + + final _i11.Key? key; + + @override + String toString() { + return 'MapCategoryRouteArgs{category: $category, key: $key}'; + } +} diff --git a/lib/src/screens/map/index.dart b/lib/src/screens/map/index.dart index fafeeec..740df41 100644 --- a/lib/src/screens/map/index.dart +++ b/lib/src/screens/map/index.dart @@ -1,13 +1,10 @@ -import 'dart:async'; - +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:furman_now/src/utils/theme.dart'; -import 'package:furman_now/src/widgets/map/filter_chip.dart'; -import 'package:furman_now/src/widgets/map/rotate_compass.dart'; -import 'package:latlong2/latlong.dart'; +import 'package:furman_now/src/screens/map/state.dart'; +import 'package:provider/provider.dart'; +import 'package:transparent_pointer/transparent_pointer.dart'; + +import 'map_widget.dart'; class MapScreen extends StatefulWidget { const MapScreen({Key? key}) : super(key: key); @@ -18,206 +15,34 @@ class MapScreen extends StatefulWidget { class _MapScreenState extends State with SingleTickerProviderStateMixin { - final MapController _mapController = MapController(); - - late final AnimationController _animationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ); - - late CenterOnLocationUpdate _centerOnLocationUpdate; - late StreamController _centerCurrentLocationStreamController; - - var _rotation = 0.0; @override void initState() { super.initState(); - _mapController.mapEventStream.listen((event) { - if (event is MapEventRotate) { - setState(() { - _rotation = _mapController.rotation * (2 * pi) / 360; - }); - } - }); - _centerOnLocationUpdate = CenterOnLocationUpdate.always; - _centerCurrentLocationStreamController = StreamController(); - } - - @override - void dispose() { - super.dispose(); - _animationController.dispose(); - } - - void resetRotation() async { - // take the shortest rotation path - var end = _mapController.rotation > 180 ? 360.0 : 0.0; - var animation = Tween( - begin: _mapController.rotation, - end: end, - ).animate(CurvedAnimation( - parent: _animationController, - curve: Curves.easeInOut, - )); - - animationListener() { - _mapController.rotate(animation.value); - } - - animation.addListener(animationListener); - - await _animationController.forward(); - - animation.removeListener(animationListener); - _animationController.reset(); - - _mapController.rotate(0); } @override Widget build(BuildContext context) { - return Scaffold( - body: Container( - color: const Color(0xffb7acc9), - child: SafeArea( - top: false, - child: Stack( - children: [ - FlutterMap( - mapController: _mapController, - options: MapOptions( - center: LatLng(34.925926, -82.439397), - enableMultiFingerGestureRace: true, - rotationWinGestures: MultiFingerGesture.all, - pinchZoomThreshold: 0.2, - rotationThreshold: 8, - zoom: 15, - minZoom: 12, - maxZoom: 18, - plugins: [ - LocationMarkerPlugin( - centerCurrentLocationStream: - _centerCurrentLocationStreamController.stream, - centerOnLocationUpdate: _centerOnLocationUpdate, - ), - ], - onPositionChanged: (MapPosition position, bool hasGesture) { - if (hasGesture) { - setState( - () => _centerOnLocationUpdate = CenterOnLocationUpdate.never, - ); - } - }, - ), - layers: [ - TileLayerOptions( - urlTemplate: - "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - userAgentPackageName: 'edu.furman.now', - ), - LocationMarkerLayerOptions(), - ], - nonRotatedChildren: [ - AttributionWidget( - attributionBuilder: (BuildContext context) { - return const ColoredBox( - color: Color(0xCCFFFFFF), - child: Padding( - padding: EdgeInsets.all(3), - child: Text("©️ OpenStreetMap contributors"), - ), - ); - }, - ), + return ChangeNotifierProvider( + create: (context) => MapPageState(vsync: this), + builder: (context, _) => WillPopScope( + onWillPop: () async { + print("Will pop"); + return false; + }, + child: Scaffold( + body: Container( + color: const Color(0xffb7acc9), + child: SafeArea( + top: false, + // child: MapWidget(), + child: Stack( + children: const [ + MapWidget(), + TransparentPointer(transparent: true, child: AutoRouter()), ], ), - // Rotation reset fab - Positioned( - top: 12, - left: 0, - right: 0, - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Container( - width: double.infinity, - height: 50, - padding: const EdgeInsets.only(left: 10, right: 20), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(60)), - boxShadow: [ - BoxShadow( - color: Color(0x33000000), - blurRadius: 8, - ), - ], - ), - child: Stack( - children: [ - Align( - alignment: Alignment.centerLeft, - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - SvgPicture.asset("assets/images/bell-tower.svg", color: Theme.of(context).primaryColor, height: 32), - const SizedBox(width: 10), - Text( - "Search locations", - style: furmanTextStyle(TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Colors.grey.shade500, - )), - ), - ], - ), - ), - ], - ), - ), - ), - // const SizedBox(height: 12), - SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), - scrollDirection: Axis.horizontal, - child: Wrap( - spacing: 6, - children: const [ - MapFilterChip(icon: Icons.restaurant, text: "Restaurants"), - MapFilterChip(icon: Icons.train, text: "Transportation"), - MapFilterChip(icon: Icons.school, text: "Campus Buildings"), - ], - ), - ), - MapRotateCompass(rotation: _rotation, resetRotation: resetRotation), - ], - ), - ), - ), - Positioned( - right: 20, - bottom: 20, - child: FloatingActionButton( - onPressed: () { - // Automatically center the location marker on the map when location updated until user interact with the map. - setState( - () => _centerOnLocationUpdate = CenterOnLocationUpdate.always, - ); - // Center the location marker on the map and zoom the map to level 18. - _centerCurrentLocationStreamController.add(16); - }, - child: const Icon( - Icons.my_location, - color: Colors.white, - ), - ) - ) - ], + ), ), ), ), diff --git a/lib/src/screens/map/map_category.dart b/lib/src/screens/map/map_category.dart new file mode 100644 index 0000000..db0bb33 --- /dev/null +++ b/lib/src/screens/map/map_category.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; +import 'package:furman_now/src/screens/map/state.dart'; +import 'package:furman_now/src/widgets/map/map_header.dart'; +import 'package:furman_now/src/widgets/map/rotate_compass.dart'; +import 'package:provider/provider.dart'; + +import 'map_widget.dart'; + +class MapCategoryScreen extends StatelessWidget { + final MapCategory category; + + const MapCategoryScreen({ + required this.category, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, state, _) => Stack( + children: [ + Positioned( + top: 12, + left: 0, + right: 0, + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Hero( + tag: "header", + child: MapHeader( + activeCategory: category.name, + ), + ), + // const SizedBox(height: 12), + // SingleChildScrollView( + // padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + // scrollDirection: Axis.horizontal, + // child: Wrap( + // spacing: 6, + // children: [ + // // ...categories.map((category) => MapFilterChip( + // // icon: Icons.restaurant, + // // text: category.name, + // // callback: category.activator + // // )), + // // MapFilterChip( + // // icon: Icons.restaurant, + // // text: "Restaurants", + // // callback: () => null, + // // ), + // // MapFilterChip(icon: Icons.train, text: "Transportation"), + // // MapFilterChip(icon: Icons.school, text: "Campus Buildings"), + // ], + // ), + // ), + MapRotateCompass(rotation: state.rotation, resetRotation: state.resetRotation), + ], + ), + ), + ), + Positioned( + right: 20, + bottom: 20, + child: FloatingActionButton( + onPressed: () { + // Automatically center the location marker on the map when location updated until user interact with the map. + state.centerOnLocationUpdate = CenterOnLocationUpdate.always; + // Center the location marker on the map and zoom the map to level 16. + state.centerCurrentLocationStreamController.add(16); + }, + child: const Icon( + Icons.my_location, + color: Colors.white, + ), + ) + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/map/map_home.dart b/lib/src/screens/map/map_home.dart new file mode 100644 index 0000000..5a463f9 --- /dev/null +++ b/lib/src/screens/map/map_home.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; +import 'package:furman_now/src/screens/map/map_widget.dart'; +import 'package:furman_now/src/screens/map/state.dart'; +import 'package:furman_now/src/widgets/map/filter_chip.dart'; +import 'package:furman_now/src/widgets/map/map_header.dart'; +import 'package:furman_now/src/widgets/map/rotate_compass.dart'; +import 'package:provider/provider.dart'; + +class MapHomeScreen extends StatelessWidget { + const MapHomeScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, state, _) => Stack( + children: [ + Positioned( + top: 12, + left: 0, + right: 0, + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Hero( + tag: "header", + child: MapHeader() + ), + // const SizedBox(height: 12), + SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + scrollDirection: Axis.horizontal, + child: Wrap( + spacing: 6, + children: [ + ...state.categories.map((category) => MapFilterChip( + icon: Icons.restaurant, + text: category.name, + callback: category.activator + )), + // MapFilterChip( + // icon: Icons.restaurant, + // text: "Restaurants", + // callback: () => null, + // ), + // MapFilterChip(icon: Icons.train, text: "Transportation"), + // MapFilterChip(icon: Icons.school, text: "Campus Buildings"), + ], + ), + ), + MapRotateCompass(rotation: state.rotation, resetRotation: state.resetRotation), + ], + ), + ), + ), + Positioned( + right: 20, + bottom: 20, + child: FloatingActionButton( + onPressed: () { + // Automatically center the location marker on the map when location updated until user interact with the map. + state.centerOnLocationUpdate = CenterOnLocationUpdate.always; + // Center the location marker on the map and zoom the map to level 16. + state.centerCurrentLocationStreamController.add(16); + }, + child: const Icon( + Icons.my_location, + color: Colors.white, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/screens/map/map_search.dart b/lib/src/screens/map/map_search.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/src/screens/map/map_widget.dart b/lib/src/screens/map/map_widget.dart new file mode 100644 index 0000000..382ffdc --- /dev/null +++ b/lib/src/screens/map/map_widget.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; +import 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart'; +import 'package:furman_now/src/screens/map/state.dart'; +import 'package:furman_now/src/utils/theme.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:provider/provider.dart'; + +class MapWidget extends StatelessWidget { + const MapWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, state, _) => Scaffold( + body: Stack( + children: [ + FlutterMap( + mapController: state.mapController, + options: MapOptions( + center: LatLng(34.925926, -82.439397), + maxBounds: LatLngBounds( + LatLng(34.991937, -82.536251), + LatLng(34.813016, -82.328766), + ), + enableMultiFingerGestureRace: true, + rotationWinGestures: MultiFingerGesture.all, + pinchZoomThreshold: 0.2, + rotationThreshold: 8, + zoom: 15, + minZoom: 12, + maxZoom: 18, + plugins: [ + LocationMarkerPlugin( + centerCurrentLocationStream: + state.centerCurrentLocationStreamController.stream, + centerOnLocationUpdate: state.centerOnLocationUpdate, + ), + MarkerClusterPlugin(), + ], + onPositionChanged: (MapPosition position, bool hasGesture) { + if (hasGesture) { + state.centerOnLocationUpdate = CenterOnLocationUpdate.never; + } + }, + ), + layers: [ + TileLayerOptions( + urlTemplate: + "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + userAgentPackageName: 'edu.furman.now', + ), + LocationMarkerLayerOptions(), + MarkerClusterLayerOptions( + maxClusterRadius: 60, + size: const Size(40, 40), + fitBoundsOptions: const FitBoundsOptions( + padding: EdgeInsets.all(50), + ), + markers: state.markers, + polygonOptions: const PolygonOptions( + borderColor: Colors.blueAccent, + color: Colors.black12, + borderStrokeWidth: 3, + ), + builder: (context, markers) { + return FloatingActionButton( + onPressed: null, + child: Text(markers.length.toString()), + ); + }, + ), + ], + nonRotatedChildren: [ + AttributionWidget( + attributionBuilder: (BuildContext context) { + return ColoredBox( + color: const Color(0xCCFFFFFF), + child: Padding( + padding: const EdgeInsets.all(3), + child: Text( + "©️ OpenStreetMap contributors", + style: furmanTextStyle(const TextStyle( + fontSize: 10, + )), + ), + ), + ); + }, + ), + ], + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/screens/map/state.dart b/lib/src/screens/map/state.dart new file mode 100644 index 0000000..2b7ddb6 --- /dev/null +++ b/lib/src/screens/map/state.dart @@ -0,0 +1,115 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; +import 'package:flutter_remix/flutter_remix.dart'; +import 'package:furman_now/src/services/restaurants/restaurant_service.dart'; + +@immutable +class MapCategory { + final String name; + final IconData icon; + final Function activator; + + const MapCategory({ + required this.name, + required this.icon, + required this.activator, + }); +} + +class MapPageState extends ChangeNotifier { + MapPageState({ + required TickerProvider vsync + }): _animationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: vsync, + ) { + _initState(); + } + + void _initState() { + mapController.mapEventStream.listen((event) { + if (event is MapEventRotate) { + rotation = mapController.rotation * (2 * pi) / 360; + } + }); + + centerOnLocationUpdate = CenterOnLocationUpdate.always; + centerCurrentLocationStreamController = StreamController(); + } + + final MapController mapController = MapController(); + final AnimationController _animationController; + + late CenterOnLocationUpdate _centerOnLocationUpdate; + CenterOnLocationUpdate get centerOnLocationUpdate => _centerOnLocationUpdate; + set centerOnLocationUpdate (CenterOnLocationUpdate value) { + _centerOnLocationUpdate = value; + notifyListeners(); + } + + late StreamController centerCurrentLocationStreamController; + + var _rotation = 0.0; + double get rotation => _rotation; + set rotation (double value) { + _rotation = value; + notifyListeners(); + } + + List _markers = []; + List get markers => _markers; + set markers (List value) { + _markers = value; + notifyListeners(); + } + + void resetRotation() async { + // take the shortest rotation path + var end = mapController.rotation > 180 ? 360.0 : 0.0; + var animation = Tween( + begin: mapController.rotation, + end: end, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + )); + + animationListener() { + mapController.rotate(animation.value); + } + animation.addListener(animationListener); + + await _animationController.forward(); + + animation.removeListener(animationListener); + _animationController.reset(); + + mapController.rotate(0); + } + + late final List categories = [ + MapCategory( + name: "Restaurants", + icon: FlutterRemix.restaurant_line, + activator: showRestaurants, + ), + ]; + + Future showRestaurants() async { + var restaurants = await RestaurantService.fetchRestaurants(); + var newMarkers = restaurants.map((restaurant) => Marker( + point: restaurant.mapLocation, + width: 40, + height: 40, + builder: (context) => GestureDetector( + onTapDown: (e) => print("tapped"), + child: const FlutterLogo() + ), + )); + markers = newMarkers.toList(); + } +} diff --git a/lib/src/utils/translucent_route.dart b/lib/src/utils/translucent_route.dart new file mode 100644 index 0000000..fb1e12e --- /dev/null +++ b/lib/src/utils/translucent_route.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +class TranslucentRoute extends TransitionRoute { + final bool _opaque; + final Duration _transitionDuration; + final Widget Function( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child + ) _transitionBuilder; + final Widget Function(BuildContext) _pageBuilder; + + TranslucentRoute({ + opaque = true, + transitionDuration = const Duration(milliseconds: 300), + required Widget Function( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child + ) transitionBuilder, + required Widget Function(BuildContext) pageBuilder, + RouteSettings? settings, + }): + _opaque = opaque, + _transitionDuration = transitionDuration, + _transitionBuilder = transitionBuilder, + _pageBuilder = pageBuilder, + super(settings: settings); + + @override + Iterable createOverlayEntries() { + return [ + OverlayEntry( + builder: (context) => _transitionBuilder( + context, + animation!, + secondaryAnimation!, + _pageBuilder(context), + ), + ), + ]; + } + + @override + bool get opaque => _opaque; + + @override + Duration get transitionDuration => _transitionDuration; +} diff --git a/lib/src/widgets/map/filter_chip.dart b/lib/src/widgets/map/filter_chip.dart index 9cbb62f..9bb66bd 100644 --- a/lib/src/widgets/map/filter_chip.dart +++ b/lib/src/widgets/map/filter_chip.dart @@ -4,51 +4,56 @@ import 'package:furman_now/src/utils/theme.dart'; class MapFilterChip extends StatelessWidget { final IconData icon; final String text; + final Function callback; const MapFilterChip({ required this.icon, required this.text, + required this.callback, Key? key }) : super(key: key); @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.only( - top: 6, bottom: 6, left: 8, right: 12), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(100), - boxShadow: const [ - BoxShadow( - color: Color(0x33000000), - blurRadius: 4, - ), - ], - ), - child: RichText( - text: TextSpan( - children: [ - WidgetSpan( - child: Icon(icon, - size: 18, - color: Colors.grey.shade800), - alignment: PlaceholderAlignment.middle, - ), - const WidgetSpan(child: SizedBox(width: 6)), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Text( - text, - style: furmanTextStyle(TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.grey.shade800, - )), - ), + return GestureDetector( + onTapDown: (e) => callback(), + child: Container( + padding: const EdgeInsets.only( + top: 6, bottom: 6, left: 8, right: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(100), + boxShadow: const [ + BoxShadow( + color: Color(0x33000000), + blurRadius: 4, ), ], ), + child: RichText( + text: TextSpan( + children: [ + WidgetSpan( + child: Icon(icon, + size: 18, + color: Colors.grey.shade800), + alignment: PlaceholderAlignment.middle, + ), + const WidgetSpan(child: SizedBox(width: 6)), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Text( + text, + style: furmanTextStyle(TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.grey.shade800, + )), + ), + ), + ], + ), + ), ), ); } diff --git a/lib/src/widgets/map/map_header.dart b/lib/src/widgets/map/map_header.dart new file mode 100644 index 0000000..9887ee9 --- /dev/null +++ b/lib/src/widgets/map/map_header.dart @@ -0,0 +1,92 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_remix/flutter_remix.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:furman_now/src/routes/index.gr.dart'; +import 'package:furman_now/src/screens/map/state.dart'; +import 'package:furman_now/src/utils/theme.dart'; + +class MapHeader extends StatelessWidget { + final String? activeCategory; + + const MapHeader({ + this.activeCategory, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Container( + width: double.infinity, + height: 50, + padding: const EdgeInsets.only(left: 10, right: 20), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(60)), + boxShadow: [ + BoxShadow( + color: Color(0x33000000), + blurRadius: 8, + ), + ], + ), + child: Stack( + children: [ + Align( + alignment: Alignment.centerLeft, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + crossFadeState: activeCategory == null ? CrossFadeState.showFirst : CrossFadeState.showSecond, + firstChild: GestureDetector( + onTapDown: (e) => context.router.push(MapCategoryRoute( + category: MapCategory( + name: "Restaurants", + icon: FlutterRemix.restaurant_line, + activator: () => null, + ), + )), + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + SvgPicture.asset("assets/images/bell-tower.svg", color: Theme.of(context).primaryColor, height: 32), + const SizedBox(width: 10), + Text( + "Search locations", + style: furmanTextStyle(TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.grey.shade500, + )), + ), + ], + ), + ), + secondChild: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + GestureDetector( + onTapDown: (e) => context.router.navigateBack(), + child: Icon(FlutterRemix.arrow_left_line, size: 28, color: Colors.grey.shade800,), + ), + const SizedBox(width: 10), + if (activeCategory != null) + Text( + activeCategory!, + style: furmanTextStyle(TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.grey.shade800, + )), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 346209a..527d3ad 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -812,6 +812,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + transparent_pointer: + dependency: "direct main" + description: + name: transparent_pointer + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" tuple: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 73235d4..1d29082 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: weather: ^2.0.1 geolocator: ^9.0.1 flutter_map_marker_cluster: ^0.5.4 + transparent_pointer: ^1.0.0 dev_dependencies: flutter_test: