Compare commits

..

1 Commits

58 changed files with 1224 additions and 3540 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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'

View File

@@ -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

View File

@@ -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;

View File

@@ -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"

View File

@@ -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>

View File

@@ -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,
),
),
),
],
),
),
),
),
);
}
}

View File

@@ -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);
}
},
), ),
), ),
); );

View File

@@ -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(
path: "",
page: HomePageHeader,
name: "HomeRoute",
transitionsBuilder: TransitionsBuilders.fadeIn,
),
CustomRoute(
path: "student-id",
page: StudentIdScreen,
transitionsBuilder: TransitionsBuilders.fadeIn,
),
]),
AutoRoute(path: "map", page: MapScreen, children: [
CustomRoute( CustomRoute(
path: "", path: "",
page: MapHomeScreen, page: HomeScreen,
customRouteBuilder: mapRouteBuilder, transitionsBuilder: TransitionsBuilders.noTransition,
), ),
CustomRoute( CustomRoute(
path: "category/:id", path: "student-id",
page: MapCategoryScreen, page: StudentIdScreen,
customRouteBuilder: mapRouteBuilder, transitionsBuilder: TransitionsBuilders.noTransition,
), ),
]), ]),
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 {}

View File

@@ -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';
}

View File

@@ -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,56 +11,98 @@ 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(
padding: const EdgeInsets.symmetric(vertical: 20), child: Stack(
child: Column( fit: StackFit.loose,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ SizedBox(
const HeaderWidget(title: "Today"), width: double.infinity,
Padding( height: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20), child: Align(
child: EventsList() alignment: Alignment.topLeft,
), child: Container(
const HeaderWidget(title: "Tomorrow"), padding: const EdgeInsets.symmetric(horizontal: 30),
Padding( width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20), height: 100,
child: EventsList(dateRange: constructDateRange( child: Column(
DateTime.now().add(const Duration(days: 1)), mainAxisAlignment: MainAxisAlignment.center,
DateTime.now().add(const Duration(days: 1)), crossAxisAlignment: CrossAxisAlignment.start,
)), children: [
), Wrap(
...[for(var i=2; i<7; i+=1) i].map((i) { crossAxisAlignment: WrapCrossAlignment.center,
var date = DateTime.now().add(Duration(days: i)); children: [
var dayName = DateFormat('EEEE').format(date); Icon(Icons.calendar_month_outlined, size: 35, color: Colors.grey[700]),
return Wrap( const SizedBox(width: 12),
children: [ Text("Events", style: furmanTextStyle(TextStyle(color: Colors.grey[900], fontSize: 28, fontWeight: FontWeight.w700))),
HeaderWidget(title: dayName), ],
Padding( ),
padding: const EdgeInsets.symmetric(horizontal: 20), ],
child: EventsList(dateRange: constructDateRange( ),
date,
date,
)),
), ),
], ),
); ),
}), ScrollViewWithHeight(
Center(child: child: Container(
Wrap( decoration: const BoxDecoration(
direction: Axis.vertical, color: Colors.white,
crossAxisAlignment: WrapCrossAlignment.center, borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
children: const [ ),
Text("Need more events?"), padding: const EdgeInsets.symmetric(vertical: 20),
Text("Syncdin"), margin: const EdgeInsets.only(top: 100),
Text("Athletics"), width: double.infinity,
Text("CLPs"), child: Column(
], crossAxisAlignment: CrossAxisAlignment.start,
), children: [
), const HeaderWidget(title: "Today"),
], Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: EventsList()
),
const HeaderWidget(title: "Tomorrow"),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: EventsList(dateRange: constructDateRange(
DateTime.now().add(const Duration(days: 1)),
DateTime.now().add(const Duration(days: 1)),
)),
),
...[for(var i=2; i<7; i+=1) i].map((i) {
var date = DateTime.now().add(Duration(days: i));
var dayName = DateFormat('EEEE').format(date);
return Wrap(
children: [
HeaderWidget(title: dayName),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: EventsList(dateRange: constructDateRange(
date,
date,
)),
),
],
);
}),
Center(child:
Wrap(
direction: Axis.vertical,
crossAxisAlignment: WrapCrossAlignment.center,
children: const [
Text("Need more events?"),
Text("Syncdin"),
Text("Athletics"),
Text("CLPs"),
],
),
),
],
),
),
),
],
),
), ),
), ),
); );

