Compare commits
1 Commits
main
...
4256293d10
| Author | SHA1 | Date | |
|---|---|---|---|
| 4256293d10 |
@@ -35,11 +35,4 @@
|
|||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
|
||||||
<queries>
|
|
||||||
<intent>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<data android:scheme="tel" />
|
|
||||||
</intent>
|
|
||||||
</queries>
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>11.0</string>
|
<string>9.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
# platform :ios, '11.0'
|
# platform :ios, '9.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
@@ -1,64 +1,34 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- external_app_launcher (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_compass (0.0.1):
|
- flutter_compass (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_inappwebview (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- flutter_inappwebview/Core (= 0.0.1)
|
|
||||||
- OrderedSet (~> 5.0)
|
|
||||||
- flutter_inappwebview/Core (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- OrderedSet (~> 5.0)
|
|
||||||
- geolocator_apple (1.2.0):
|
- geolocator_apple (1.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- OrderedSet (5.0.0)
|
- path_provider_ios (0.0.1):
|
||||||
- path_provider_foundation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- url_launcher_ios (0.0.1):
|
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- external_app_launcher (from `.symlinks/plugins/external_app_launcher/ios`)
|
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_compass (from `.symlinks/plugins/flutter_compass/ios`)
|
- flutter_compass (from `.symlinks/plugins/flutter_compass/ios`)
|
||||||
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
|
|
||||||
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`)
|
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
|
||||||
|
|
||||||
SPEC REPOS:
|
|
||||||
trunk:
|
|
||||||
- OrderedSet
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
external_app_launcher:
|
|
||||||
:path: ".symlinks/plugins/external_app_launcher/ios"
|
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_compass:
|
flutter_compass:
|
||||||
:path: ".symlinks/plugins/flutter_compass/ios"
|
:path: ".symlinks/plugins/flutter_compass/ios"
|
||||||
flutter_inappwebview:
|
|
||||||
:path: ".symlinks/plugins/flutter_inappwebview/ios"
|
|
||||||
geolocator_apple:
|
geolocator_apple:
|
||||||
:path: ".symlinks/plugins/geolocator_apple/ios"
|
:path: ".symlinks/plugins/geolocator_apple/ios"
|
||||||
path_provider_foundation:
|
path_provider_ios:
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||||
url_launcher_ios:
|
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
external_app_launcher: ad55ac844aa21f2d2197d7cec58ff0d0dc40bbc0
|
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
|
||||||
flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855
|
flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855
|
||||||
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
|
|
||||||
geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401
|
geolocator_apple: cc556e6844d508c95df1e87e3ea6fa4e58c50401
|
||||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
|
||||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
|
||||||
|
|
||||||
COCOAPODS: 1.12.1
|
COCOAPODS: 1.11.3
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 50;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1300;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
@@ -200,12 +200,10 @@
|
|||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
|
||||||
);
|
);
|
||||||
name = "Thin Binary";
|
name = "Thin Binary";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
@@ -238,7 +236,6 @@
|
|||||||
};
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
@@ -343,7 +340,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -420,7 +417,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -469,7 +466,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1300"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@@ -45,13 +45,5 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
|
||||||
<string>Your location will be used to provide weather and map data.</string>
|
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>tel</string>
|
|
||||||
</array>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:furman_now/src/utils/conditional_parent_widget.dart';
|
|
||||||
import 'package:furman_now/src/utils/theme.dart';
|
|
||||||
import 'package:furman_now/src/widgets/scroll_view_height.dart';
|
|
||||||
import 'package:transparent_pointer/transparent_pointer.dart';
|
|
||||||
|
|
||||||
class AppPageLayout extends StatefulWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final String title;
|
|
||||||
final Color? backgroundColor;
|
|
||||||
final Widget content;
|
|
||||||
final void Function()? iconTapAction;
|
|
||||||
final bool darkStatusBar;
|
|
||||||
|
|
||||||
const AppPageLayout({
|
|
||||||
required this.title,
|
|
||||||
required this.icon,
|
|
||||||
this.backgroundColor,
|
|
||||||
required this.content,
|
|
||||||
this.iconTapAction,
|
|
||||||
this.darkStatusBar = true,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<AppPageLayout> createState() => _AppPageLayoutState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AppPageLayoutState extends State<AppPageLayout> {
|
|
||||||
final ScrollController _controller = ScrollController();
|
|
||||||
double overscrollBoxHeight = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
_controller.addListener(updateScrollPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateScrollPosition() {
|
|
||||||
if (_controller.position.pixels > _controller.position.maxScrollExtent) {
|
|
||||||
setState(() {
|
|
||||||
overscrollBoxHeight =
|
|
||||||
_controller.position.pixels - _controller.position.maxScrollExtent;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (overscrollBoxHeight != 0) {
|
|
||||||
setState(() {
|
|
||||||
overscrollBoxHeight = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
body: AnnotatedRegion<SystemUiOverlayStyle>(
|
|
||||||
value: constructOverlayStyle(dark: widget.darkStatusBar),
|
|
||||||
child: Container(
|
|
||||||
color: widget.backgroundColor ?? Colors.grey[100],
|
|
||||||
child: SafeArea(
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.loose,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: double.infinity,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
|
||||||
width: double.infinity,
|
|
||||||
height: 100,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Wrap(
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
children: [
|
|
||||||
ConditionalParentWidget(
|
|
||||||
condition: widget.iconTapAction != null,
|
|
||||||
conditionalBuilder: (child) => GestureDetector(
|
|
||||||
onTap: widget.iconTapAction!,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
widget.icon,
|
|
||||||
size: 35,
|
|
||||||
color: Colors.grey[700]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
widget.title,
|
|
||||||
style: furmanTextStyle(TextStyle(
|
|
||||||
color: Colors.grey[900],
|
|
||||||
fontSize: 28, fontWeight: FontWeight.w700
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// makes the overscroll color at the bottom match
|
|
||||||
// that of the navbar
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: Container(
|
|
||||||
height: 30 + overscrollBoxHeight,
|
|
||||||
color: Colors.grey.shade50,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TransparentPointer(
|
|
||||||
child: ScrollViewWithHeight(
|
|
||||||
controller: _controller,
|
|
||||||
child: Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
|
||||||
),
|
|
||||||
// padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
|
|
||||||
margin: const EdgeInsets.only(top: 100),
|
|
||||||
width: double.infinity,
|
|
||||||
child: widget.content,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,14 +35,13 @@ class _MainLayoutState extends State<MainLayout> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AutoTabsScaffold(
|
return AutoTabsScaffold(
|
||||||
animationDuration: const Duration(milliseconds: 150),
|
|
||||||
routes: const [
|
routes: const [
|
||||||
HomePageRouter(children: [
|
HomePageRouter(children: [
|
||||||
HomeRoute(),
|
HomeRoute(),
|
||||||
]),
|
]),
|
||||||
MapRoute(),
|
MapRoute(),
|
||||||
EventsRoute(),
|
EventsRoute(),
|
||||||
InfoPageRouter(children: [InfoRoute()]),
|
InfoRoute(),
|
||||||
],
|
],
|
||||||
bottomNavigationBuilder: (_, tabsRouter) {
|
bottomNavigationBuilder: (_, tabsRouter) {
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
@@ -65,10 +64,8 @@ class _MainLayoutState extends State<MainLayout> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 20),
|
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
|
||||||
child: SalomonBottomBar(
|
child: SalomonBottomBar(
|
||||||
itemPadding:
|
|
||||||
const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
|
||||||
items: <SalomonBottomBarItem>[
|
items: <SalomonBottomBarItem>[
|
||||||
SalomonBottomBarItem(
|
SalomonBottomBarItem(
|
||||||
icon: const Icon(FlutterRemix.home_line),
|
icon: const Icon(FlutterRemix.home_line),
|
||||||
@@ -90,13 +87,7 @@ class _MainLayoutState extends State<MainLayout> {
|
|||||||
currentIndex: tabsRouter.activeIndex,
|
currentIndex: tabsRouter.activeIndex,
|
||||||
selectedItemColor: Theme.of(context).primaryColor,
|
selectedItemColor: Theme.of(context).primaryColor,
|
||||||
unselectedItemColor: Colors.grey[600],
|
unselectedItemColor: Colors.grey[600],
|
||||||
onTap: (index) {
|
onTap: tabsRouter.setActiveIndex,
|
||||||
if (tabsRouter.activeIndex == index) {
|
|
||||||
// tabs
|
|
||||||
} else {
|
|
||||||
tabsRouter.setActiveIndex(index);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,80 +1,39 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
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/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/home/index.dart';
|
||||||
import 'package:furman_now/src/screens/info/contacts.dart';
|
|
||||||
import 'package:furman_now/src/screens/info/health_safety.dart';
|
|
||||||
import 'package:furman_now/src/screens/info/hours.dart';
|
|
||||||
import 'package:furman_now/src/screens/info/index.dart';
|
import 'package:furman_now/src/screens/info/index.dart';
|
||||||
import 'package:furman_now/src/screens/settings/settings.dart';
|
|
||||||
import 'package:furman_now/src/screens/map/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/screens/student_id/index.dart';
|
||||||
import 'package:furman_now/src/utils/hero_empty_router_page.dart';
|
import 'package:furman_now/src/utils/hero_empty_router_page.dart';
|
||||||
import 'package:furman_now/src/utils/translucent_route.dart';
|
|
||||||
|
|
||||||
import '../layouts/main/index.dart';
|
import '../layouts/main/index.dart';
|
||||||
|
|
||||||
Route<T> mapRouteBuilder<T>(BuildContext context, Widget child, CustomPage<T> page) {
|
|
||||||
return TranslucentRoute(
|
|
||||||
settings: page,
|
|
||||||
transitionDuration: const Duration(milliseconds: 200),
|
|
||||||
transitionBuilder: (context, animation, secondaryAnimation, child) =>
|
|
||||||
FadeTransition(
|
|
||||||
opacity: animation,
|
|
||||||
child: FadeTransition(
|
|
||||||
opacity: secondaryAnimation.drive(Tween<double>(begin: 1, end: 0)),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
pageBuilder: (context, _, __) => child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MaterialAutoRouter(
|
@MaterialAutoRouter(
|
||||||
replaceInRouteName: 'Screen,Route',
|
replaceInRouteName: 'Screen,Route',
|
||||||
routes: <AutoRoute>[
|
routes: <AutoRoute>[
|
||||||
AutoRoute(path: "/", page: MainLayout, children: [
|
AutoRoute(path: "/", page: MainLayout, children: [
|
||||||
AutoRoute(
|
AutoRoute(path: "home", name: "HomePageRouter", page: HeroEmptyRouterPage, children: [
|
||||||
path: "home",
|
|
||||||
name: "HomePageRouter",
|
|
||||||
page: HomeScreenProvider,
|
|
||||||
children: [
|
|
||||||
CustomRoute(
|
CustomRoute(
|
||||||
path: "",
|
path: "",
|
||||||
page: HomePageHeader,
|
page: HomeScreen,
|
||||||
name: "HomeRoute",
|
transitionsBuilder: TransitionsBuilders.noTransition,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
|
||||||
),
|
),
|
||||||
CustomRoute(
|
CustomRoute(
|
||||||
path: "student-id",
|
path: "student-id",
|
||||||
page: StudentIdScreen,
|
page: StudentIdScreen,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.noTransition,
|
||||||
),
|
|
||||||
]),
|
|
||||||
AutoRoute(path: "map", page: MapScreen, children: [
|
|
||||||
CustomRoute(
|
|
||||||
path: "",
|
|
||||||
page: MapHomeScreen,
|
|
||||||
customRouteBuilder: mapRouteBuilder,
|
|
||||||
),
|
|
||||||
CustomRoute(
|
|
||||||
path: "category/:id",
|
|
||||||
page: MapCategoryScreen,
|
|
||||||
customRouteBuilder: mapRouteBuilder,
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
AutoRoute(path: "map", page: MapScreen),
|
||||||
AutoRoute(path: "events", page: EventsScreen),
|
AutoRoute(path: "events", page: EventsScreen),
|
||||||
AutoRoute(path: "info", name: "InfoPageRouter", page: HeroEmptyRouterPage, children: [
|
AutoRoute(path: "info", page: InfoScreen),
|
||||||
AutoRoute(path: "", page: InfoScreen),
|
|
||||||
AutoRoute(path: "health-and-safety", page: HealthSafetyScreen),
|
|
||||||
AutoRoute(path: "contacts", page: ContactsScreen),
|
|
||||||
AutoRoute(path: "hours", page: HoursScreen),
|
|
||||||
AutoRoute(path: "settings", page: SettingsScreen),
|
|
||||||
]),
|
|
||||||
]),
|
]),
|
||||||
|
// AutoRoute(
|
||||||
|
// path: "/student-id",
|
||||||
|
// page: StudentIdScreen,
|
||||||
|
// // transitionsBuilder: TransitionsBuilders.slideTop,
|
||||||
|
// // durationInMilliseconds: 200,
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class $AppRouter {}
|
class $AppRouter {}
|
||||||
@@ -11,162 +11,94 @@
|
|||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
import 'package:auto_route/auto_route.dart' as _i15;
|
import 'package:auto_route/auto_route.dart' as _i8;
|
||||||
import 'package:flutter/material.dart' as _i16;
|
import 'package:flutter/material.dart' as _i9;
|
||||||
|
|
||||||
import '../layouts/main/index.dart' as _i1;
|
import '../layouts/main/index.dart' as _i1;
|
||||||
import '../screens/events/index.dart' as _i4;
|
import '../screens/events/index.dart' as _i4;
|
||||||
import '../screens/home/home_header.dart' as _i6;
|
import '../screens/home/index.dart' as _i6;
|
||||||
import '../screens/home/index.dart' as _i2;
|
import '../screens/info/index.dart' as _i5;
|
||||||
import '../screens/info/contacts.dart' as _i12;
|
|
||||||
import '../screens/info/health_safety.dart' as _i11;
|
|
||||||
import '../screens/info/hours.dart' as _i13;
|
|
||||||
import '../screens/info/index.dart' as _i10;
|
|
||||||
import '../screens/map/index.dart' as _i3;
|
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 _i18;
|
|
||||||
import '../screens/settings/settings.dart' as _i14;
|
|
||||||
import '../screens/student_id/index.dart' as _i7;
|
import '../screens/student_id/index.dart' as _i7;
|
||||||
import '../utils/hero_empty_router_page.dart' as _i5;
|
import '../utils/hero_empty_router_page.dart' as _i2;
|
||||||
import 'index.dart' as _i17;
|
|
||||||
|
|
||||||
class AppRouter extends _i15.RootStackRouter {
|
class AppRouter extends _i8.RootStackRouter {
|
||||||
AppRouter([_i16.GlobalKey<_i16.NavigatorState>? navigatorKey])
|
AppRouter([_i9.GlobalKey<_i9.NavigatorState>? navigatorKey])
|
||||||
: super(navigatorKey);
|
: super(navigatorKey);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final Map<String, _i15.PageFactory> pagesMap = {
|
final Map<String, _i8.PageFactory> pagesMap = {
|
||||||
MainLayout.name: (routeData) {
|
MainLayout.name: (routeData) {
|
||||||
return _i15.MaterialPageX<dynamic>(
|
return _i8.MaterialPageX<dynamic>(
|
||||||
routeData: routeData, child: const _i1.MainLayout());
|
routeData: routeData, child: const _i1.MainLayout());
|
||||||
},
|
},
|
||||||
HomePageRouter.name: (routeData) {
|
HomePageRouter.name: (routeData) {
|
||||||
return _i15.MaterialPageX<dynamic>(
|
return _i8.MaterialPageX<dynamic>(
|
||||||
routeData: routeData, child: _i2.HomeScreenProvider());
|
routeData: routeData, child: const _i2.HeroEmptyRouterPage());
|
||||||
},
|
},
|
||||||
MapRoute.name: (routeData) {
|
MapRoute.name: (routeData) {
|
||||||
return _i15.MaterialPageX<dynamic>(
|
return _i8.MaterialPageX<dynamic>(
|
||||||
routeData: routeData, child: const _i3.MapScreen());
|
routeData: routeData, child: const _i3.MapScreen());
|
||||||
},
|
},
|
||||||
EventsRoute.name: (routeData) {
|
EventsRoute.name: (routeData) {
|
||||||
return _i15.MaterialPageX<dynamic>(
|
return _i8.MaterialPageX<dynamic>(
|
||||||
routeData: routeData, child: const _i4.EventsScreen());
|
routeData: routeData, child: const _i4.EventsScreen());
|
||||||
},
|
},
|
||||||
InfoPageRouter.name: (routeData) {
|
InfoRoute.name: (routeData) {
|
||||||
return _i15.MaterialPageX<dynamic>(
|
return _i8.MaterialPageX<dynamic>(
|
||||||
routeData: routeData, child: const _i5.HeroEmptyRouterPage());
|
routeData: routeData, child: const _i5.InfoScreen());
|
||||||
},
|
},
|
||||||
HomeRoute.name: (routeData) {
|
HomeRoute.name: (routeData) {
|
||||||
return _i15.CustomPage<dynamic>(
|
return _i8.CustomPage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
child: const _i6.HomePageHeader(),
|
child: const _i6.HomeScreen(),
|
||||||
transitionsBuilder: _i15.TransitionsBuilders.fadeIn,
|
transitionsBuilder: _i8.TransitionsBuilders.noTransition,
|
||||||
opaque: true,
|
opaque: true,
|
||||||
barrierDismissible: false);
|
barrierDismissible: false);
|
||||||
},
|
},
|
||||||
StudentIdRoute.name: (routeData) {
|
StudentIdRoute.name: (routeData) {
|
||||||
return _i15.CustomPage<dynamic>(
|
return _i8.CustomPage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
child: const _i7.StudentIdScreen(),
|
child: const _i7.StudentIdScreen(),
|
||||||
transitionsBuilder: _i15.TransitionsBuilders.fadeIn,
|
transitionsBuilder: _i8.TransitionsBuilders.noTransition,
|
||||||
opaque: true,
|
opaque: true,
|
||||||
barrierDismissible: false);
|
barrierDismissible: false);
|
||||||
},
|
|
||||||
MapHomeRoute.name: (routeData) {
|
|
||||||
return _i15.CustomPage<dynamic>(
|
|
||||||
routeData: routeData,
|
|
||||||
child: const _i8.MapHomeScreen(),
|
|
||||||
customRouteBuilder: _i17.mapRouteBuilder,
|
|
||||||
opaque: true,
|
|
||||||
barrierDismissible: false);
|
|
||||||
},
|
|
||||||
MapCategoryRoute.name: (routeData) {
|
|
||||||
final args = routeData.argsAs<MapCategoryRouteArgs>();
|
|
||||||
return _i15.CustomPage<dynamic>(
|
|
||||||
routeData: routeData,
|
|
||||||
child: _i9.MapCategoryScreen(category: args.category, key: args.key),
|
|
||||||
customRouteBuilder: _i17.mapRouteBuilder,
|
|
||||||
opaque: true,
|
|
||||||
barrierDismissible: false);
|
|
||||||
},
|
|
||||||
InfoRoute.name: (routeData) {
|
|
||||||
return _i15.MaterialPageX<dynamic>(
|
|
||||||
routeData: routeData, child: const _i10.InfoScreen());
|
|
||||||
},
|
|
||||||
HealthSafetyRoute.name: (routeData) {
|
|
||||||
return _i15.MaterialPageX<dynamic>(
|
|
||||||
routeData: routeData, child: const _i11.HealthSafetyScreen());
|
|
||||||
},
|
|
||||||
ContactsRoute.name: (routeData) {
|
|
||||||
return _i15.MaterialPageX<dynamic>(
|
|
||||||
routeData: routeData, child: const _i12.ContactsScreen());
|
|
||||||
},
|
|
||||||
HoursRoute.name: (routeData) {
|
|
||||||
return _i15.MaterialPageX<dynamic>(
|
|
||||||
routeData: routeData, child: const _i13.HoursScreen());
|
|
||||||
},
|
|
||||||
SettingsRoute.name: (routeData) {
|
|
||||||
return _i15.MaterialPageX<dynamic>(
|
|
||||||
routeData: routeData, child: const _i14.SettingsScreen());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<_i15.RouteConfig> get routes => [
|
List<_i8.RouteConfig> get routes => [
|
||||||
_i15.RouteConfig(MainLayout.name, path: '/', children: [
|
_i8.RouteConfig(MainLayout.name, path: '/', children: [
|
||||||
_i15.RouteConfig(HomePageRouter.name,
|
_i8.RouteConfig(HomePageRouter.name,
|
||||||
path: 'home',
|
path: 'home',
|
||||||
parent: MainLayout.name,
|
parent: MainLayout.name,
|
||||||
children: [
|
children: [
|
||||||
_i15.RouteConfig(HomeRoute.name,
|
_i8.RouteConfig(HomeRoute.name,
|
||||||
path: '', parent: HomePageRouter.name),
|
path: '', parent: HomePageRouter.name),
|
||||||
_i15.RouteConfig(StudentIdRoute.name,
|
_i8.RouteConfig(StudentIdRoute.name,
|
||||||
path: 'student-id', parent: HomePageRouter.name)
|
path: 'student-id', parent: HomePageRouter.name)
|
||||||
]),
|
]),
|
||||||
_i15.RouteConfig(MapRoute.name,
|
_i8.RouteConfig(MapRoute.name, path: 'map', parent: MainLayout.name),
|
||||||
path: 'map',
|
_i8.RouteConfig(EventsRoute.name,
|
||||||
parent: MainLayout.name,
|
|
||||||
children: [
|
|
||||||
_i15.RouteConfig(MapHomeRoute.name,
|
|
||||||
path: '', parent: MapRoute.name),
|
|
||||||
_i15.RouteConfig(MapCategoryRoute.name,
|
|
||||||
path: 'category/:id', parent: MapRoute.name)
|
|
||||||
]),
|
|
||||||
_i15.RouteConfig(EventsRoute.name,
|
|
||||||
path: 'events', parent: MainLayout.name),
|
path: 'events', parent: MainLayout.name),
|
||||||
_i15.RouteConfig(InfoPageRouter.name,
|
_i8.RouteConfig(InfoRoute.name, path: 'info', parent: MainLayout.name)
|
||||||
path: 'info',
|
|
||||||
parent: MainLayout.name,
|
|
||||||
children: [
|
|
||||||
_i15.RouteConfig(InfoRoute.name,
|
|
||||||
path: '', parent: InfoPageRouter.name),
|
|
||||||
_i15.RouteConfig(HealthSafetyRoute.name,
|
|
||||||
path: 'health-and-safety', parent: InfoPageRouter.name),
|
|
||||||
_i15.RouteConfig(ContactsRoute.name,
|
|
||||||
path: 'contacts', parent: InfoPageRouter.name),
|
|
||||||
_i15.RouteConfig(HoursRoute.name,
|
|
||||||
path: 'hours', parent: InfoPageRouter.name),
|
|
||||||
_i15.RouteConfig(SettingsRoute.name,
|
|
||||||
path: 'settings', parent: InfoPageRouter.name)
|
|
||||||
])
|
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i1.MainLayout]
|
/// [_i1.MainLayout]
|
||||||
class MainLayout extends _i15.PageRouteInfo<void> {
|
class MainLayout extends _i8.PageRouteInfo<void> {
|
||||||
const MainLayout({List<_i15.PageRouteInfo>? children})
|
const MainLayout({List<_i8.PageRouteInfo>? children})
|
||||||
: super(MainLayout.name, path: '/', initialChildren: children);
|
: super(MainLayout.name, path: '/', initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'MainLayout';
|
static const String name = 'MainLayout';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i2.HomeScreen]
|
/// [_i2.HeroEmptyRouterPage]
|
||||||
class HomePageRouter extends _i15.PageRouteInfo<void> {
|
class HomePageRouter extends _i8.PageRouteInfo<void> {
|
||||||
const HomePageRouter({List<_i15.PageRouteInfo>? children})
|
const HomePageRouter({List<_i8.PageRouteInfo>? children})
|
||||||
: super(HomePageRouter.name, path: 'home', initialChildren: children);
|
: super(HomePageRouter.name, path: 'home', initialChildren: children);
|
||||||
|
|
||||||
static const String name = 'HomePageRouter';
|
static const String name = 'HomePageRouter';
|
||||||
@@ -174,33 +106,31 @@ class HomePageRouter extends _i15.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i3.MapScreen]
|
/// [_i3.MapScreen]
|
||||||
class MapRoute extends _i15.PageRouteInfo<void> {
|
class MapRoute extends _i8.PageRouteInfo<void> {
|
||||||
const MapRoute({List<_i15.PageRouteInfo>? children})
|
const MapRoute() : super(MapRoute.name, path: 'map');
|
||||||
: super(MapRoute.name, path: 'map', initialChildren: children);
|
|
||||||
|
|
||||||
static const String name = 'MapRoute';
|
static const String name = 'MapRoute';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i4.EventsScreen]
|
/// [_i4.EventsScreen]
|
||||||
class EventsRoute extends _i15.PageRouteInfo<void> {
|
class EventsRoute extends _i8.PageRouteInfo<void> {
|
||||||
const EventsRoute() : super(EventsRoute.name, path: 'events');
|
const EventsRoute() : super(EventsRoute.name, path: 'events');
|
||||||
|
|
||||||
static const String name = 'EventsRoute';
|
static const String name = 'EventsRoute';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i5.HeroEmptyRouterPage]
|
/// [_i5.InfoScreen]
|
||||||
class InfoPageRouter extends _i15.PageRouteInfo<void> {
|
class InfoRoute extends _i8.PageRouteInfo<void> {
|
||||||
const InfoPageRouter({List<_i15.PageRouteInfo>? children})
|
const InfoRoute() : super(InfoRoute.name, path: 'info');
|
||||||
: super(InfoPageRouter.name, path: 'info', initialChildren: children);
|
|
||||||
|
|
||||||
static const String name = 'InfoPageRouter';
|
static const String name = 'InfoRoute';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i6.HomePageHeader]
|
/// [_i6.HomeScreen]
|
||||||
class HomeRoute extends _i15.PageRouteInfo<void> {
|
class HomeRoute extends _i8.PageRouteInfo<void> {
|
||||||
const HomeRoute() : super(HomeRoute.name, path: '');
|
const HomeRoute() : super(HomeRoute.name, path: '');
|
||||||
|
|
||||||
static const String name = 'HomeRoute';
|
static const String name = 'HomeRoute';
|
||||||
@@ -208,81 +138,8 @@ class HomeRoute extends _i15.PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [_i7.StudentIdScreen]
|
/// [_i7.StudentIdScreen]
|
||||||
class StudentIdRoute extends _i15.PageRouteInfo<void> {
|
class StudentIdRoute extends _i8.PageRouteInfo<void> {
|
||||||
const StudentIdRoute() : super(StudentIdRoute.name, path: 'student-id');
|
const StudentIdRoute() : super(StudentIdRoute.name, path: 'student-id');
|
||||||
|
|
||||||
static const String name = 'StudentIdRoute';
|
static const String name = 'StudentIdRoute';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [_i8.MapHomeScreen]
|
|
||||||
class MapHomeRoute extends _i15.PageRouteInfo<void> {
|
|
||||||
const MapHomeRoute() : super(MapHomeRoute.name, path: '');
|
|
||||||
|
|
||||||
static const String name = 'MapHomeRoute';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [_i9.MapCategoryScreen]
|
|
||||||
class MapCategoryRoute extends _i15.PageRouteInfo<MapCategoryRouteArgs> {
|
|
||||||
MapCategoryRoute({required _i18.MapCategory category, _i16.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 _i18.MapCategory category;
|
|
||||||
|
|
||||||
final _i16.Key? key;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'MapCategoryRouteArgs{category: $category, key: $key}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [_i10.InfoScreen]
|
|
||||||
class InfoRoute extends _i15.PageRouteInfo<void> {
|
|
||||||
const InfoRoute() : super(InfoRoute.name, path: '');
|
|
||||||
|
|
||||||
static const String name = 'InfoRoute';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [_i11.HealthSafetyScreen]
|
|
||||||
class HealthSafetyRoute extends _i15.PageRouteInfo<void> {
|
|
||||||
const HealthSafetyRoute()
|
|
||||||
: super(HealthSafetyRoute.name, path: 'health-and-safety');
|
|
||||||
|
|
||||||
static const String name = 'HealthSafetyRoute';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [_i12.ContactsScreen]
|
|
||||||
class ContactsRoute extends _i15.PageRouteInfo<void> {
|
|
||||||
const ContactsRoute() : super(ContactsRoute.name, path: 'contacts');
|
|
||||||
|
|
||||||
static const String name = 'ContactsRoute';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [_i13.HoursScreen]
|
|
||||||
class HoursRoute extends _i15.PageRouteInfo<void> {
|
|
||||||
const HoursRoute() : super(HoursRoute.name, path: 'hours');
|
|
||||||
|
|
||||||
static const String name = 'HoursRoute';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [_i14.SettingsScreen]
|
|
||||||
class SettingsRoute extends _i15.PageRouteInfo<void> {
|
|
||||||
const SettingsRoute() : super(SettingsRoute.name, path: 'settings');
|
|
||||||
|
|
||||||
static const String name = 'SettingsRoute';
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
|
||||||
import 'package:furman_now/src/layouts/app_page.dart';
|
|
||||||
import 'package:furman_now/src/utils/date_range.dart';
|
import 'package:furman_now/src/utils/date_range.dart';
|
||||||
import 'package:furman_now/src/utils/theme.dart';
|
import 'package:furman_now/src/utils/theme.dart';
|
||||||
import 'package:furman_now/src/widgets/header.dart';
|
import 'package:furman_now/src/widgets/header.dart';
|
||||||
@@ -13,11 +11,48 @@ class EventsScreen extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppPageLayout(
|
return Scaffold(
|
||||||
title: "Events",
|
body: Container(
|
||||||
icon: FlutterRemix.calendar_line,
|
color: Colors.grey[100],
|
||||||
content: Padding(
|
child: SafeArea(
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.loose,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||||
|
width: double.infinity,
|
||||||
|
height: 100,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Wrap(
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.calendar_month_outlined, size: 35, color: Colors.grey[700]),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text("Events", style: furmanTextStyle(TextStyle(color: Colors.grey[900], fontSize: 28, fontWeight: FontWeight.w700))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ScrollViewWithHeight(
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
|
||||||
|
),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
|
margin: const EdgeInsets.only(top: 100),
|
||||||
|
width: double.infinity,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -65,6 +100,11 @@ class EventsScreen extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:furman_now/src/routes/index.gr.dart';
|
|
||||||
import 'package:furman_now/src/widgets/events/events_list.dart';
|
|
||||||
import 'package:furman_now/src/widgets/header.dart';
|
|
||||||
import 'package:furman_now/src/widgets/home/restaurants/restaurants_list.dart';
|
|
||||||
import 'package:furman_now/src/widgets/home/transportation/transportation_card.dart';
|
|
||||||
import 'package:furman_now/src/widgets/scroll_view_height.dart';
|
|
||||||
|
|
||||||
class HomeContent extends StatefulWidget {
|
|
||||||
final bool collapse;
|
|
||||||
final ScrollController controller;
|
|
||||||
|
|
||||||
const HomeContent({
|
|
||||||
required this.collapse,
|
|
||||||
required this.controller,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<HomeContent> createState() => _HomeContentState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HomeContentState extends State<HomeContent> {
|
|
||||||
bool _collapse = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (widget.collapse != _collapse) {
|
|
||||||
widget.controller.animateTo(
|
|
||||||
0,
|
|
||||||
duration: const Duration(milliseconds: 400),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
|
||||||
_collapse = widget.collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, constraints) => ScrollViewWithHeight(
|
|
||||||
controller: widget.controller,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: TweenAnimationBuilder<double>(
|
|
||||||
tween: widget.collapse
|
|
||||||
? Tween<double>(begin: 200, end: constraints.maxHeight - 75)
|
|
||||||
: Tween<double>(begin: constraints.maxHeight - 75, end: 200),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
builder: (context, margin, _) => Stack(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.vertical(
|
|
||||||
top: Radius.circular(30),
|
|
||||||
bottom: Radius.circular(30),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.only(bottom: 15),
|
|
||||||
margin: EdgeInsets.only(top: margin),
|
|
||||||
width: double.infinity,
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight - margin,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
height: widget.collapse ? 55 : 0,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 15),
|
|
||||||
width: 60,
|
|
||||||
height: 5,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[300],
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
const HeaderWidget(
|
|
||||||
title: "Upcoming Events",
|
|
||||||
link:
|
|
||||||
HeaderLink(text: "View more", href: EventsRoute()),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: EventsList(limit: 3),
|
|
||||||
),
|
|
||||||
const HeaderWidget(title: "Food & Dining"),
|
|
||||||
const RestaurantsList(),
|
|
||||||
const HeaderWidget(title: "Transportation"),
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: TransportationCard(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:furman_now/src/screens/home/state.dart';
|
|
||||||
import 'package:furman_now/src/services/weather/weather_service.dart';
|
|
||||||
import 'package:furman_now/src/utils/greeting.dart';
|
|
||||||
import 'package:furman_now/src/utils/theme.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class HomePageHeader extends StatelessWidget {
|
|
||||||
const HomePageHeader({ Key? key }) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 300,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: <Color>[
|
|
||||||
Color(0xffb7acc9),
|
|
||||||
Color(0xffb7acc9),
|
|
||||||
],
|
|
||||||
tileMode: TileMode.mirror,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(40),
|
|
||||||
width: double.infinity,
|
|
||||||
height: 200,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Hero(
|
|
||||||
tag: "title",
|
|
||||||
child: Material(
|
|
||||||
type: MaterialType.transparency,
|
|
||||||
child: Text(
|
|
||||||
"${greeting()},\nMichael",
|
|
||||||
style: furmanTextStyle(const TextStyle(
|
|
||||||
color: Color(0xff26183d),
|
|
||||||
fontSize: 36,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
height: 1.2,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Consumer<HomePageState>(
|
|
||||||
builder: (context, state, _) => AnimatedCrossFade(
|
|
||||||
crossFadeState:
|
|
||||||
state.showScrollMessage
|
|
||||||
? CrossFadeState.showSecond
|
|
||||||
: CrossFadeState.showFirst,
|
|
||||||
duration: const Duration(milliseconds: 100),
|
|
||||||
firstChild: FutureBuilder<String>(
|
|
||||||
future: WeatherService.getWeatherMessage(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if(snapshot.hasData) {
|
|
||||||
return Text(
|
|
||||||
snapshot.data!,
|
|
||||||
// "It's 76º and partly cloudy",
|
|
||||||
style: furmanTextStyle(const TextStyle
|
|
||||||
(
|
|
||||||
color: Color(0xff26183d),
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox(height: 0);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
secondChild: Text("Pull down to view your Meal ID",
|
|
||||||
style: furmanTextStyle(const TextStyle(
|
|
||||||
color: Color(0xff26183d),
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,13 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:furman_now/src/routes/index.gr.dart';
|
import 'package:furman_now/src/routes/index.gr.dart';
|
||||||
import 'package:furman_now/src/screens/home/content.dart';
|
import 'package:furman_now/src/utils/greeting.dart';
|
||||||
import 'package:furman_now/src/screens/home/state.dart';
|
|
||||||
import 'package:furman_now/src/utils/theme.dart';
|
import 'package:furman_now/src/utils/theme.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:furman_now/src/widgets/header.dart';
|
||||||
|
import 'package:furman_now/src/widgets/events/events_list.dart';
|
||||||
class HomeScreenProvider extends StatelessWidget {
|
import 'package:furman_now/src/widgets/home/restaurants/restaurants_list.dart';
|
||||||
const HomeScreenProvider({super.key});
|
import 'package:furman_now/src/widgets/home/transportation/transportation_card.dart';
|
||||||
|
import 'package:furman_now/src/widgets/scroll_view_height.dart';
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ChangeNotifierProvider(
|
|
||||||
create: (context) => HomePageState(),
|
|
||||||
child: const HomeScreen(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
const HomeScreen({Key? key}) : super(key: key);
|
const HomeScreen({Key? key}) : super(key: key);
|
||||||
@@ -27,112 +17,73 @@ class HomeScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> {
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
final ScrollController _controller = ScrollController();
|
CrossFadeState _showScrollMessage = CrossFadeState.showFirst;
|
||||||
|
|
||||||
double overscrollTopAmount = 0;
|
|
||||||
double overscrollBottomAmount = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
_controller.addListener(updateScrollPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateScrollPosition() {
|
|
||||||
// bottom overscroll
|
|
||||||
var bottomOverscroll =
|
|
||||||
_controller.position.pixels - _controller.position.maxScrollExtent;
|
|
||||||
if (bottomOverscroll > 0) {
|
|
||||||
setState(() {
|
|
||||||
overscrollBottomAmount = bottomOverscroll;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (overscrollBottomAmount != 0) {
|
|
||||||
setState(() {
|
|
||||||
overscrollBottomAmount = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// top overscroll
|
|
||||||
var topOverscroll = -_controller.position.pixels;
|
|
||||||
if (topOverscroll > 0) {
|
|
||||||
setState(() {
|
|
||||||
overscrollTopAmount = topOverscroll;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (overscrollTopAmount != 0) {
|
|
||||||
setState(() {
|
|
||||||
overscrollTopAmount = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTopOverscroll(topOverscroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTopOverscroll(double overscrollTopAmount) {
|
|
||||||
const scrollMessageSensitivity = 20;
|
|
||||||
const pageSwitchSensitivity = 60;
|
|
||||||
var collapsed = context.read<HomePageState>().collapse;
|
|
||||||
|
|
||||||
if (!collapsed) {
|
|
||||||
if (overscrollTopAmount > pageSwitchSensitivity) {
|
|
||||||
// switch to student ID page
|
|
||||||
context.router.navigate(const StudentIdRoute());
|
|
||||||
context.read<HomePageState>().collapse = true;
|
|
||||||
} else if (overscrollTopAmount > scrollMessageSensitivity) {
|
|
||||||
// show student ID hint
|
|
||||||
context.read<HomePageState>().showScrollMessage = true;
|
|
||||||
} else {
|
|
||||||
// hide student ID hint
|
|
||||||
context.read<HomePageState>().showScrollMessage = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (collapsed && -overscrollTopAmount > pageSwitchSensitivity) {
|
|
||||||
// switch back to home page
|
|
||||||
context.router.navigate(const HomeRoute());
|
|
||||||
context.read<HomePageState>().collapse = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: AnnotatedRegion<SystemUiOverlayStyle>(
|
body: Container(
|
||||||
value: constructOverlayStyle(),
|
|
||||||
child: Container(
|
|
||||||
color: const Color(0xffb7acc9),
|
color: const Color(0xffb7acc9),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: const Color(0xffb7acc9),
|
color: Colors.grey[100],
|
||||||
child: Stack(
|
child: Stack(
|
||||||
fit: StackFit.loose,
|
fit: StackFit.loose,
|
||||||
children: [
|
children: [
|
||||||
const AutoTabsRouter(
|
Container(
|
||||||
routes: [
|
width: double.infinity,
|
||||||
HomeRoute(),
|
height: 300,
|
||||||
StudentIdRoute(),
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: <Color>[
|
||||||
|
Color(0xffb7acc9),
|
||||||
|
Color(0xffb7acc9),
|
||||||
],
|
],
|
||||||
|
tileMode: TileMode.mirror,
|
||||||
),
|
),
|
||||||
// overscroll indicator color
|
),
|
||||||
Align(
|
child: Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.topLeft,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: overscrollBottomAmount + 30,
|
padding: const EdgeInsets.all(40),
|
||||||
decoration: BoxDecoration(
|
width: double.infinity,
|
||||||
color: Colors.grey.shade50,
|
height: 200,
|
||||||
border: Border.all(width: 0, color: Colors.white),
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Hero(
|
||||||
|
tag: "title",
|
||||||
|
child: Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: Text(
|
||||||
|
"${greeting()},\nMichael",
|
||||||
|
style: furmanTextStyle(const TextStyle(
|
||||||
|
color: Color(0xff26183d),
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Consumer<HomePageState>(
|
const SizedBox(height: 5),
|
||||||
builder: (context, state, _) =>
|
AnimatedCrossFade(
|
||||||
ClipRect(child: HomeContent(
|
crossFadeState: _showScrollMessage,
|
||||||
controller: _controller,
|
duration: const Duration(milliseconds: 100),
|
||||||
collapse: state.collapse,
|
firstChild: Text("It's 76º and partly cloudy",
|
||||||
|
style: furmanTextStyle(const TextStyle(
|
||||||
|
color: Color(0xff26183d),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
secondChild: Text("Pull down to view your Meal ID",
|
||||||
|
style: furmanTextStyle(const TextStyle(
|
||||||
|
color: Color(0xff26183d),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -141,7 +92,81 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
NotificationListener<ScrollNotification>(
|
||||||
|
onNotification: (notification) {
|
||||||
|
if (notification is ScrollUpdateNotification) {
|
||||||
|
final offset = notification.metrics.pixels;
|
||||||
|
if (offset < 0) {
|
||||||
|
var offsetAmount = offset.abs();
|
||||||
|
if (offsetAmount > 50) {
|
||||||
|
context.router.navigate(const StudentIdRoute());
|
||||||
|
} else if (offsetAmount > 20) {
|
||||||
|
setState(() {
|
||||||
|
_showScrollMessage = CrossFadeState.showSecond;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_showScrollMessage = CrossFadeState.showFirst;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_showScrollMessage != CrossFadeState.showFirst) {
|
||||||
|
setState(() {
|
||||||
|
_showScrollMessage = CrossFadeState.showFirst;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: ScrollViewWithHeight(
|
||||||
|
child: Hero(
|
||||||
|
tag: "card",
|
||||||
|
child: Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.vertical(top: Radius.circular(30)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
|
margin: const EdgeInsets.only(top: 200),
|
||||||
|
width: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const HeaderWidget(
|
||||||
|
title: "Today's Events",
|
||||||
|
link: HeaderLink(
|
||||||
|
text: "View more",
|
||||||
|
href: EventsRoute()
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: EventsList(),
|
||||||
|
),
|
||||||
|
const HeaderWidget(title: "Food & Dining"),
|
||||||
|
const RestaurantsList(),
|
||||||
|
const HeaderWidget(title: "Transportation"),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: TransportationCard(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class HomePageState extends ChangeNotifier {
|
|
||||||
bool _showScrollMessage = false;
|
|
||||||
bool _collapse = false;
|
|
||||||
|
|
||||||
bool get showScrollMessage { return _showScrollMessage; }
|
|
||||||
set showScrollMessage(bool value) {
|
|
||||||
if (value != _showScrollMessage) {
|
|
||||||
_showScrollMessage = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get collapse { return _collapse; }
|
|
||||||
set collapse(bool value) {
|
|
||||||
if (value != _collapse) {
|
|
||||||
_collapse = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
|
||||||
import 'package:furman_now/src/layouts/app_page.dart';
|
|
||||||
import 'package:furman_now/src/services/info/contacts_service.dart';
|
|
||||||
import 'package:furman_now/src/widgets/info/info_card.dart';
|
|
||||||
|
|
||||||
class ContactsScreen extends StatelessWidget {
|
|
||||||
const ContactsScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AppPageLayout(
|
|
||||||
title: "Contacts",
|
|
||||||
icon: FlutterRemix.arrow_left_line,
|
|
||||||
backgroundColor: Colors.deepPurple.shade50,
|
|
||||||
iconTapAction: () => context.router.pop(),
|
|
||||||
content: Padding(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: FutureBuilder<Iterable<ContactInfo>>(
|
|
||||||
future: ContactsService.fetchContactInfo(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
var items = snapshot.data!;
|
|
||||||
return Column(
|
|
||||||
children: items.map((item) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 15),
|
|
||||||
child: InfoCard(
|
|
||||||
title: item.name,
|
|
||||||
icon: item.buildingId != 0
|
|
||||||
? FlutterRemix.building_line
|
|
||||||
: FlutterRemix.contacts_line,
|
|
||||||
items: [
|
|
||||||
InfoCardItem(
|
|
||||||
name: item.phone,
|
|
||||||
icon: FlutterRemix.phone_line
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onClick: () => item.launch(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList());
|
|
||||||
}
|
|
||||||
else if (snapshot.hasError) {
|
|
||||||
return Text(
|
|
||||||
'${snapshot.error}',
|
|
||||||
style: const TextStyle(color: Colors.red)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// By default, show a loading spinner.
|
|
||||||
return const Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 25),
|
|
||||||
child: CircularProgressIndicator()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
|
||||||
import 'package:furman_now/src/layouts/app_page.dart';
|
|
||||||
import 'package:furman_now/src/services/info/health_safety_service.dart';
|
|
||||||
import 'package:furman_now/src/widgets/info/info_card.dart';
|
|
||||||
|
|
||||||
class HealthSafetyScreen extends StatelessWidget {
|
|
||||||
const HealthSafetyScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AppPageLayout(
|
|
||||||
title: "Health and Safety",
|
|
||||||
icon: FlutterRemix.arrow_left_line,
|
|
||||||
backgroundColor: Colors.red.shade50,
|
|
||||||
iconTapAction: () => context.router.pop(),
|
|
||||||
content: Padding(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 15, bottom: 35),
|
|
||||||
child: Text(
|
|
||||||
"If you are in an emergency, dial 911",
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
color: Colors.red.shade800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FutureBuilder<Iterable<HealthSafetyInfo>>(
|
|
||||||
future: HealthSafetyService.fetchHealthSafetyInfo(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
var items = snapshot.data!.where((item) =>
|
|
||||||
item.type != HealthSafetyInfoType.removed
|
|
||||||
);
|
|
||||||
var iconMap = {
|
|
||||||
HealthSafetyInfoType.phone: FlutterRemix.phone_line,
|
|
||||||
HealthSafetyInfoType.link: FlutterRemix.external_link_line,
|
|
||||||
HealthSafetyInfoType.app: FlutterRemix.apps_line,
|
|
||||||
};
|
|
||||||
return Column(
|
|
||||||
children: items.map((item) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 15),
|
|
||||||
child: InfoCard(
|
|
||||||
title: item.name,
|
|
||||||
icon: item.icon,
|
|
||||||
items: [
|
|
||||||
InfoCardItem(
|
|
||||||
name: item.linkText,
|
|
||||||
icon: iconMap[item.type] ?? FlutterRemix.link
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onClick: () => item.launch(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList());
|
|
||||||
}
|
|
||||||
else if (snapshot.hasError) {
|
|
||||||
return Text(
|
|
||||||
'${snapshot.error}', style: const TextStyle(color: Colors.red),);
|
|
||||||
}
|
|
||||||
// By default, show a loading spinner.
|
|
||||||
return const Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 25),
|
|
||||||
child: CircularProgressIndicator()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
|
||||||
import 'package:furman_now/src/layouts/app_page.dart';
|
|
||||||
import 'package:furman_now/src/services/buildings/buildings_service.dart';
|
|
||||||
import 'package:furman_now/src/store/index.dart';
|
|
||||||
import 'package:furman_now/src/widgets/info/info_card.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class HoursScreen extends StatelessWidget {
|
|
||||||
const HoursScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AppPageLayout(
|
|
||||||
title: "Hours",
|
|
||||||
icon: FlutterRemix.arrow_left_line,
|
|
||||||
backgroundColor: Colors.blue.shade50,
|
|
||||||
iconTapAction: () => context.router.pop(),
|
|
||||||
content: Padding(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: Consumer<AppState>(
|
|
||||||
builder: (context, state, _) {
|
|
||||||
return FutureBuilder<Iterable<Building>>(
|
|
||||||
future: state.buildings,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
var items = snapshot.data!.where(
|
|
||||||
(building) => building.hoursList.isNotEmpty
|
|
||||||
);
|
|
||||||
return Column(
|
|
||||||
children: items.map((item) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 15),
|
|
||||||
child: InfoCard(
|
|
||||||
title: item.name,
|
|
||||||
icon: FlutterRemix.building_line,
|
|
||||||
items: item.hoursList.map((hours) =>
|
|
||||||
InfoCardItem(
|
|
||||||
name: ""
|
|
||||||
"${hours.daysOfWeek} from "
|
|
||||||
"${hours.startTime.format(context)} - "
|
|
||||||
"${hours.endTime.format(context)}",
|
|
||||||
icon: FlutterRemix.time_line,
|
|
||||||
),
|
|
||||||
).toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (snapshot.hasError) {
|
|
||||||
return Text(
|
|
||||||
'${snapshot.error}',
|
|
||||||
style: const TextStyle(color: Colors.red),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// By default, show a loading spinner.
|
|
||||||
return const Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 25),
|
|
||||||
child: CircularProgressIndicator()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +1,86 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
import 'package:furman_now/src/utils/theme.dart';
|
||||||
import 'package:furman_now/src/layouts/app_page.dart';
|
import 'package:furman_now/src/widgets/info/info_card.dart';
|
||||||
import 'package:furman_now/src/routes/index.gr.dart';
|
import 'package:furman_now/src/widgets/scroll_view_height.dart';
|
||||||
import 'package:furman_now/src/widgets/info/info_category_card.dart';
|
|
||||||
|
|
||||||
class InfoScreen extends StatelessWidget {
|
class InfoScreen extends StatelessWidget {
|
||||||
const InfoScreen({Key? key}) : super(key: key);
|
const InfoScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppPageLayout(
|
return Scaffold(
|
||||||
title: "Info",
|
body: Container(
|
||||||
icon: FlutterRemix.information_line,
|
color: Colors.grey[100],
|
||||||
content: Padding(
|
child: SafeArea(
|
||||||
padding: const EdgeInsets.all(20),
|
child: Stack(
|
||||||
|
fit: StackFit.loose,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||||
|
width: double.infinity,
|
||||||
|
height: 100,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Wrap(
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, size: 35, color: Colors.grey[700]),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text("Info", style: furmanTextStyle(TextStyle(color: Colors.grey[900], fontSize: 28, fontWeight: FontWeight.w700))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ScrollViewWithHeight(
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
|
||||||
|
margin: const EdgeInsets.only(top: 100),
|
||||||
|
width: double.infinity,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
InfoCard(
|
||||||
onTap: () => context.router.push(const HealthSafetyRoute()),
|
|
||||||
child: InfoCategoryCard(
|
|
||||||
color: Colors.red.shade50,
|
color: Colors.red.shade50,
|
||||||
icon: Icons.local_hospital,
|
icon: Icons.local_hospital,
|
||||||
title: "Health and Safety",
|
title: "Health and Safety",
|
||||||
description: "Important contact information and links regarding student health and safety.",
|
description: "Important contact information and links regarding student health and safety.",
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
GestureDetector(
|
InfoCard(
|
||||||
onTap: () => context.router.push(const ContactsRoute()),
|
|
||||||
child: InfoCategoryCard(
|
|
||||||
color: Colors.deepPurple.shade50,
|
color: Colors.deepPurple.shade50,
|
||||||
icon: Icons.phone,
|
icon: Icons.phone,
|
||||||
title: "Contacts",
|
title: "Contacts",
|
||||||
description: "Contact information for offices and resources on Furman's campus.",
|
description: "Important contact information and links regarding student health and safety.",
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
GestureDetector(
|
InfoCard(
|
||||||
onTap: () => context.router.push(const HoursRoute()),
|
|
||||||
child: InfoCategoryCard(
|
|
||||||
color: Colors.blue.shade50,
|
color: Colors.blue.shade50,
|
||||||
icon: Icons.access_time,
|
icon: Icons.access_time,
|
||||||
title: "Hours",
|
title: "Hours",
|
||||||
description: "Check the hours for buildings and services on Furman's campus.",
|
description: "Important contact information and links regarding student health and safety.",
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
|
||||||
const Spacer(),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => context.router.push(const SettingsRoute()),
|
|
||||||
child: InfoCategoryCard(
|
|
||||||
color: Colors.grey.shade100,
|
|
||||||
icon: Icons.settings,
|
|
||||||
title: "Settings",
|
|
||||||
description: "Account settings, GET login, dark mode",
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.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';
|
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';
|
||||||
|
|
||||||
class MapScreen extends StatefulWidget {
|
class MapScreen extends StatefulWidget {
|
||||||
const MapScreen({Key? key}) : super(key: key);
|
const MapScreen({Key? key}) : super(key: key);
|
||||||
@@ -15,34 +18,206 @@ class MapScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _MapScreenState extends State<MapScreen>
|
class _MapScreenState extends State<MapScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
|
final MapController _mapController = MapController();
|
||||||
|
|
||||||
|
late final AnimationController _animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
late CenterOnLocationUpdate _centerOnLocationUpdate;
|
||||||
|
late StreamController<double?> _centerCurrentLocationStreamController;
|
||||||
|
|
||||||
|
var _rotation = 0.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_mapController.mapEventStream.listen((event) {
|
||||||
|
if (event is MapEventRotate) {
|
||||||
|
setState(() {
|
||||||
|
_rotation = _mapController.rotation * (2 * pi) / 360;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_centerOnLocationUpdate = CenterOnLocationUpdate.always;
|
||||||
|
_centerCurrentLocationStreamController = StreamController<double?>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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<double>(
|
||||||
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider(
|
return Scaffold(
|
||||||
create: (context) => MapPageState(vsync: this),
|
|
||||||
builder: (context, _) => WillPopScope(
|
|
||||||
onWillPop: () async {
|
|
||||||
print("Will pop");
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: Scaffold(
|
|
||||||
body: Container(
|
body: Container(
|
||||||
color: const Color(0xffb7acc9),
|
color: const Color(0xffb7acc9),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
// child: MapWidget(),
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: const [
|
children: [
|
||||||
MapWidget(),
|
FlutterMap(
|
||||||
TransparentPointer(transparent: true, child: AutoRouter()),
|
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"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// 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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
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<MapPageState>(
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
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<MapPageState>(
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
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<MapPageState>(
|
|
||||||
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,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
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<double?>();
|
|
||||||
}
|
|
||||||
|
|
||||||
final MapController mapController = MapController();
|
|
||||||
final AnimationController _animationController;
|
|
||||||
|
|
||||||
late CenterOnLocationUpdate _centerOnLocationUpdate;
|
|
||||||
CenterOnLocationUpdate get centerOnLocationUpdate => _centerOnLocationUpdate;
|
|
||||||
set centerOnLocationUpdate (CenterOnLocationUpdate value) {
|
|
||||||
_centerOnLocationUpdate = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
late StreamController<double?> centerCurrentLocationStreamController;
|
|
||||||
|
|
||||||
var _rotation = 0.0;
|
|
||||||
double get rotation => _rotation;
|
|
||||||
set rotation (double value) {
|
|
||||||
_rotation = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Marker> _markers = <Marker>[];
|
|
||||||
List<Marker> get markers => _markers;
|
|
||||||
set markers (List<Marker> value) {
|
|
||||||
_markers = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetRotation() async {
|
|
||||||
// take the shortest rotation path
|
|
||||||
var end = mapController.rotation > 180 ? 360.0 : 0.0;
|
|
||||||
var animation = Tween<double>(
|
|
||||||
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<MapCategory> categories = [
|
|
||||||
MapCategory(
|
|
||||||
name: "Restaurants",
|
|
||||||
icon: FlutterRemix.restaurant_line,
|
|
||||||
activator: showRestaurants,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
Future<void> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
|
||||||
import 'package:furman_now/src/layouts/app_page.dart';
|
|
||||||
import 'package:furman_now/src/services/get_app/user/user_service.dart';
|
|
||||||
|
|
||||||
class SettingsScreen extends StatefulWidget {
|
|
||||||
const SettingsScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SettingsScreenState extends State<SettingsScreen> {
|
|
||||||
final UserService _service = UserService();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AppPageLayout(
|
|
||||||
title: "Settings",
|
|
||||||
icon: FlutterRemix.arrow_left_line,
|
|
||||||
backgroundColor: Colors.grey.shade100,
|
|
||||||
iconTapAction: () => context.router.pop(),
|
|
||||||
content: Padding(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
_service.login();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(20),
|
|
||||||
child: Text("Log in with GET app."),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,9 @@ import 'dart:async';
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:barcode_widget/barcode_widget.dart';
|
import 'package:barcode_widget/barcode_widget.dart';
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
|
||||||
import 'package:furman_now/src/routes/index.gr.dart';
|
import 'package:furman_now/src/routes/index.gr.dart';
|
||||||
import 'package:furman_now/src/services/get_app/barcode/barcode_service.dart';
|
import 'package:furman_now/src/services/get_app/barcode/barcode_service.dart';
|
||||||
import 'package:furman_now/src/utils/theme.dart';
|
import 'package:furman_now/src/utils/theme.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../home/state.dart';
|
|
||||||
|
|
||||||
class StudentIdScreen extends StatefulWidget {
|
class StudentIdScreen extends StatefulWidget {
|
||||||
const StudentIdScreen({Key? key}) : super(key: key);
|
const StudentIdScreen({Key? key}) : super(key: key);
|
||||||
@@ -47,23 +43,16 @@ class _StudentIdScreenState extends State<StudentIdScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: WillPopScope(
|
body: Listener(
|
||||||
onWillPop: () async {
|
|
||||||
context.read<HomePageState>().collapse = false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
child: Listener(
|
|
||||||
onPointerMove: (details) {
|
onPointerMove: (details) {
|
||||||
int sensitivity = 8;
|
int sensitivity = 8;
|
||||||
if (details.delta.dy > sensitivity) {
|
if (details.delta.dy > sensitivity) {
|
||||||
// Down Swipe
|
// Down Swipe
|
||||||
} else if (details.delta.dy < -sensitivity) {
|
} else if (details.delta.dy < -sensitivity) {
|
||||||
context.router.navigate(HomeRoute());
|
context.router.navigate(const MainLayout());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Container(
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
color: const Color(0xffb7acc9),
|
color: const Color(0xffb7acc9),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
@@ -71,47 +60,9 @@ class _StudentIdScreenState extends State<StudentIdScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Furman ID",
|
"Furman ID",
|
||||||
style: furmanTextStyle(const TextStyle(
|
style: furmanTextStyle(const TextStyle(color: Color(0xff26183d), fontSize: 36, fontWeight: FontWeight.w800)),
|
||||||
color: Color(0xff26183d),
|
|
||||||
fontSize: 36,
|
|
||||||
fontWeight: FontWeight.w800
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 200),
|
||||||
Center(
|
|
||||||
child: Container(
|
|
||||||
height: 200,
|
|
||||||
width: 200,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[200],
|
|
||||||
borderRadius: BorderRadius.circular(40),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
FlutterRemix.user_3_line,
|
|
||||||
size: 120,
|
|
||||||
color: Colors.grey[400],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Center(child: Text(
|
|
||||||
"Michael Thomas",
|
|
||||||
style: furmanTextStyle(const TextStyle(
|
|
||||||
color: Color(0xff26183d),
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: FontWeight.w700
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Center(child: Text(
|
|
||||||
"5001962",
|
|
||||||
style: furmanTextStyle(const TextStyle(
|
|
||||||
color: Color(0xff26183d),
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Container(
|
Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -133,9 +84,6 @@ class _StudentIdScreenState extends State<StudentIdScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
class BuildingsService {
|
|
||||||
static Future<List<Building>> fetchBuildings() async {
|
|
||||||
var buildings = (await _fetchBuildings()).toList();
|
|
||||||
await _fetchBuildingHours(buildings);
|
|
||||||
return buildings;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<Iterable<Building>> _fetchBuildings() async {
|
|
||||||
final response = await http
|
|
||||||
.get(Uri.parse("https://cs.furman.edu/~csdaemon/FUNow/buildingGet.php"));
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// If the server did return a 200 OK response,
|
|
||||||
// then parse the JSON.
|
|
||||||
final buildingsJson = jsonDecode(response.body);
|
|
||||||
return (buildingsJson["results"] as List<dynamic>).map((json) =>
|
|
||||||
Building.fromJson(json)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// If the server did not return a 200 OK response,
|
|
||||||
// then throw an exception.
|
|
||||||
throw Exception('Failed to load buildings.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> _fetchBuildingHours(List<Building> buildings) async {
|
|
||||||
final response = await http
|
|
||||||
.get(Uri.parse("https://cs.furman.edu/~csdaemon/FUNow/hoursGet.php"));
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// If the server did return a 200 OK response,
|
|
||||||
// then parse the JSON.
|
|
||||||
final hoursListJson = jsonDecode(response.body);
|
|
||||||
for (var hoursJson in (hoursListJson["results"] as List<dynamic>)) {
|
|
||||||
try {
|
|
||||||
var hours = BuildingHours.fromJson(hoursJson);
|
|
||||||
buildings.firstWhere((building) => building.id == hours.id).hoursList.add(hours);
|
|
||||||
} on ArgumentError {
|
|
||||||
// if json parse fails, just move on
|
|
||||||
// this is terribly hacky but the server is a mess I cannot fix
|
|
||||||
}
|
|
||||||
for (var building in buildings) { building.hoursList.sort(); }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If the server did not return a 200 OK response,
|
|
||||||
// then throw an exception.
|
|
||||||
throw Exception('Failed to load building hours.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Building {
|
|
||||||
int id;
|
|
||||||
String name;
|
|
||||||
String? shortName;
|
|
||||||
String category;
|
|
||||||
String? location;
|
|
||||||
LatLng mapLocation;
|
|
||||||
int frequency;
|
|
||||||
List<BuildingHours> hoursList = <BuildingHours>[];
|
|
||||||
|
|
||||||
Building({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
this.shortName,
|
|
||||||
required this.category,
|
|
||||||
this.location,
|
|
||||||
required this.mapLocation,
|
|
||||||
required this.frequency,
|
|
||||||
});
|
|
||||||
|
|
||||||
// bool get isOpen {
|
|
||||||
// for(var hours in hoursList) {
|
|
||||||
// var now = DateTime.now();
|
|
||||||
// var today = Weekday.values[now.weekday - 1];
|
|
||||||
// if (hours.daysOfWeek.days[today] == true) {
|
|
||||||
// var currentTime = Duration(hours: now.hour, minutes: now.minute, seconds: now.second);
|
|
||||||
// if(hours.startTime != null && hours.endTime != null) {
|
|
||||||
// if (hours.startTime! < currentTime && hours.endTime! > currentTime) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
factory Building.fromJson(Map<String, dynamic> json) {
|
|
||||||
return Building(
|
|
||||||
id: int.parse(json["buildingID"]),
|
|
||||||
name: json["name"],
|
|
||||||
shortName: json["nickname"],
|
|
||||||
category: json["category"],
|
|
||||||
location: json["location"],
|
|
||||||
mapLocation: LatLng(double.parse(json["latitude"]), double.parse(json["longitude"])),
|
|
||||||
frequency: int.parse(json["frequency"]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BuildingHours implements Comparable<BuildingHours> {
|
|
||||||
final int id;
|
|
||||||
final String? meal;
|
|
||||||
final TimeOfDay startTime;
|
|
||||||
final TimeOfDay endTime;
|
|
||||||
final String daysOfWeek;
|
|
||||||
final int dayOrder;
|
|
||||||
|
|
||||||
BuildingHours({
|
|
||||||
required this.id,
|
|
||||||
this.meal,
|
|
||||||
required this.startTime,
|
|
||||||
required this.endTime,
|
|
||||||
required this.daysOfWeek,
|
|
||||||
required this.dayOrder,
|
|
||||||
});
|
|
||||||
|
|
||||||
static TimeOfDay _getDurationFromString(String s) {
|
|
||||||
var timeComponents = s.split(":");
|
|
||||||
var hour = int.parse(timeComponents[0]);
|
|
||||||
var minute = int.parse(timeComponents[1]);
|
|
||||||
|
|
||||||
return TimeOfDay(hour: hour, minute: minute);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory BuildingHours.fromJson(Map<String, dynamic> json) {
|
|
||||||
if (json["Start"] == null || json["End"] == null) {
|
|
||||||
throw ArgumentError("Start and end time cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
return BuildingHours(
|
|
||||||
id: int.parse(json["buildingID"]),
|
|
||||||
meal: json["meal"],
|
|
||||||
startTime: _getDurationFromString(json["Start"]),
|
|
||||||
endTime: _getDurationFromString(json["End"]),
|
|
||||||
daysOfWeek: json["day"],
|
|
||||||
dayOrder: int.parse(json["dayorder"]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int compareTo(BuildingHours other) {
|
|
||||||
return dayOrder - other.dayOrder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
class DaysOfWeek {
|
|
||||||
final Weekday startDay;
|
|
||||||
final Weekday endDay;
|
|
||||||
final Map<Weekday, bool> days;
|
|
||||||
|
|
||||||
DaysOfWeek({
|
|
||||||
required this.startDay,
|
|
||||||
required this.endDay,
|
|
||||||
required this.days,
|
|
||||||
});
|
|
||||||
|
|
||||||
static Weekday _getDayFromShortName(String shortName) {
|
|
||||||
switch(shortName.toLowerCase()) {
|
|
||||||
case "mon":
|
|
||||||
return Weekday.monday;
|
|
||||||
case "tue":
|
|
||||||
return Weekday.tuesday;
|
|
||||||
case "wed":
|
|
||||||
return Weekday.wednesday;
|
|
||||||
case "thu":
|
|
||||||
return Weekday.thursday;
|
|
||||||
case "fri":
|
|
||||||
return Weekday.friday;
|
|
||||||
case "sat":
|
|
||||||
return Weekday.saturday;
|
|
||||||
case "sun":
|
|
||||||
return Weekday.sunday;
|
|
||||||
default:
|
|
||||||
throw "Invalid date short name.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
factory DaysOfWeek.parse(String s) {
|
|
||||||
final Map<Weekday, bool> days = { for (var e in Weekday.values) e : false };
|
|
||||||
|
|
||||||
// single day
|
|
||||||
if (!s.contains("-")) {
|
|
||||||
var day = _getDayFromShortName(s);
|
|
||||||
days.update(day, (val) => true);
|
|
||||||
return DaysOfWeek(startDay: day, endDay: day, days: days);
|
|
||||||
}
|
|
||||||
|
|
||||||
// date range
|
|
||||||
var dayNames = s.split("-");
|
|
||||||
final startDay = _getDayFromShortName(dayNames[0]);
|
|
||||||
final endDay = _getDayFromShortName(dayNames[1]);
|
|
||||||
int i = startDay.index;
|
|
||||||
|
|
||||||
// loop through to add all dates in date range to list
|
|
||||||
while(i != endDay.index) {
|
|
||||||
days.update(Weekday.values[i], (val) => true);
|
|
||||||
|
|
||||||
if (i < Weekday.values.length - 1) {
|
|
||||||
i++;
|
|
||||||
} else {
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
days.update(endDay, (val) => true);
|
|
||||||
|
|
||||||
return DaysOfWeek(
|
|
||||||
startDay: startDay,
|
|
||||||
endDay: endDay,
|
|
||||||
days: days,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Weekday {
|
|
||||||
monday,
|
|
||||||
tuesday,
|
|
||||||
wednesday,
|
|
||||||
thursday,
|
|
||||||
friday,
|
|
||||||
saturday,
|
|
||||||
sunday,
|
|
||||||
}
|
|
||||||
@@ -19,21 +19,10 @@ class EventsService {
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// If the server did return a 200 OK response,
|
// If the server did return a 200 OK response,
|
||||||
// then parse the JSON.
|
// then parse the JSON.
|
||||||
try {
|
|
||||||
final eventsJson = jsonDecode(response.body);
|
final eventsJson = jsonDecode(response.body);
|
||||||
if (eventsJson["results"] != null) {
|
|
||||||
return (eventsJson["results"] as List<dynamic>).map((event) =>
|
return (eventsJson["results"] as List<dynamic>).map((event) =>
|
||||||
AthleticsEvent.fromJson(event)
|
AthleticsEvent.fromJson(event)
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw
|
|
||||||
"Failed to parse athletics event data."
|
|
||||||
"\n"
|
|
||||||
"Exception: $e";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// If the server did not return a 200 OK response,
|
// If the server did not return a 200 OK response,
|
||||||
// then throw an exception.
|
// then throw an exception.
|
||||||
@@ -48,26 +37,14 @@ class EventsService {
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// If the server did return a 200 OK response,
|
// If the server did return a 200 OK response,
|
||||||
// then parse the JSON.
|
// then parse the JSON.
|
||||||
try {
|
|
||||||
final eventsJson = jsonDecode(response.body);
|
final eventsJson = jsonDecode(response.body);
|
||||||
if (eventsJson["results"] != null) {
|
|
||||||
return (eventsJson["results"] as List<dynamic>).map((event) =>
|
return (eventsJson["results"] as List<dynamic>).map((event) =>
|
||||||
ClpEvent.fromJson(event)
|
ClpEvent.fromJson(event)
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw
|
|
||||||
"Failed to parse CLP event data."
|
|
||||||
"\n"
|
|
||||||
"Exception: $e";
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// If the server did not return a 200 OK response,
|
// If the server did not return a 200 OK response,
|
||||||
// then throw an exception.
|
// then throw an exception.
|
||||||
throw Exception('Failed to load CLP events.');
|
throw Exception('Failed to load athletics events.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +162,7 @@ class ClpEvent implements Event {
|
|||||||
|
|
||||||
factory ClpEvent.fromJson(Map<String, dynamic> json) {
|
factory ClpEvent.fromJson(Map<String, dynamic> json) {
|
||||||
return ClpEvent(
|
return ClpEvent(
|
||||||
title: json["title"] ?? "",
|
title: json["title"],
|
||||||
startTime: DateTime.parse(json["date"] + " " + json["start"]),
|
startTime: DateTime.parse(json["date"] + " " + json["start"]),
|
||||||
endTime: DateTime.parse(json["date"] + " " + json["end"]),
|
endTime: DateTime.parse(json["date"] + " " + json["end"]),
|
||||||
location: json["location"],
|
location: json["location"],
|
||||||
|
|||||||
@@ -1,78 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
||||||
import 'package:furman_now/src/services/get_app/user/in_app_browser.dart';
|
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
class UserService {
|
class UserService {
|
||||||
final String servicesUrl = "https://services.get.cbord.com/GETServices/services/json";
|
|
||||||
final GetLoginInAppBrowser browser = GetLoginInAppBrowser();
|
|
||||||
|
|
||||||
void login() {
|
|
||||||
var options = InAppBrowserClassOptions(
|
|
||||||
crossPlatform: InAppBrowserOptions(
|
|
||||||
hideUrlBar: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
browser.loadStart.subscribe((args) async {
|
|
||||||
if (args!.url != null) {
|
|
||||||
bool success = await _getAuthSessionFromUrl(args.url!);
|
|
||||||
print(success);
|
|
||||||
if (success) {
|
|
||||||
browser.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.openUrlRequest(
|
|
||||||
urlRequest: URLRequest(
|
|
||||||
url: Uri.parse("https://get.cbord.com/furman/full/login.php?mobileapp=1"),
|
|
||||||
),
|
|
||||||
options: options,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _getAuthSessionFromUrl(Uri url) async {
|
|
||||||
print(url);
|
|
||||||
if (
|
|
||||||
url.origin == "https://get.cbord.com" &&
|
|
||||||
url.path.contains("mobileapp_login_validator.php")
|
|
||||||
) {
|
|
||||||
var sessionId = url.queryParameters["sessionId"];
|
|
||||||
if (sessionId != null) {
|
|
||||||
return _validateSession(sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _validateSession(String sessionId) async {
|
|
||||||
final response = await http.post(
|
|
||||||
Uri.parse("$servicesUrl/user"),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: """{
|
|
||||||
"method": "retrieve",
|
|
||||||
"params": {
|
|
||||||
"sessionId": "$sessionId"
|
|
||||||
}
|
|
||||||
}"""
|
|
||||||
);
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// If the server did return a 200 OK response,
|
|
||||||
// then parse the JSON.
|
|
||||||
final json = jsonDecode(response.body);
|
|
||||||
print(json);
|
|
||||||
// make sure there wasn't an exception and the data was actually loaded
|
|
||||||
return json["exception"] == null;
|
|
||||||
} else {
|
|
||||||
// If the server did not return a 200 OK response,
|
|
||||||
// then throw an exception.
|
|
||||||
print(response.statusCode);
|
|
||||||
throw Exception('Failed to load user info.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:external_app_launcher/external_app_launcher.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
|
|
||||||
class ContactsService {
|
|
||||||
static Future<Iterable<ContactInfo>> fetchContactInfo() async {
|
|
||||||
final response = await http
|
|
||||||
.get(Uri.parse("https://cs.furman.edu/~csdaemon/FUNow/contactsGet.php"));
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// If the server did return a 200 OK response,
|
|
||||||
// then parse the JSON.
|
|
||||||
final healthSafetyJson = jsonDecode(response.body);
|
|
||||||
return (healthSafetyJson["results"] as List<dynamic>).map((json) =>
|
|
||||||
ContactInfo.fromJson(json)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// If the server did not return a 200 OK response,
|
|
||||||
// then throw an exception.
|
|
||||||
throw Exception('Failed to load contact info.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ContactInfo {
|
|
||||||
int id;
|
|
||||||
int buildingId;
|
|
||||||
String name;
|
|
||||||
String? room;
|
|
||||||
String number;
|
|
||||||
|
|
||||||
ContactInfo({
|
|
||||||
required this.id,
|
|
||||||
required this.buildingId,
|
|
||||||
required this.name,
|
|
||||||
this.room,
|
|
||||||
required this.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
String get phone {
|
|
||||||
return number.replaceAllMapped(
|
|
||||||
RegExp(r'(\d{3})(\d{3})(\d+)'),
|
|
||||||
(Match m) => "(${m[1]}) ${m[2]}-${m[3]}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void launch() async {
|
|
||||||
var url = Uri(scheme: "tel", path: number);
|
|
||||||
if (await canLaunchUrl(url)) {
|
|
||||||
await launchUrl(
|
|
||||||
url,
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw "Could not launch $url";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
factory ContactInfo.fromJson(Map<String, dynamic> json) {
|
|
||||||
return ContactInfo(
|
|
||||||
id: int.parse(json["id"]),
|
|
||||||
buildingId: int.parse(json["buildingID"]),
|
|
||||||
name: json["name"],
|
|
||||||
room: (json["room"] as String).isNotEmpty ? json["room"] : null,
|
|
||||||
number: json["number"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:external_app_launcher/external_app_launcher.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_remix/flutter_remix.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
|
|
||||||
class HealthSafetyService {
|
|
||||||
static Future<Iterable<HealthSafetyInfo>> fetchHealthSafetyInfo() async {
|
|
||||||
final response = await http
|
|
||||||
.get(Uri.parse("https://cs.furman.edu/~csdaemon/FUNow/healthSafetyGet.php"));
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// If the server did return a 200 OK response,
|
|
||||||
// then parse the JSON.
|
|
||||||
final healthSafetyJson = jsonDecode(response.body);
|
|
||||||
return (healthSafetyJson["results"] as List<dynamic>).map((json) =>
|
|
||||||
HealthSafetyInfo.fromJson(json)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// If the server did not return a 200 OK response,
|
|
||||||
// then throw an exception.
|
|
||||||
throw Exception('Failed to load health and safety info.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HealthSafetyInfo {
|
|
||||||
int id;
|
|
||||||
String name;
|
|
||||||
HealthSafetyInfoType type;
|
|
||||||
String content;
|
|
||||||
IconData icon;
|
|
||||||
|
|
||||||
HealthSafetyInfo({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.type,
|
|
||||||
required this.content,
|
|
||||||
required this.icon,
|
|
||||||
});
|
|
||||||
|
|
||||||
String get linkText {
|
|
||||||
switch(type) {
|
|
||||||
case HealthSafetyInfoType.link:
|
|
||||||
return "External Link";
|
|
||||||
case HealthSafetyInfoType.app:
|
|
||||||
return "Open App";
|
|
||||||
case HealthSafetyInfoType.phone:
|
|
||||||
return content.replaceAllMapped(
|
|
||||||
RegExp(r'(\d{3})(\d{3})(\d+)'),
|
|
||||||
(Match m) => "(${m[1]}) ${m[2]}-${m[3]}"
|
|
||||||
);
|
|
||||||
case HealthSafetyInfoType.removed:
|
|
||||||
return "Removed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void launch() async {
|
|
||||||
if(
|
|
||||||
type == HealthSafetyInfoType.link ||
|
|
||||||
type == HealthSafetyInfoType.phone
|
|
||||||
) {
|
|
||||||
var url = (type == HealthSafetyInfoType.link)
|
|
||||||
? Uri.parse(content)
|
|
||||||
: Uri(scheme: "tel", path: content);
|
|
||||||
if (await canLaunchUrl(url)) {
|
|
||||||
await launchUrl(
|
|
||||||
url,
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw "Could not launch $url";
|
|
||||||
}
|
|
||||||
} else if (type == HealthSafetyInfoType.app) {
|
|
||||||
await LaunchApp.openApp(
|
|
||||||
androidPackageName: content.split("https://")[1],
|
|
||||||
iosUrlScheme: 'pulsesecure://',
|
|
||||||
appStoreLink: 'itms-apps://itunes.apple.com/us/app/pulse-secure/id945832041',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
factory HealthSafetyInfo.fromJson(Map<String, dynamic> json) {
|
|
||||||
getTypeFromString(String typeName) {
|
|
||||||
switch(typeName) {
|
|
||||||
case "phone":
|
|
||||||
return HealthSafetyInfoType.phone;
|
|
||||||
case "link":
|
|
||||||
return HealthSafetyInfoType.link;
|
|
||||||
case "app":
|
|
||||||
return HealthSafetyInfoType.app;
|
|
||||||
case "removed":
|
|
||||||
return HealthSafetyInfoType.removed;
|
|
||||||
default:
|
|
||||||
throw "Invalid health safety info type value \"$typeName\"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, IconData> iconMap = {
|
|
||||||
"shield": FlutterRemix.shield_star_line,
|
|
||||||
"staroflife": FlutterRemix.star_line,
|
|
||||||
"car": FlutterRemix.car_line,
|
|
||||||
"bandage.fill": Icons.healing,
|
|
||||||
"person.circle": FlutterRemix.user_line,
|
|
||||||
"heart.circle": FlutterRemix.heart_line,
|
|
||||||
};
|
|
||||||
|
|
||||||
return HealthSafetyInfo(
|
|
||||||
id: int.parse(json["id"]),
|
|
||||||
name: json["name"],
|
|
||||||
type: getTypeFromString(json["type"]),
|
|
||||||
content: json["content"],
|
|
||||||
icon: iconMap[json["icon"]] ?? FlutterRemix.heart_line,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HealthSafetyInfoType {
|
|
||||||
app,
|
|
||||||
phone,
|
|
||||||
link,
|
|
||||||
removed,
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:furman_now/src/services/days_of_week.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
|
||||||
|
|
||||||
class RestaurantService {
|
class RestaurantService {
|
||||||
static Future<List<Restaurant>> fetchRestaurants() async {
|
static Future<List<Restaurant>> fetchRestaurants() async {
|
||||||
@@ -27,7 +25,7 @@ class RestaurantService {
|
|||||||
} else {
|
} else {
|
||||||
// If the server did not return a 200 OK response,
|
// If the server did not return a 200 OK response,
|
||||||
// then throw an exception.
|
// then throw an exception.
|
||||||
throw Exception('Failed to load restaurants.');
|
throw Exception('Failed to load athletics events.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +38,7 @@ class RestaurantService {
|
|||||||
// then parse the JSON.
|
// then parse the JSON.
|
||||||
final hoursListJson = jsonDecode(response.body);
|
final hoursListJson = jsonDecode(response.body);
|
||||||
for (var hoursJson in (hoursListJson["results"] as List<dynamic>)) {
|
for (var hoursJson in (hoursListJson["results"] as List<dynamic>)) {
|
||||||
var hours = RestaurantHours.fromJson(hoursJson);
|
var hours = Hours.fromJson(hoursJson);
|
||||||
restaurants.firstWhere((restaurant) => restaurant.id == hours.id).hoursList.add(hours);
|
restaurants.firstWhere((restaurant) => restaurant.id == hours.id).hoursList.add(hours);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -60,8 +58,7 @@ class Restaurant {
|
|||||||
int frequency;
|
int frequency;
|
||||||
int busyness;
|
int busyness;
|
||||||
String? url;
|
String? url;
|
||||||
List<RestaurantHours> hoursList = <RestaurantHours>[];
|
List<Hours> hoursList = <Hours>[];
|
||||||
Color? backgroundColor;
|
|
||||||
|
|
||||||
Restaurant({
|
Restaurant({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -99,18 +96,6 @@ class Restaurant {
|
|||||||
return NetworkImage(imageUri);
|
return NetworkImage(imageUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _generateColor() async {
|
|
||||||
var paletteGenerator = await PaletteGenerator.fromImageProvider(
|
|
||||||
image,
|
|
||||||
maximumColorCount: 1,
|
|
||||||
);
|
|
||||||
if (paletteGenerator.colors.toList().isNotEmpty) {
|
|
||||||
var hslColor = HSLColor.fromColor(paletteGenerator.colors.toList()[0]);
|
|
||||||
var newColor = HSLColor.fromAHSL(1, hslColor.hue, 40/100, 90/100);
|
|
||||||
backgroundColor = newColor.toColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
factory Restaurant.fromJson(Map<String, dynamic> json) {
|
factory Restaurant.fromJson(Map<String, dynamic> json) {
|
||||||
return Restaurant(
|
return Restaurant(
|
||||||
id: int.parse(json["id"]),
|
id: int.parse(json["id"]),
|
||||||
@@ -125,7 +110,7 @@ class Restaurant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RestaurantHours {
|
class Hours {
|
||||||
int id;
|
int id;
|
||||||
String? meal;
|
String? meal;
|
||||||
Duration? startTime;
|
Duration? startTime;
|
||||||
@@ -133,7 +118,7 @@ class RestaurantHours {
|
|||||||
DaysOfWeek daysOfWeek;
|
DaysOfWeek daysOfWeek;
|
||||||
int dayOrder;
|
int dayOrder;
|
||||||
|
|
||||||
RestaurantHours({
|
Hours({
|
||||||
required this.id,
|
required this.id,
|
||||||
this.meal,
|
this.meal,
|
||||||
this.startTime,
|
this.startTime,
|
||||||
@@ -151,8 +136,8 @@ class RestaurantHours {
|
|||||||
return Duration(hours: hours, minutes: minutes, seconds: seconds);
|
return Duration(hours: hours, minutes: minutes, seconds: seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory RestaurantHours.fromJson(Map<String, dynamic> json) {
|
factory Hours.fromJson(Map<String, dynamic> json) {
|
||||||
return RestaurantHours(
|
return Hours(
|
||||||
id: int.parse(json["id"]),
|
id: int.parse(json["id"]),
|
||||||
meal: json["meal"],
|
meal: json["meal"],
|
||||||
startTime: (json["start"] != null) ? _getDurationFromString(json["start"]) : null,
|
startTime: (json["start"] != null) ? _getDurationFromString(json["start"]) : null,
|
||||||
@@ -162,3 +147,81 @@ class RestaurantHours {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DaysOfWeek {
|
||||||
|
final Weekday startDay;
|
||||||
|
final Weekday endDay;
|
||||||
|
final Map<Weekday, bool> days;
|
||||||
|
|
||||||
|
DaysOfWeek({
|
||||||
|
required this.startDay,
|
||||||
|
required this.endDay,
|
||||||
|
required this.days,
|
||||||
|
});
|
||||||
|
|
||||||
|
static Weekday _getDayFromShortName(String shortName) {
|
||||||
|
switch(shortName.toLowerCase()) {
|
||||||
|
case "mon":
|
||||||
|
return Weekday.monday;
|
||||||
|
case "tue":
|
||||||
|
return Weekday.tuesday;
|
||||||
|
case "wed":
|
||||||
|
return Weekday.wednesday;
|
||||||
|
case "thu":
|
||||||
|
return Weekday.thursday;
|
||||||
|
case "fri":
|
||||||
|
return Weekday.friday;
|
||||||
|
case "sat":
|
||||||
|
return Weekday.saturday;
|
||||||
|
case "sun":
|
||||||
|
return Weekday.sunday;
|
||||||
|
default:
|
||||||
|
throw "Invalid date short name.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
factory DaysOfWeek.parse(String s) {
|
||||||
|
final Map<Weekday, bool> days = { for (var e in Weekday.values) e : false };
|
||||||
|
|
||||||
|
// single day
|
||||||
|
if (!s.contains("-")) {
|
||||||
|
var day = _getDayFromShortName(s);
|
||||||
|
days.update(day, (val) => true);
|
||||||
|
return DaysOfWeek(startDay: day, endDay: day, days: days);
|
||||||
|
}
|
||||||
|
|
||||||
|
// date range
|
||||||
|
var dayNames = s.split("-");
|
||||||
|
final startDay = _getDayFromShortName(dayNames[0]);
|
||||||
|
final endDay = _getDayFromShortName(dayNames[1]);
|
||||||
|
int i = startDay.index;
|
||||||
|
|
||||||
|
// loop through to add all dates in date range to list
|
||||||
|
while(i != endDay.index) {
|
||||||
|
days.update(Weekday.values[i], (val) => true);
|
||||||
|
|
||||||
|
if (i < Weekday.values.length - 1) {
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
days.update(endDay, (val) => true);
|
||||||
|
|
||||||
|
return DaysOfWeek(
|
||||||
|
startDay: startDay,
|
||||||
|
endDay: endDay,
|
||||||
|
days: days,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Weekday {
|
||||||
|
monday,
|
||||||
|
tuesday,
|
||||||
|
wednesday,
|
||||||
|
thursday,
|
||||||
|
friday,
|
||||||
|
saturday,
|
||||||
|
sunday,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
|
||||||
import 'package:furman_now/src/widgets/map/route_marker.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
class TransportationRoute {
|
|
||||||
final Polyline route;
|
|
||||||
final List<Stop> stops;
|
|
||||||
|
|
||||||
TransportationRoute({
|
|
||||||
required this.route,
|
|
||||||
required this.stops,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory TransportationRoute.fromPoints({
|
|
||||||
required List<LatLng> points,
|
|
||||||
required Color color,
|
|
||||||
required List<Stop> stops,
|
|
||||||
}) {
|
|
||||||
var route = Polyline(
|
|
||||||
points: points,
|
|
||||||
color: color,
|
|
||||||
strokeWidth: 4,
|
|
||||||
);
|
|
||||||
return TransportationRoute(
|
|
||||||
route: route,
|
|
||||||
stops: stops,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Stop {
|
|
||||||
int id;
|
|
||||||
String name;
|
|
||||||
LatLng location;
|
|
||||||
|
|
||||||
Stop({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.location,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Stop.fromLiveSafeJson(Map<String, dynamic> json) {
|
|
||||||
return Stop(
|
|
||||||
id: json["AddressID"],
|
|
||||||
name: json["Description"],
|
|
||||||
location: LatLng(json["Latitude"], json["Longitude"]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Marker toMarker({required Color color}) {
|
|
||||||
return Marker(
|
|
||||||
point: location,
|
|
||||||
height: 100,
|
|
||||||
width: 100,
|
|
||||||
builder: (context) => RouteMarker(color: color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:furman_now/src/utils/decode_polyline.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
import 'route.dart';
|
|
||||||
|
|
||||||
class TransportationSafeRideShuttleService {
|
class TransportationSafeRideShuttleService {
|
||||||
static const service =
|
|
||||||
"https://furmansaferide.ridesystems.net/Services/JSONPRelay.svc";
|
|
||||||
|
|
||||||
static const apiKey = "8882812681";
|
|
||||||
|
|
||||||
static Future<http.Response> _serviceRequest(
|
|
||||||
String endpoint, Map<String, dynamic>? queryParameters) async {
|
|
||||||
Uri serviceUri = Uri.https(
|
|
||||||
'furmansaferide.ridesystems.net',
|
|
||||||
"/Services/JSONPRelay.svc/$endpoint",
|
|
||||||
{...?queryParameters, 'apiKey': apiKey});
|
|
||||||
return http.get(serviceUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<TransportationRoute> fetchShuttleRoute() async {
|
|
||||||
final response = await _serviceRequest(
|
|
||||||
'GetRoutesForMapWithScheduleWithEncodedLine', {'isDispatch': 'false'});
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// If the server did return a 200 OK response,
|
|
||||||
// then parse the JSON.
|
|
||||||
final json = jsonDecode(response.body);
|
|
||||||
String encodedPolyline = json[0]["EncodedPolyline"];
|
|
||||||
final points = decodeEncodedPolyline(encodedPolyline);
|
|
||||||
var stops = (json[0]["Stops"] as List<dynamic>)
|
|
||||||
.map((e) => Stop.fromLiveSafeJson(e))
|
|
||||||
.toList();
|
|
||||||
return TransportationRoute.fromPoints(
|
|
||||||
points: points,
|
|
||||||
color: Colors.red.shade300,
|
|
||||||
stops: stops,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// If the server did not return a 200 OK response,
|
|
||||||
// then throw an exception.
|
|
||||||
throw Exception('Failed to load SafeRide shuttle route.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
abstract class TransportationVehicle {
|
|
||||||
// name of the vehicle
|
|
||||||
String get name;
|
|
||||||
|
|
||||||
// is the vehicle currently running?
|
|
||||||
VehicleStatus get status;
|
|
||||||
|
|
||||||
// name of vehicle's next stop
|
|
||||||
String get nextStop;
|
|
||||||
|
|
||||||
// vehicle icon
|
|
||||||
IconData get icon;
|
|
||||||
|
|
||||||
// vehicle route
|
|
||||||
TransitionRoute get route;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VehicleStatus { running, stopped }
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import 'package:furman_now/secrets.dart';
|
|
||||||
import 'package:geolocator/geolocator.dart';
|
|
||||||
import 'package:weather/weather.dart';
|
|
||||||
|
|
||||||
class WeatherService {
|
|
||||||
static final WeatherFactory _factory = WeatherFactory(Secrets.openWeatherMapKey);
|
|
||||||
|
|
||||||
static Future<String> getWeatherMessage() async {
|
|
||||||
var position = await _determinePosition();
|
|
||||||
var weather = await _factory.currentWeatherByLocation(
|
|
||||||
position.latitude,
|
|
||||||
position.longitude,
|
|
||||||
);
|
|
||||||
return "It's ${weather.temperature?.fahrenheit?.round()}º with ${weather.weatherDescription}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine the current position of the device.
|
|
||||||
///
|
|
||||||
/// When the location services are not enabled or permissions
|
|
||||||
/// are denied the `Future` will return an error.
|
|
||||||
static Future<Position> _determinePosition() async {
|
|
||||||
bool serviceEnabled;
|
|
||||||
LocationPermission permission;
|
|
||||||
|
|
||||||
// Test if location services are enabled.
|
|
||||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
|
||||||
if (!serviceEnabled) {
|
|
||||||
// Location services are not enabled don't continue
|
|
||||||
// accessing the position and request users of the
|
|
||||||
// App to enable the location services.
|
|
||||||
return Future.error('Location services are disabled.');
|
|
||||||
}
|
|
||||||
|
|
||||||
permission = await Geolocator.checkPermission();
|
|
||||||
if (permission == LocationPermission.denied) {
|
|
||||||
permission = await Geolocator.requestPermission();
|
|
||||||
if (permission == LocationPermission.denied) {
|
|
||||||
// Permissions are denied, next time you could try
|
|
||||||
// requesting permissions again (this is also where
|
|
||||||
// Android's shouldShowRequestPermissionRationale
|
|
||||||
// returned true. According to Android guidelines
|
|
||||||
// your App should show an explanatory UI now.
|
|
||||||
return Future.error('Location permissions are denied');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permission == LocationPermission.deniedForever) {
|
|
||||||
// Permissions are denied forever, handle appropriately.
|
|
||||||
return Future.error(
|
|
||||||
'Location permissions are permanently denied, we cannot request permissions.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// When we reach here, permissions are granted and we can
|
|
||||||
// continue accessing the position of the device.
|
|
||||||
return await Geolocator.getCurrentPosition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:furman_now/src/services/buildings/buildings_service.dart';
|
|
||||||
import 'package:furman_now/src/services/events/event.dart';
|
import 'package:furman_now/src/services/events/event.dart';
|
||||||
import 'package:furman_now/src/services/events/events_service.dart';
|
import 'package:furman_now/src/services/events/events_service.dart';
|
||||||
import 'package:furman_now/src/services/restaurants/restaurant_service.dart';
|
import 'package:furman_now/src/services/restaurants/restaurant_service.dart';
|
||||||
@@ -7,7 +6,6 @@ import 'package:furman_now/src/services/restaurants/restaurant_service.dart';
|
|||||||
class AppState extends ChangeNotifier {
|
class AppState extends ChangeNotifier {
|
||||||
late Future<List<Event>> events;
|
late Future<List<Event>> events;
|
||||||
late Future<List<Restaurant>> restaurants;
|
late Future<List<Restaurant>> restaurants;
|
||||||
late Future<List<Building>> buildings;
|
|
||||||
|
|
||||||
AppState() {
|
AppState() {
|
||||||
refresh();
|
refresh();
|
||||||
@@ -16,15 +14,13 @@ class AppState extends ChangeNotifier {
|
|||||||
void refresh() {
|
void refresh() {
|
||||||
events = EventsService.fetchEvents();
|
events = EventsService.fetchEvents();
|
||||||
restaurants = RestaurantService.fetchRestaurants();
|
restaurants = RestaurantService.fetchRestaurants();
|
||||||
buildings = BuildingsService.fetchBuildings();
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
events.hashCode ^
|
events.hashCode ^
|
||||||
restaurants.hashCode ^
|
restaurants.hashCode;
|
||||||
buildings.hashCode;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@@ -32,6 +28,6 @@ class AppState extends ChangeNotifier {
|
|||||||
other is AppState &&
|
other is AppState &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
events == other.events &&
|
events == other.events &&
|
||||||
restaurants == other.restaurants &&
|
restaurants == other.restaurants;
|
||||||
buildings == other.buildings;
|
|
||||||
}
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
/// Conditionally wrap a subtree with a parent widget without breaking the code tree.
|
|
||||||
///
|
|
||||||
/// [condition]: the condition depending on which the subtree [child] is wrapped with the parent.
|
|
||||||
/// [child]: The subtree that should always be build.
|
|
||||||
/// [conditionalBuilder]: builds the parent with the subtree [child].
|
|
||||||
///
|
|
||||||
/// ___________
|
|
||||||
/// Usage:
|
|
||||||
/// ```dart
|
|
||||||
/// return ConditionalParentWidget(
|
|
||||||
/// condition: shouldIncludeParent,
|
|
||||||
/// child: Widget1(
|
|
||||||
/// child: Widget2(
|
|
||||||
/// child: Widget3(),
|
|
||||||
/// ),
|
|
||||||
/// ),
|
|
||||||
/// conditionalBuilder: (Widget child) => SomeParentWidget(child: child),
|
|
||||||
///);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ___________
|
|
||||||
/// Instead of:
|
|
||||||
/// ```dart
|
|
||||||
/// Widget child = Widget1(
|
|
||||||
/// child: Widget2(
|
|
||||||
/// child: Widget3(),
|
|
||||||
/// ),
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// return shouldIncludeParent ? SomeParentWidget(child: child) : child;
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
class ConditionalParentWidget extends StatelessWidget {
|
|
||||||
const ConditionalParentWidget({
|
|
||||||
required this.child,
|
|
||||||
required this.condition,
|
|
||||||
required this.conditionalBuilder,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final bool condition;
|
|
||||||
final Widget Function(Widget child) conditionalBuilder;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return condition ? conditionalBuilder(child) : child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
/// Decodes the an encoded polyline using the Encoded Polyline Algorithm Format
|
|
||||||
/// for more info about the algorithm check
|
|
||||||
/// https://developers.google.com/maps/documentation/utilities/polylinealgorithm
|
|
||||||
List<LatLng> decodeEncodedPolyline(String encoded) {
|
|
||||||
List<LatLng> poly = [];
|
|
||||||
int index = 0, len = encoded.length;
|
|
||||||
int lat = 0, lng = 0;
|
|
||||||
|
|
||||||
while (index < len) {
|
|
||||||
int b, shift = 0, result = 0;
|
|
||||||
do {
|
|
||||||
b = encoded.codeUnitAt(index++) - 63;
|
|
||||||
result |= (b & 0x1f) << shift;
|
|
||||||
shift += 5;
|
|
||||||
} while (b >= 0x20);
|
|
||||||
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
|
||||||
lat += dlat;
|
|
||||||
|
|
||||||
shift = 0;
|
|
||||||
result = 0;
|
|
||||||
do {
|
|
||||||
b = encoded.codeUnitAt(index++) - 63;
|
|
||||||
result |= (b & 0x1f) << shift;
|
|
||||||
shift += 5;
|
|
||||||
} while (b >= 0x20);
|
|
||||||
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
|
||||||
lng += dlng;
|
|
||||||
LatLng p = LatLng((lat / 1E5).toDouble(), (lng / 1E5).toDouble());
|
|
||||||
poly.add(p);
|
|
||||||
}
|
|
||||||
return poly;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
ThemeData _baseTheme = ThemeData(
|
ThemeData _baseTheme = ThemeData(
|
||||||
@@ -9,53 +8,26 @@ ThemeData _baseTheme = ThemeData(
|
|||||||
unselectedItemColor: Colors.grey[500],
|
unselectedItemColor: Colors.grey[500],
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: TextTheme(
|
||||||
headlineLarge: TextStyle(
|
headline1: TextStyle(
|
||||||
color: Colors.grey[800],
|
color: Colors.grey[800],
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
),
|
),
|
||||||
titleLarge: TextStyle(
|
subtitle1: TextStyle(
|
||||||
color: Colors.grey[800],
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
titleSmall: TextStyle(
|
|
||||||
color: Colors.grey[800],
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
labelLarge: TextStyle(
|
|
||||||
color: Colors.grey[500],
|
color: Colors.grey[500],
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
labelMedium: TextStyle(
|
subtitle2: TextStyle(
|
||||||
color: Colors.grey[500],
|
color: Colors.grey[500],
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pageTransitionsTheme: const PageTransitionsTheme(
|
|
||||||
builders: {
|
|
||||||
TargetPlatform.android:
|
|
||||||
CupertinoPageTransitionsBuilder(),
|
|
||||||
TargetPlatform.iOS:
|
|
||||||
CupertinoPageTransitionsBuilder(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ThemeData myFurmanTheme = _baseTheme.copyWith(
|
ThemeData myFurmanTheme = _baseTheme.copyWith(
|
||||||
textTheme: GoogleFonts.interTextTheme(_baseTheme.textTheme),
|
textTheme: GoogleFonts.interTextTheme(_baseTheme.textTheme),
|
||||||
);
|
);
|
||||||
|
|
||||||
var furmanTextStyle =
|
var furmanTextStyle = (TextStyle baseStyle) => GoogleFonts.inter(textStyle: baseStyle);
|
||||||
(TextStyle baseStyle) => GoogleFonts.inter(textStyle: baseStyle);
|
|
||||||
|
|
||||||
SystemUiOverlayStyle constructOverlayStyle({bool dark = false}) {
|
|
||||||
return SystemUiOverlayStyle(
|
|
||||||
statusBarColor: Colors.transparent,
|
|
||||||
systemNavigationBarContrastEnforced: true,
|
|
||||||
statusBarIconBrightness: dark ? Brightness.dark : Brightness.light,
|
|
||||||
statusBarBrightness: dark ? Brightness.light : Brightness.dark,
|
|
||||||
systemNavigationBarColor: Colors.grey.shade50);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class TranslucentRoute<T> extends TransitionRoute<T> {
|
|
||||||
final bool _opaque;
|
|
||||||
final Duration _transitionDuration;
|
|
||||||
final Widget Function(
|
|
||||||
BuildContext context,
|
|
||||||
Animation<double> animation,
|
|
||||||
Animation<double> secondaryAnimation,
|
|
||||||
Widget child
|
|
||||||
) _transitionBuilder;
|
|
||||||
final Widget Function(BuildContext) _pageBuilder;
|
|
||||||
|
|
||||||
TranslucentRoute({
|
|
||||||
opaque = true,
|
|
||||||
transitionDuration = const Duration(milliseconds: 300),
|
|
||||||
required Widget Function(
|
|
||||||
BuildContext context,
|
|
||||||
Animation<double> animation,
|
|
||||||
Animation<double> secondaryAnimation,
|
|
||||||
Widget child
|
|
||||||
) transitionBuilder,
|
|
||||||
required Widget Function(BuildContext) pageBuilder,
|
|
||||||
RouteSettings? settings,
|
|
||||||
}):
|
|
||||||
_opaque = opaque,
|
|
||||||
_transitionDuration = transitionDuration,
|
|
||||||
_transitionBuilder = transitionBuilder,
|
|
||||||
_pageBuilder = pageBuilder,
|
|
||||||
super(settings: settings);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<OverlayEntry> createOverlayEntries() {
|
|
||||||
return <OverlayEntry>[
|
|
||||||
OverlayEntry(
|
|
||||||
builder: (context) => _transitionBuilder(
|
|
||||||
context,
|
|
||||||
animation!,
|
|
||||||
secondaryAnimation!,
|
|
||||||
_pageBuilder(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get opaque => _opaque;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Duration get transitionDuration => _transitionDuration;
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:furman_now/src/services/events/event.dart';
|
import 'package:furman_now/src/services/events/event.dart';
|
||||||
import 'package:furman_now/src/utils/theme.dart';
|
import 'package:furman_now/src/utils/theme.dart';
|
||||||
import 'package:furman_now/src/widgets/events/event_modal.dart';
|
import 'package:furman_now/src/widgets/events/event_modal.dart';
|
||||||
import 'package:furman_now/src/widgets/text_with_icon.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class EventCard extends StatelessWidget {
|
class EventCard extends StatelessWidget {
|
||||||
@@ -34,6 +33,12 @@ class EventCard extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
event,
|
event,
|
||||||
);
|
);
|
||||||
|
// showModalBottomSheet<void>(
|
||||||
|
// context: context,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// return EventModal(event: event);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
@@ -48,11 +53,8 @@ class EventCard extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(eventHour, style: furmanTextStyle(TextStyle(
|
Text(eventHour, style: furmanTextStyle(const TextStyle(fontWeight: FontWeight.w700))),
|
||||||
color: Colors.grey[800],
|
Text(eventAmPm, style: Theme.of(context).textTheme.subtitle2),
|
||||||
fontWeight: FontWeight.w700
|
|
||||||
))),
|
|
||||||
Text(eventAmPm, style: Theme.of(context).textTheme.labelMedium),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -67,19 +69,35 @@ class EventCard extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(event.title, style: Theme.of(context).textTheme.titleSmall),
|
Text(event.title, style: furmanTextStyle(const TextStyle(fontWeight: FontWeight.w600))),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
TextWithIcon(
|
RichText(text: TextSpan(
|
||||||
icon: Icons.place_outlined,
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
text: event.location,
|
children: [
|
||||||
size: TextWithIconSize.small,
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 5.0),
|
||||||
|
child: Icon(Icons.place_outlined, size: 20, color: Colors.grey[500])
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: event.location),
|
||||||
|
],
|
||||||
|
)),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
TextWithIcon(
|
RichText(text: TextSpan(
|
||||||
icon: Icons.sell_outlined,
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
text: event.category,
|
children: [
|
||||||
size: TextWithIconSize.small,
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 1.0, right: 6.0),
|
||||||
|
child: Icon(Icons.sell_outlined, size: 18, color: Colors.grey[500])
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: event.category),
|
||||||
|
],
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:furman_now/src/services/events/event.dart';
|
import 'package:furman_now/src/services/events/event.dart';
|
||||||
import 'package:furman_now/src/services/events/events_service.dart';
|
import 'package:furman_now/src/services/events/events_service.dart';
|
||||||
import 'package:furman_now/src/widgets/text_with_icon.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
void showEventsModal(BuildContext context, Event event) {
|
void showEventsModal(BuildContext context, Event event) {
|
||||||
@@ -32,19 +31,16 @@ class _EventModalState extends State<_EventModal> {
|
|||||||
BoxConstraints? _parentConstraints;
|
BoxConstraints? _parentConstraints;
|
||||||
final _key = GlobalKey();
|
final _key = GlobalKey();
|
||||||
|
|
||||||
static const _maxDialogHeight = 0.8;
|
double _maxChildHeight = 0.75;
|
||||||
|
|
||||||
var _maxChildHeight = _maxDialogHeight;
|
|
||||||
|
|
||||||
void updateMaxHeight() {
|
void updateMaxHeight() {
|
||||||
if (_parentConstraints != null) {
|
if (_parentConstraints != null) {
|
||||||
var listHeight = _key.currentContext?.size?.height;
|
var listHeight = _key.currentContext?.size?.height;
|
||||||
var parentHeight = _parentConstraints?.maxHeight;
|
var parentHeight = _parentConstraints?.maxHeight;
|
||||||
if (listHeight != null && parentHeight != null) {
|
if (listHeight != null && parentHeight != null) {
|
||||||
var maxHeight = (listHeight + 70) / parentHeight;
|
var maxHeight = (listHeight + 50) / parentHeight;
|
||||||
setState(() {
|
setState(() {
|
||||||
_maxChildHeight = (maxHeight < _maxDialogHeight)
|
_maxChildHeight = (maxHeight < 0.75) ? maxHeight : 0.75;
|
||||||
? maxHeight : _maxDialogHeight;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,10 +78,8 @@ class _EventModalState extends State<_EventModal> {
|
|||||||
child: CloseButton(),
|
child: CloseButton(),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: NotificationListener
|
child: NotificationListener<OverscrollIndicatorNotification>(
|
||||||
<OverscrollIndicatorNotification>(
|
onNotification: (OverscrollIndicatorNotification overscroll) {
|
||||||
onNotification:
|
|
||||||
(OverscrollIndicatorNotification overscroll) {
|
|
||||||
overscroll.disallowIndicator();
|
overscroll.disallowIndicator();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -129,55 +123,120 @@ class _EventModalContent extends StatelessWidget {
|
|||||||
// Title
|
// Title
|
||||||
Text(
|
Text(
|
||||||
event.title,
|
event.title,
|
||||||
style: Theme.of(context).textTheme.headlineLarge,
|
style: Theme.of(context).textTheme.headline1,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// Location
|
// Location
|
||||||
TextWithIcon(
|
RichText(text: TextSpan(
|
||||||
icon: Icons.place_outlined,
|
style: Theme.of(context).textTheme.subtitle1,
|
||||||
text: event.location,
|
children: [
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 5.0),
|
||||||
|
child: Icon(
|
||||||
|
Icons.place_outlined, size: 24,
|
||||||
|
color: Colors.grey[500])
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: event.location),
|
||||||
|
],
|
||||||
|
)),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
// Additional info for CLP events
|
if (event is ClpEvent) ...[
|
||||||
if (event is ClpEvent) ...(() {
|
|
||||||
final clpEvent = event as ClpEvent;
|
|
||||||
return [
|
|
||||||
// Organization
|
// Organization
|
||||||
TextWithIcon(
|
RichText(text: TextSpan(
|
||||||
icon: Icons.group_outlined,
|
style: Theme.of(context).textTheme.subtitle1,
|
||||||
text: clpEvent.organization
|
children: [
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 5.0),
|
||||||
|
child: Icon(
|
||||||
|
Icons.group_outlined,
|
||||||
|
size: 24,
|
||||||
|
color: Colors.grey[500]
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: (event as ClpEvent).organization),
|
||||||
|
],
|
||||||
|
)),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
// Time
|
// Time
|
||||||
TextWithIcon(
|
RichText(text: TextSpan(
|
||||||
icon: Icons.access_time_outlined,
|
style: Theme.of(context).textTheme.subtitle1,
|
||||||
text:
|
children: [
|
||||||
"${DateFormat("EEEE, MMMM d").format(clpEvent.startTime)}"
|
WidgetSpan(
|
||||||
"\n"
|
alignment: PlaceholderAlignment.middle,
|
||||||
"${DateFormat.jm().format(clpEvent.startTime)} - ${DateFormat.jm().format(clpEvent.endTime)}",
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 5.0),
|
||||||
|
child: Icon(
|
||||||
|
Icons.access_time_outlined,
|
||||||
|
size: 24,
|
||||||
|
color: Colors.grey[500])
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.top,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment
|
||||||
|
.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat("EEEE, MMMM d")
|
||||||
|
.format(
|
||||||
|
(event as ClpEvent)
|
||||||
|
.startTime),
|
||||||
|
style: Theme
|
||||||
|
.of(context)
|
||||||
|
.textTheme
|
||||||
|
.subtitle1,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${DateFormat.jm().format(
|
||||||
|
(event as ClpEvent)
|
||||||
|
.startTime)} - ${DateFormat
|
||||||
|
.jm().format(
|
||||||
|
(event as ClpEvent)
|
||||||
|
.endTime)}",
|
||||||
|
style: Theme
|
||||||
|
.of(context)
|
||||||
|
.textTheme
|
||||||
|
.subtitle1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
// Description
|
// Description
|
||||||
TextWithIcon(
|
Row(
|
||||||
icon: Icons.notes,
|
crossAxisAlignment: CrossAxisAlignment
|
||||||
text: clpEvent.description
|
.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 5.0),
|
||||||
|
child: Icon(Icons.notes, size: 24,
|
||||||
|
color: Colors
|
||||||
|
.grey[500])
|
||||||
),
|
),
|
||||||
];
|
Flexible(
|
||||||
})(),
|
child: Text(
|
||||||
// Additional info for Athletics Events
|
(event as ClpEvent)
|
||||||
if (event is AthleticsEvent) ...(() {
|
.description,
|
||||||
final athleticsEvent = event as AthleticsEvent;
|
style: Theme
|
||||||
return [
|
.of(context)
|
||||||
// Time
|
.textTheme
|
||||||
TextWithIcon(
|
.subtitle1,
|
||||||
icon: Icons.access_time_outlined,
|
|
||||||
text:
|
|
||||||
"${DateFormat("EEEE, MMMM d").format(athleticsEvent.time)}"
|
|
||||||
"\n"
|
|
||||||
"${DateFormat.jm().format(athleticsEvent.time)}",
|
|
||||||
),
|
),
|
||||||
];
|
),
|
||||||
})(),
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,29 +8,24 @@ import 'event_card.dart';
|
|||||||
|
|
||||||
class EventsList extends StatefulWidget {
|
class EventsList extends StatefulWidget {
|
||||||
final DateTimeRange dateRange;
|
final DateTimeRange dateRange;
|
||||||
final int? limit;
|
|
||||||
|
|
||||||
const EventsList._({
|
const EventsList._({
|
||||||
required this.dateRange,
|
required this.dateRange,
|
||||||
this.limit,
|
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
factory EventsList({
|
factory EventsList({
|
||||||
DateTimeRange? dateRange,
|
DateTimeRange? dateRange,
|
||||||
int? limit,
|
|
||||||
Key? key,
|
Key? key,
|
||||||
}) {
|
}) {
|
||||||
if (dateRange == null) {
|
if (dateRange == null) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
// final today = DateTime(now.year, now.month, now.day);
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
final tonight = DateTime(now.year, now.month, now.day, 23, 59, 59);
|
final tonight = DateTime(now.year, now.month, now.day, 23, 59, 59);
|
||||||
// dateRange = DateTimeRange(start: today, end: tonight);
|
dateRange = DateTimeRange(start: today, end: tonight);
|
||||||
dateRange = DateTimeRange(start: now, end: tonight);
|
|
||||||
}
|
}
|
||||||
return EventsList._(
|
return EventsList._(
|
||||||
dateRange: dateRange,
|
dateRange: dateRange,
|
||||||
limit: limit,
|
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -53,9 +48,6 @@ class _EventsListState extends State<EventsList> {
|
|||||||
return event.time.isAfter(widget.dateRange.start) &&
|
return event.time.isAfter(widget.dateRange.start) &&
|
||||||
event.time.isBefore(widget.dateRange.end);
|
event.time.isBefore(widget.dateRange.end);
|
||||||
});
|
});
|
||||||
if (widget.limit != null) {
|
|
||||||
events = events.take(widget.limit!);
|
|
||||||
}
|
|
||||||
if (events.isNotEmpty) {
|
if (events.isNotEmpty) {
|
||||||
return Column(
|
return Column(
|
||||||
children: events.map((event) {
|
children: events.map((event) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class HeaderWidget extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.only(left: 40, right: 36, top: 20, bottom: 15),
|
padding: const EdgeInsets.only(left: 40, right: 40, top: 20, bottom: 15),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@@ -31,9 +31,6 @@ class HeaderWidget extends StatelessWidget {
|
|||||||
if (link != null)
|
if (link != null)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => context.router.navigate(link!.href),
|
onTap: () => context.router.navigate(link!.href),
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(6),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
link!.text,
|
link!.text,
|
||||||
style: furmanTextStyle(const TextStyle(
|
style: furmanTextStyle(const TextStyle(
|
||||||
@@ -43,7 +40,6 @@ class HeaderWidget extends StatelessWidget {
|
|||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class _RestaurantsListState extends State<RestaurantsList> {
|
|||||||
itemCount: restaurants.length,
|
itemCount: restaurants.length,
|
||||||
cacheExtent: 10000,
|
cacheExtent: 10000,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
physics: const BouncingScrollPhysics(),
|
|
||||||
prototypeItem: Padding(
|
prototypeItem: Padding(
|
||||||
padding: const EdgeInsets.only(right: 15),
|
padding: const EdgeInsets.only(right: 15),
|
||||||
child: RestaurantCard(restaurant: restaurants.first),
|
child: RestaurantCard(restaurant: restaurants.first),
|
||||||
|
|||||||
@@ -1,76 +1,46 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:furman_now/src/utils/conditional_parent_widget.dart';
|
|
||||||
import 'package:furman_now/src/widgets/text_with_icon.dart';
|
|
||||||
|
|
||||||
class InfoCardItem {
|
|
||||||
final String name;
|
|
||||||
final IconData icon;
|
|
||||||
|
|
||||||
InfoCardItem({
|
|
||||||
required this.name,
|
|
||||||
required this.icon,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class InfoCard extends StatelessWidget {
|
class InfoCard extends StatelessWidget {
|
||||||
final String title;
|
final Color color;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final List<InfoCardItem> items;
|
final String title;
|
||||||
final void Function()? onClick;
|
final String description;
|
||||||
|
|
||||||
const InfoCard({
|
const InfoCard({
|
||||||
required this.title,
|
required this.color,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.items,
|
required this.title,
|
||||||
this.onClick,
|
required this.description,
|
||||||
Key? key
|
Key? key
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ConditionalParentWidget(
|
return Container(
|
||||||
condition: onClick != null,
|
|
||||||
conditionalBuilder: (child) => GestureDetector(
|
|
||||||
onTap: onClick,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey.shade100,
|
color: color,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 15, right: 5),
|
padding: const EdgeInsets.only(left: 15, right: 5),
|
||||||
child: Icon(
|
child: Icon(icon, size: 40,)
|
||||||
icon,
|
|
||||||
size: 40,
|
|
||||||
color: Colors.grey.shade800,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(title, style: Theme.of(context).textTheme.titleSmall),
|
Text(title, style: Theme.of(context).textTheme.titleLarge,),
|
||||||
const SizedBox(height: 6),
|
Text(description),
|
||||||
...(items.map((item) =>
|
|
||||||
TextWithIcon(
|
|
||||||
icon: item.icon,
|
|
||||||
text: item.name,
|
|
||||||
size: TextWithIconSize.small,
|
|
||||||
)
|
|
||||||
).toList()),
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:furman_now/src/utils/theme.dart';
|
|
||||||
|
|
||||||
class InfoCategoryCard extends StatelessWidget {
|
|
||||||
final Color color;
|
|
||||||
final IconData icon;
|
|
||||||
final String title;
|
|
||||||
final String description;
|
|
||||||
|
|
||||||
const InfoCategoryCard({
|
|
||||||
required this.color,
|
|
||||||
required this.icon,
|
|
||||||
required this.title,
|
|
||||||
required this.description,
|
|
||||||
Key? key
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 15, right: 5),
|
|
||||||
child: Icon(
|
|
||||||
icon,
|
|
||||||
size: 40,
|
|
||||||
color: Colors.grey.shade800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 15,
|
|
||||||
vertical: 12,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(context).textTheme.titleLarge
|
|
||||||
),
|
|
||||||
const SizedBox(height: 1),
|
|
||||||
Text(
|
|
||||||
description,
|
|
||||||
style: furmanTextStyle(TextStyle(
|
|
||||||
color: Colors.grey.shade700,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,20 +4,16 @@ import 'package:furman_now/src/utils/theme.dart';
|
|||||||
class MapFilterChip extends StatelessWidget {
|
class MapFilterChip extends StatelessWidget {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String text;
|
final String text;
|
||||||
final Function callback;
|
|
||||||
|
|
||||||
const MapFilterChip({
|
const MapFilterChip({
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.callback,
|
|
||||||
Key? key
|
Key? key
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return Container(
|
||||||
onTapDown: (e) => callback(),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 6, bottom: 6, left: 8, right: 12),
|
top: 6, bottom: 6, left: 8, right: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -54,7 +50,6 @@ class MapFilterChip extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
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,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,25 +2,19 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class ScrollViewWithHeight extends StatelessWidget {
|
class ScrollViewWithHeight extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final ScrollController? controller;
|
|
||||||
|
|
||||||
const ScrollViewWithHeight({
|
const ScrollViewWithHeight({
|
||||||
required this.child,
|
required this.child,
|
||||||
this.controller,
|
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
return CustomScrollView(
|
return SingleChildScrollView(
|
||||||
controller: controller,
|
|
||||||
physics: const BouncingScrollPhysics(
|
physics: const BouncingScrollPhysics(
|
||||||
parent: AlwaysScrollableScrollPhysics(),
|
parent: AlwaysScrollableScrollPhysics()
|
||||||
),
|
),
|
||||||
slivers: [
|
|
||||||
SliverFillRemaining(
|
|
||||||
hasScrollBody: false,
|
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: constraints.copyWith(
|
constraints: constraints.copyWith(
|
||||||
minHeight: constraints.maxHeight,
|
minHeight: constraints.maxHeight,
|
||||||
@@ -28,8 +22,6 @@ class ScrollViewWithHeight extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
enum TextWithIconSize {
|
|
||||||
small,
|
|
||||||
large,
|
|
||||||
}
|
|
||||||
|
|
||||||
class TextWithIcon extends StatelessWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final String text;
|
|
||||||
final TextWithIconSize size;
|
|
||||||
|
|
||||||
const TextWithIcon({
|
|
||||||
super.key,
|
|
||||||
required this.icon,
|
|
||||||
required this.text,
|
|
||||||
this.size = TextWithIconSize.large,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 5.0),
|
|
||||||
child: Icon(
|
|
||||||
icon,
|
|
||||||
size: size == TextWithIconSize.large ? 24 : 20,
|
|
||||||
color: Colors.grey[500],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
style: size == TextWithIconSize.large
|
|
||||||
? Theme.of(context).textTheme.labelLarge
|
|
||||||
: Theme.of(context).textTheme.labelMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
770
pubspec.lock
770
pubspec.lock
File diff suppressed because it is too large
Load Diff
13
pubspec.yaml
13
pubspec.yaml
@@ -1,5 +1,5 @@
|
|||||||
name: furman_now
|
name: furman_now
|
||||||
description: The place for all things Furman.
|
description: A new Flutter project.
|
||||||
|
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||||
@@ -35,7 +35,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
english_words: ^4.0.0
|
english_words: ^4.0.0
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
google_fonts: ^4.0.4
|
google_fonts: ^3.0.1
|
||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
convert: ^3.0.2
|
convert: ^3.0.2
|
||||||
crypto: ^3.0.2
|
crypto: ^3.0.2
|
||||||
@@ -50,15 +50,6 @@ dependencies:
|
|||||||
salomon_bottom_bar: ^3.3.1
|
salomon_bottom_bar: ^3.3.1
|
||||||
flutter_remix: ^0.0.3
|
flutter_remix: ^0.0.3
|
||||||
provider: ^6.0.3
|
provider: ^6.0.3
|
||||||
weather: ^2.0.1
|
|
||||||
geolocator: ^9.0.1
|
|
||||||
flutter_map_marker_cluster: ^0.5.4
|
|
||||||
transparent_pointer: ^1.0.0
|
|
||||||
url_launcher: ^6.1.5
|
|
||||||
external_app_launcher: ^3.1.0
|
|
||||||
morphable_shape: ^1.6.4
|
|
||||||
flutter_inappwebview: ^5.6.0+2
|
|
||||||
event: ^2.1.2
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import "package:furman_now/src/services/transportation/saferide_shuttle.dart";
|
|
||||||
import "package:test/test.dart";
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test("shuttle status is fetched successfully", () async {
|
|
||||||
var route = await TransportationSafeRideShuttleService.fetchShuttleRoute();
|
|
||||||
|
|
||||||
// ensure polyline has points
|
|
||||||
expect(route.route.points.length, isNonZero);
|
|
||||||
// ensure stops are listed
|
|
||||||
expect(route.stops.length, isNonZero);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("shuttle route is fetched successfully", () async {
|
|
||||||
var route = await TransportationSafeRideShuttleService.fetchShuttleRoute();
|
|
||||||
|
|
||||||
// ensure polyline has points
|
|
||||||
expect(route.route.points.length, isNonZero);
|
|
||||||
// ensure stops are listed
|
|
||||||
expect(route.stops.length, isNonZero);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user