View File

@@ -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(),
),
],
),
),
],
),
),
),
),
);
}
}

View File

@@ -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
)),
),
),
),
],
),
),
),
);
}
}

View File

@@ -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,117 +17,151 @@ 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(), color: const Color(0xffb7acc9),
child: Container( child: SafeArea(
color: const Color(0xffb7acc9), child: Container(
child: SafeArea( color: Colors.grey[100],
child: Container( child: Stack(
color: const Color(0xffb7acc9), fit: StackFit.loose,
child: Stack( children: [
fit: StackFit.loose, Container(
children: [ width: double.infinity,
const AutoTabsRouter( height: 300,
routes: [ decoration: const BoxDecoration(
HomeRoute(), gradient: LinearGradient(
StudentIdRoute(), begin: Alignment.topCenter,
], end: Alignment.bottomCenter,
colors: <Color>[
Color(0xffb7acc9),
Color(0xffb7acc9),
],
tileMode: TileMode.mirror,
),
), ),
// overscroll indicator color child: Align(
Align( alignment: Alignment.topLeft,
alignment: Alignment.bottomCenter,
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,
)),
),
),
),
const SizedBox(height: 5),
AnimatedCrossFade(
crossFadeState: _showScrollMessage,
duration: const Duration(milliseconds: 100),
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)
),
),
),
],
), ),
), ),
), ),
Consumer<HomePageState>( ),
builder: (context, state, _) => NotificationListener<ScrollNotification>(
ClipRect(child: HomeContent( onNotification: (notification) {
controller: _controller, if (notification is ScrollUpdateNotification) {
collapse: state.collapse, 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(),
),
],
),
),
), ),
), ),
), ),
], ),
), ],
), ),
), ),
), ),
@@ -145,3 +169,4 @@ class _HomeScreenState extends State<HomeScreen> {
); );
} }
} }

View File

@@ -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();
}
}
}

View File

@@ -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()
),
);
},
),
)
);
}
}

View File

@@ -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()
),
);
},
),
],
),
)
);
}
}

View File

@@ -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()
),
);
},
);
}
),
),
);
}
}

View File

@@ -1,64 +1,84 @@
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(
child: Column( fit: StackFit.loose,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ SizedBox(
GestureDetector( width: double.infinity,
onTap: () => context.router.push(const HealthSafetyRoute()), height: double.infinity,
child: InfoCategoryCard( child: Align(
color: Colors.red.shade50, alignment: Alignment.topLeft,
icon: Icons.local_hospital, child: Container(
title: "Health and Safety", padding: const EdgeInsets.symmetric(horizontal: 30),
description: "Important contact information and links regarding student health and safety.", 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(
const SizedBox(height: 10), child: Container(
GestureDetector( decoration: const BoxDecoration(
onTap: () => context.router.push(const ContactsRoute()), color: Colors.white,
child: InfoCategoryCard( borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
color: Colors.deepPurple.shade50, ),
icon: Icons.phone, padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
title: "Contacts", margin: const EdgeInsets.only(top: 100),
description: "Contact information for offices and resources on Furman's campus.", width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoCard(
color: Colors.red.shade50,
icon: Icons.local_hospital,
title: "Health and Safety",
description: "Important contact information and links regarding student health and safety.",
),
const SizedBox(height: 10),
InfoCard(
color: Colors.deepPurple.shade50,
icon: Icons.phone,
title: "Contacts",
description: "Important contact information and links regarding student health and safety.",
),
const SizedBox(height: 10),
InfoCard(
color: Colors.blue.shade50,
icon: Icons.access_time,
title: "Hours",
description: "Important contact information and links regarding student health and safety.",
),
],
),
),
), ),
), ],
const SizedBox(height: 10), ),
GestureDetector(
onTap: () => context.router.push(const HoursRoute()),
child: InfoCategoryCard(
color: Colors.blue.shade50,
icon: Icons.access_time,
title: "Hours",
description: "Check the hours for buildings and services on Furman's campus.",
),
),
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",
),
),
],
), ),
), ),
); );

View File

@@ -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), body: Container(
builder: (context, _) => WillPopScope( color: const Color(0xffb7acc9),
onWillPop: () async { child: SafeArea(
print("Will pop"); top: false,
return false; child: Stack(
}, children: [
child: Scaffold( FlutterMap(
body: Container( mapController: _mapController,
color: const Color(0xffb7acc9), options: MapOptions(
child: SafeArea( center: LatLng(34.925926, -82.439397),
top: false, enableMultiFingerGestureRace: true,
// child: MapWidget(), rotationWinGestures: MultiFingerGesture.all,
child: Stack( pinchZoomThreshold: 0.2,
children: const [ rotationThreshold: 8,
MapWidget(), zoom: 15,
TransparentPointer(transparent: true, child: AutoRouter()), 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,
),
)
)
],
), ),
), ),
), ),

View File

@@ -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,
),
)
),
],
),
);
}
}

View File

@@ -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,
),
),
),
],
),
);
}
}

View File

@@ -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,
)),
),
),
);
},
),
],
),
],
),
),
);
}
}

View File

@@ -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();
}
}

View File

@@ -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."),
),
)
],
),
),
);
}
}

View File

@@ -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,93 +43,45 @@ 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 { onPointerMove: (details) {
context.read<HomePageState>().collapse = false; int sensitivity = 8;
return true; if (details.delta.dy > sensitivity) {
// Down Swipe
} else if (details.delta.dy < -sensitivity) {
context.router.navigate(const MainLayout());
}
}, },
child: Listener( child: Container(
onPointerMove: (details) { color: const Color(0xffb7acc9),
int sensitivity = 8; child: SafeArea(
if (details.delta.dy > sensitivity) { child: ListView(
// Down Swipe padding: const EdgeInsets.all(40),
} else if (details.delta.dy < -sensitivity) { children: [
context.router.navigate(HomeRoute()); Text(
} "Furman ID",
}, style: furmanTextStyle(const TextStyle(color: Color(0xff26183d), fontSize: 36, fontWeight: FontWeight.w800)),
child: Stack( ),
children: [ const SizedBox(height: 200),
Container( Container(
color: const Color(0xffb7acc9), decoration: const BoxDecoration(
child: SafeArea( color: Colors.white,
child: ListView( borderRadius: BorderRadius.all(Radius.circular(10)),
padding: const EdgeInsets.all(40), ),
children: [ // hack since the barcode has a weird intrinsic size for some reason
Text( child: LayoutBuilder(
"Furman ID", builder: (BuildContext context, BoxConstraints constraints) {
style: furmanTextStyle(const TextStyle( return BarcodeWidget(
color: Color(0xff26183d), barcode: Barcode.pdf417(moduleHeight: 4),
fontSize: 36, data: barcodeNumber,
fontWeight: FontWeight.w800 margin: const EdgeInsets.all(10),
)), height: constraints.maxWidth / 3,
), );
const SizedBox(height: 30), },
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(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
// hack since the barcode has a weird intrinsic size for some reason
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return BarcodeWidget(
barcode: Barcode.pdf417(moduleHeight: 4),
data: barcodeNumber,
margin: const EdgeInsets.all(10),
height: constraints.maxWidth / 3,
);
},
),
),
],
), ),
), ),
), ],
], ),
), ),
), ),
), ),

View File

@@ -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;
}
}

View File

@@ -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,
}

View File

@@ -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); return (eventsJson["results"] as List<dynamic>).map((event) =>
if (eventsJson["results"] != null) { AthleticsEvent.fromJson(event)
return (eventsJson["results"] as List<dynamic>).map((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); return (eventsJson["results"] as List<dynamic>).map((event) =>
if (eventsJson["results"] != null) { ClpEvent.fromJson(event)
return (eventsJson["results"] as List<dynamic>).map((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"],

View File

@@ -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.');
}
}
} }

View File

@@ -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"],
);
}
}

View File

@@ -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,
}

View File

@@ -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 {
@@ -15,7 +13,7 @@ class RestaurantService {
static Future<Iterable<Restaurant>> _fetchRestaurants() async { static Future<Iterable<Restaurant>> _fetchRestaurants() async {
final response = await http final response = await http
.get(Uri.parse("https://cs.furman.edu/~csdaemon/FUNow/restaurantGet.php")); .get(Uri.parse("https://cs.furman.edu/~csdaemon/FUNow/restaurantGet.php"));
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,
@@ -27,20 +25,20 @@ 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.');
} }
} }
static Future<void> _fetchRestaurantHours(List<Restaurant> restaurants) async { static Future<void> _fetchRestaurantHours(List<Restaurant> restaurants) async {
final response = await http final response = await http
.get(Uri.parse("https://cs.furman.edu/~csdaemon/FUNow/restaurantHoursGet.php")); .get(Uri.parse("https://cs.furman.edu/~csdaemon/FUNow/restaurantHoursGet.php"));
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.
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,
}

View File

@@ -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),
);
}
}

View File

@@ -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.');
}
}
} }

View File

@@ -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 }

View File

@@ -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();
}
}

View File

@@ -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,22 +14,20 @@ 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) =>
identical(this, other) || identical(this, other) ||
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;
} }

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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),
],
)),
], ],
), ),
), ),

View File

@@ -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,13 +78,11 @@ class _EventModalState extends State<_EventModal> {
child: CloseButton(), child: CloseButton(),
), ),
Flexible( Flexible(
child: NotificationListener child: NotificationListener<OverscrollIndicatorNotification>(
<OverscrollIndicatorNotification>( onNotification: (OverscrollIndicatorNotification overscroll) {
onNotification: overscroll.disallowIndicator();
(OverscrollIndicatorNotification overscroll) { return true;
overscroll.disallowIndicator(); },
return true;
},
child: _EventModalContent( child: _EventModalContent(
key: _key, key: _key,
controller: controller, controller: controller,
@@ -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) ...(() { // Organization
final clpEvent = event as ClpEvent; RichText(text: TextSpan(
return [ style: Theme.of(context).textTheme.subtitle1,
// Organization children: [
TextWithIcon( WidgetSpan(
icon: Icons.group_outlined, alignment: PlaceholderAlignment.middle,
text: clpEvent.organization child: Padding(
), padding: const EdgeInsets.only(right: 5.0),
const SizedBox(height: 5), child: Icon(
// Time Icons.group_outlined,
TextWithIcon( size: 24,
icon: Icons.access_time_outlined, color: Colors.grey[500]
text: ),
"${DateFormat("EEEE, MMMM d").format(clpEvent.startTime)}" ),
"\n" ),
"${DateFormat.jm().format(clpEvent.startTime)} - ${DateFormat.jm().format(clpEvent.endTime)}", TextSpan(text: (event as ClpEvent).organization),
), ],
const SizedBox(height: 5), )),
// Description const SizedBox(height: 5),
TextWithIcon( // Time
icon: Icons.notes, RichText(text: TextSpan(
text: clpEvent.description style: Theme.of(context).textTheme.subtitle1,
), children: [
]; WidgetSpan(
})(), alignment: PlaceholderAlignment.middle,
// Additional info for Athletics Events child: Padding(
if (event is AthleticsEvent) ...(() { padding: const EdgeInsets.only(
final athleticsEvent = event as AthleticsEvent; right: 5.0),
return [ child: Icon(
// Time Icons.access_time_outlined,
TextWithIcon( size: 24,
icon: Icons.access_time_outlined, color: Colors.grey[500])
text: ),
"${DateFormat("EEEE, MMMM d").format(athleticsEvent.time)}" ),
"\n" WidgetSpan(
"${DateFormat.jm().format(athleticsEvent.time)}", 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),
// Description
Row(
crossAxisAlignment: CrossAxisAlignment
.start,
children: [
Padding(
padding: const EdgeInsets.only(
right: 5.0),
child: Icon(Icons.notes, size: 24,
color: Colors
.grey[500])
),
Flexible(
child: Text(
(event as ClpEvent)
.description,
style: Theme
.of(context)
.textTheme
.subtitle1,
),
),
],
),
]
], ],
); );
} }

View File

@@ -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,
); );
} }
@@ -50,24 +45,21 @@ class _EventsListState extends State<EventsList> {
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
var events = snapshot.data!.where((event) { var events = snapshot.data!.where((event) {
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) { if (events.isNotEmpty) {
events = events.take(widget.limit!); return Column(
} children: events.map((event) {
if (events.isNotEmpty) { return Padding(
return Column( padding: const EdgeInsets.only(bottom: 15),
children: events.map((event) { child: EventCard(event),
);
}).toList());
} else {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 15), padding: const EdgeInsets.only(bottom: 15),
child: EventCard(event), child: SizedBox(
);
}).toList());
} else {
return Padding(
padding: const EdgeInsets.only(bottom: 15),
child: SizedBox(
width: double.infinity, width: double.infinity,
height: 50, height: 50,
child: Center( child: Center(

View File

@@ -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,17 +31,13 @@ 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: Text(
child: Padding( link!.text,
padding: const EdgeInsets.all(6), style: furmanTextStyle(const TextStyle(
child: Text( color: Color(0xff755898),
link!.text, fontSize: 12,
style: furmanTextStyle(const TextStyle( fontWeight: FontWeight.bold,
color: Color(0xff755898), )),
fontSize: 12,
fontWeight: FontWeight.bold,
)),
),
), ),
), ),
], ],

View File

@@ -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),

View File

@@ -1,75 +1,45 @@
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, decoration: BoxDecoration(
conditionalBuilder: (child) => GestureDetector( color: color,
onTap: onClick, borderRadius: BorderRadius.circular(10),
child: child,
), ),
child: Container( child: Row(
decoration: BoxDecoration( children: [
color: Colors.grey.shade100, Padding(
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [
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, Flexible(
color: Colors.grey.shade800, child: Container(
), padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
), child: Column(
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12),
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()),
] ]
),
), ),
) ),
], )
), ],
), ),
); );
} }

View File

@@ -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,
)),
),
],
),
),
)
],
),
);
}
}

View File

@@ -4,56 +4,51 @@ 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(), padding: const EdgeInsets.only(
child: Container( top: 6, bottom: 6, left: 8, right: 12),
padding: const EdgeInsets.only( decoration: BoxDecoration(
top: 6, bottom: 6, left: 8, right: 12), color: Colors.white,
decoration: BoxDecoration( borderRadius: BorderRadius.circular(100),
color: Colors.white, boxShadow: const [
borderRadius: BorderRadius.circular(100), BoxShadow(
boxShadow: const [ color: Color(0x33000000),
BoxShadow( blurRadius: 4,
color: Color(0x33000000), ),
blurRadius: 4, ],
),
child: RichText(
text: TextSpan(
children: [
WidgetSpan(
child: Icon(icon,
size: 18,
color: Colors.grey.shade800),
alignment: PlaceholderAlignment.middle,
),
const WidgetSpan(child: SizedBox(width: 6)),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Text(
text,
style: furmanTextStyle(TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
)),
),
), ),
], ],
), ),
child: RichText(
text: TextSpan(
children: [
WidgetSpan(
child: Icon(icon,
size: 18,
color: Colors.grey.shade800),
alignment: PlaceholderAlignment.middle,
),
const WidgetSpan(child: SizedBox(width: 6)),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Text(
text,
style: furmanTextStyle(TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
)),
),
),
],
),
),
), ),
); );
} }

View File

@@ -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,
)),
),
],
),
),
),
],
),
),
);
}
}

View File

@@ -2,34 +2,26 @@ 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: [ child: ConstrainedBox(
SliverFillRemaining( constraints: constraints.copyWith(
hasScrollBody: false, minHeight: constraints.maxHeight,
child: ConstrainedBox( maxHeight: double.infinity
constraints: constraints.copyWith(
minHeight: constraints.maxHeight,
maxHeight: double.infinity
),
child: child,
),
), ),
], child: child,
),
); );
}); });
} }

View File

@@ -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,
),
),
],
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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:

View File

@@ -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);
});
}