diff --git a/lib/components/beacon_card.dart b/lib/components/beacon_card.dart index 4cdb9526..e270d53d 100644 --- a/lib/components/beacon_card.dart +++ b/lib/components/beacon_card.dart @@ -167,7 +167,7 @@ class BeaconCustomWidgets { TextSpan( text: 'in ', style: TextStyle( - color: const Color(0xffb6b2df), + color: Color(0xffb6b2df), fontSize: 14.0, fontWeight: FontWeight.w400), ), diff --git a/lib/components/create_join_dialog.dart b/lib/components/create_join_dialog.dart index 40465539..f43cd470 100644 --- a/lib/components/create_join_dialog.dart +++ b/lib/components/create_join_dialog.dart @@ -12,9 +12,9 @@ class CreateJoinBeaconDialog { BuildContext context, HomeViewModel model, Function reloadList) { bool isSmallSized = MediaQuery.of(context).size.height < 800; model.resultingDuration = Duration(minutes: 30); - model.durationController = new TextEditingController(); - model.startsAtDate = new TextEditingController(); - model.startsAtTime = new TextEditingController(); + model.durationController = TextEditingController(); + model.startsAtDate = TextEditingController(); + model.startsAtTime = TextEditingController(); return showDialog( context: context, builder: (context) => GestureDetector( diff --git a/lib/components/dialog_boxes.dart b/lib/components/dialog_boxes.dart index 6824be78..ceb846ee 100644 --- a/lib/components/dialog_boxes.dart +++ b/lib/components/dialog_boxes.dart @@ -1,6 +1,7 @@ import 'package:beacon/components/hike_button.dart'; import 'package:beacon/locator.dart'; import 'package:beacon/utilities/constants.dart'; +import 'package:beacon/view_model/hike_screen_model.dart'; import 'package:flutter/material.dart'; import 'package:sizer/sizer.dart'; @@ -45,7 +46,14 @@ class DialogBoxes { ); } - static Future changeDurationDialog(BuildContext context) { + static Future changeDurationDialog( + BuildContext context, + HikeScreenViewModel model, + ) { + DateTime dateTime; + TimeOfDay timeOfDay; + var startsAtDate = TextEditingController(); + var startsAtTime = TextEditingController(); return showDialog( context: context, builder: (context) => Dialog( @@ -53,40 +61,182 @@ class DialogBoxes { borderRadius: BorderRadius.circular(10.0), ), child: Container( - height: 500, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Flexible( - child: Container( - color: kLightBlue, - child: Column( - children: [ - Text( - 'Change Beacon Duration', - style: TextStyle(color: kYellow, fontSize: 14.0), + SizedBox( + height: 2.h, + ), + Container( + child: Text( + 'Change End Date-Time', + style: TextStyle(color: kYellow, fontSize: 15.0), + ), + ), + SizedBox( + height: 2.h, + ), + Container( + color: kLightBlue, + height: 10.h, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: InkWell( + onTap: () async { + dateTime = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime(2100), + ); + startsAtDate.text = + dateTime.toString().substring(0, 10); + }, + child: TextFormField( + enabled: false, + controller: startsAtDate, + onChanged: (value) { + startsAtDate.text = + dateTime.toString().substring(0, 10); + }, + decoration: InputDecoration( + alignLabelWithHint: true, + errorStyle: TextStyle(color: Colors.red[800]), + floatingLabelBehavior: FloatingLabelBehavior.always, + labelText: 'End Date', + labelStyle: + TextStyle(fontSize: labelsize, color: kYellow), + hintStyle: + TextStyle(fontSize: hintsize, color: hintColor), + hintText: 'Choose end date', + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, ), - ], + ), ), ), ), SizedBox( - height: 3.h, + height: 2.h, ), - Flexible( - child: HikeButton( - buttonWidth: optbwidth, - text: 'Done', - textSize: 18.0, - textColor: Colors.white, - buttonColor: kYellow, - onTap: () { - // DateTime newTime = - // DateTime.now().add(newDuration); - // update time - Navigator.pop(context); - }), + Container( + height: 10.h, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: InkWell( + onTap: () async { + timeOfDay = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + startsAtTime.text = + timeOfDay.toString().substring(10, 15); + }, + child: TextFormField( + enabled: false, + controller: startsAtTime, + onChanged: (value) { + startsAtTime.text = + timeOfDay.toString().substring(10, 15); + }, + decoration: InputDecoration( + alignLabelWithHint: true, + errorStyle: TextStyle(color: Colors.red[800]), + floatingLabelBehavior: FloatingLabelBehavior.always, + labelText: 'End Time', + labelStyle: + TextStyle(fontSize: labelsize, color: kYellow), + hintStyle: + TextStyle(fontSize: hintsize, color: hintColor), + hintText: 'Choose End time', + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), + ), + ), + ), + color: kLightBlue, + ), + SizedBox( + height: 2.h, + ), + HikeButton( + buttonWidth: optbwidth, + text: 'Disable', + textSize: 16.0, + textColor: Colors.white, + buttonColor: kYellow, + onTap: () async { + await databaseFunctions.init(); + var timeNow = DateTime.now() + .add( + Duration( + seconds: 1, + ), + ) + .millisecondsSinceEpoch; + final updatedBeacon = + await databaseFunctions.changeBeaconDuration( + model.beacon.id, + timeNow, + ); + if (updatedBeacon != null) { + model.updateBeaconDuration( + timeNow, + ); + } + Navigator.pop(context, timeNow); + }, + ), + SizedBox( + height: 2.h, + ), + HikeButton( + buttonWidth: optbwidth, + text: 'Done', + textSize: 18.0, + textColor: Colors.white, + buttonColor: kYellow, + onTap: () async { + if (dateTime == null || timeOfDay == null) { + navigationService.showSnackBar("Enter date and time"); + return; + } + dateTime = DateTime( + dateTime.year, + dateTime.month, + dateTime.day, + timeOfDay.hour, + timeOfDay.minute, + ); + // localNotif.scheduleNotification(); + if (DateTime.fromMillisecondsSinceEpoch( + model.beacon.startsAt) + .isAfter(dateTime)) { + navigationService + .showSnackBar("Enter a valid date and time!!"); + return; + } + // DateTime newTime = + // DateTime.now().add(newDuration); + // update time + await databaseFunctions.init(); + final updatedBeacon = + await databaseFunctions.changeBeaconDuration( + model.beacon.id, + dateTime.millisecondsSinceEpoch, + ); + if (updatedBeacon != null) { + model.updateBeaconDuration( + dateTime.millisecondsSinceEpoch); + } + Navigator.pop(context, dateTime); + }, + ), + SizedBox( + height: 2.h, ), ], ), diff --git a/lib/components/hike_button.dart b/lib/components/hike_button.dart index 6344178f..7a7ef0a4 100644 --- a/lib/components/hike_button.dart +++ b/lib/components/hike_button.dart @@ -26,7 +26,7 @@ class HikeButton extends StatelessWidget { style: ElevatedButton.styleFrom( primary: buttonColor, shape: RoundedRectangleBorder( - borderRadius: new BorderRadius.circular(50.0), + borderRadius: BorderRadius.circular(50.0), side: BorderSide(color: borderColor)), ), child: Padding( diff --git a/lib/components/hike_screen_widget.dart b/lib/components/hike_screen_widget.dart index 4acb6c3d..0f4e2f50 100644 --- a/lib/components/hike_screen_widget.dart +++ b/lib/components/hike_screen_widget.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:beacon/components/dialog_boxes.dart'; import 'package:beacon/components/hike_button.dart'; import 'package:beacon/locator.dart'; import 'package:beacon/models/beacon/beacon.dart'; @@ -31,6 +32,7 @@ class HikeScreenWidget extends ChangeNotifier { static Widget shareButton(BuildContext context, String passkey) { return FloatingActionButton( + tooltip: 'Add people to the beacon!', onPressed: () { showDialog( context: context, @@ -104,6 +106,7 @@ class HikeScreenWidget extends ChangeNotifier { List beaconRoute, ) { return FloatingActionButton( + tooltip: 'Share image of the map!', heroTag: 'shareRouteTag', //had to pass this tag else we would get error since there will be two FAB in the same subtree with the same tag. onPressed: () async { @@ -240,14 +243,14 @@ class HikeScreenWidget extends ChangeNotifier { trailing: model.hikers[index].id == model.beacon.leader.id ? GestureDetector( onDoubleTap: () { - isLeader + !isLeader ? Fluttertoast.showToast( msg: 'Only beacon holder has access to change the duration') - //TODO: enable this once backend has updated. - //Commented, since we dont have the neccessary mutation atm on backend to change the duration. - // : DialogBoxes.changeDurationDialog(context); - : Container(); + : DialogBoxes.changeDurationDialog( + context, + model, + ); }, child: Icon( Icons.room, @@ -269,6 +272,25 @@ class HikeScreenWidget extends ChangeNotifier { ); } + static Widget changeDurationFAB( + BuildContext context, HikeScreenViewModel model) { + return FloatingActionButton( + tooltip: 'Change beacon\'s duration!', + heroTag: + 'changeDurationTag', //had to pass this tag else we would get error since there will be many FAB in the same subtree with the same tag. + onPressed: () async { + await DialogBoxes.changeDurationDialog( + context, + model, + ); + }, + backgroundColor: kYellow, + child: Icon( + Icons.alarm_outlined, + ), + ); + } + static void showCreateLandMarkDialogueDialog( BuildContext context, var landmarkFormKey, diff --git a/lib/queries/beacon.dart b/lib/queries/beacon.dart index 5f5b08a8..899eeb5e 100644 --- a/lib/queries/beacon.dart +++ b/lib/queries/beacon.dart @@ -78,6 +78,40 @@ class BeaconQueries { '''; } + String changeBeaconDuration(String id, int newExpiresAt) { + return ''' + mutation{ + changeBeaconDuration(newExpiresAt: $newExpiresAt, beaconID: "$id") + { + _id + title + shortcode + leader { + _id + name + } + location{ + lat + lon + } + followers { + _id + name + } + startsAt + expiresAt + landmarks { + title + location { + lat + lon + } + } + } + } + '''; + } + String updateLeaderLoc(String id, String lat, String lon) { return ''' mutation { diff --git a/lib/services/database_mutation_functions.dart b/lib/services/database_mutation_functions.dart index cf113ae2..b8f59e0f 100644 --- a/lib/services/database_mutation_functions.dart +++ b/lib/services/database_mutation_functions.dart @@ -31,6 +31,8 @@ class DataBaseMutationFunctions { const GraphQLError(message: 'Email address already exists'); GraphQLError wrongCredentials = const GraphQLError(message: 'Invalid credentials'); + GraphQLError beaconHasAlreadyExpired = const GraphQLError( + message: 'Beacon can not expire before it has started.'); bool encounteredExceptionOrError(OperationException exception, {bool showSnackBar = true}) { @@ -65,6 +67,12 @@ class DataBaseMutationFunctions { .showSnackBar("Account with this email already registered"); } return false; + } else if (exception.graphqlErrors[i].message == + beaconHasAlreadyExpired.message) { + if (showSnackBar) { + navigationService.showSnackBar(beaconHasAlreadyExpired.message); + } + return false; } } print("Something went wrong"); @@ -271,6 +279,30 @@ class DataBaseMutationFunctions { return null; } + Future changeBeaconDuration(String id, int newExpiresAt) async { + final QueryResult result = await clientAuth.mutate( + MutationOptions( + document: gql( + _beaconQuery.changeBeaconDuration( + id, + newExpiresAt, + ), + ), + ), + ); + if (result.hasException) { + navigationService.showSnackBar( + "Something went wrong: ${result.exception.graphqlErrors.first.message}"); + print("Something went wrong: ${result.exception}"); + } else if (result.data != null && result.isConcrete) { + final Beacon beacon = Beacon.fromJson( + result.data['changeBeaconDuration'] as Map, + ); + return beacon; + } + return null; + } + Future updateLeaderLoc(String id, LatLng latLng) async { final QueryResult result = await clientAuth.mutate(MutationOptions( document: gql(_beaconQuery.updateLeaderLoc( diff --git a/lib/utilities/indication_painter.dart b/lib/utilities/indication_painter.dart index a82219a5..609155bb 100644 --- a/lib/utilities/indication_painter.dart +++ b/lib/utilities/indication_painter.dart @@ -19,7 +19,7 @@ class TabIndicationPainter extends CustomPainter { this.dy = 25.0, this.pageController}) : super(repaint: pageController) { - painter = new Paint() + painter = Paint() ..color = kBlue ..style = PaintingStyle.fill; } @@ -33,16 +33,15 @@ class TabIndicationPainter extends CustomPainter { double pageOffset = pos.extentBefore / fullExtent; bool left2right = dxEntry < dxTarget; - Offset entry = new Offset(left2right ? dxEntry : dxTarget, dy); - Offset target = new Offset(left2right ? dxTarget : dxEntry, dy); + Offset entry = Offset(left2right ? dxEntry : dxTarget, dy); + Offset target = Offset(left2right ? dxTarget : dxEntry, dy); - Path path = new Path(); + Path path = Path(); path.addArc( - new Rect.fromCircle(center: entry, radius: radius), 0.5 * pi, 1 * pi); - path.addRect( - new Rect.fromLTRB(entry.dx, dy - radius, target.dx, dy + radius)); + Rect.fromCircle(center: entry, radius: radius), 0.5 * pi, 1 * pi); + path.addRect(Rect.fromLTRB(entry.dx, dy - radius, target.dx, dy + radius)); path.addArc( - new Rect.fromCircle(center: target, radius: radius), 1.5 * pi, 1 * pi); + Rect.fromCircle(center: target, radius: radius), 1.5 * pi, 1 * pi); canvas.translate(size.width * pageOffset, 0.0); canvas.drawShadow(path, kLightBlue, 3.0, true); diff --git a/lib/view_model/auth_screen_model.dart b/lib/view_model/auth_screen_model.dart index 4e27eb75..d6a47a8b 100644 --- a/lib/view_model/auth_screen_model.dart +++ b/lib/view_model/auth_screen_model.dart @@ -10,7 +10,7 @@ class AuthViewModel extends BaseModel { AutovalidateMode loginValidate = AutovalidateMode.disabled; AutovalidateMode signupValidate = AutovalidateMode.disabled; - final GlobalKey scaffoldKey = new GlobalKey(); + final GlobalKey scaffoldKey = GlobalKey(); final FocusNode emailLogin = FocusNode(); final FocusNode passwordLogin = FocusNode(); @@ -19,15 +19,15 @@ class AuthViewModel extends BaseModel { final FocusNode email = FocusNode(); final FocusNode name = FocusNode(); - TextEditingController loginEmailController = new TextEditingController(); - TextEditingController loginPasswordController = new TextEditingController(); + TextEditingController loginEmailController = TextEditingController(); + TextEditingController loginPasswordController = TextEditingController(); bool obscureTextLogin = true; bool obscureTextSignup = true; - TextEditingController signupEmailController = new TextEditingController(); - TextEditingController signupNameController = new TextEditingController(); - TextEditingController signupPasswordController = new TextEditingController(); + TextEditingController signupEmailController = TextEditingController(); + TextEditingController signupNameController = TextEditingController(); + TextEditingController signupPasswordController = TextEditingController(); PageController pageController = PageController(); diff --git a/lib/view_model/hike_screen_model.dart b/lib/view_model/hike_screen_model.dart index 23a5d7d4..8afc353a 100644 --- a/lib/view_model/hike_screen_model.dart +++ b/lib/view_model/hike_screen_model.dart @@ -43,7 +43,7 @@ class HikeScreenViewModel extends BaseModel { PolylinePoints polylinePoints = PolylinePoints(); final GlobalKey landmarkFormKey = GlobalKey(); ScrollController scrollController = ScrollController(); - Location loc = new Location(); + Location loc = Location(); GraphQLClient graphQlClient; PanelController panelController = PanelController(); final List mergedStreamSubscriptions = []; @@ -417,6 +417,14 @@ class HikeScreenViewModel extends BaseModel { setState(ViewState.idle); } + void updateBeaconDuration(int newExpiresAt) async { + beacon.expiresAt = newExpiresAt; + navigationService + .showSnackBar('Yay! Duration has been changed successfully.'); + notifyListeners(); + await hiveDb.putBeaconInBeaconBox(beacon.id, beacon); + } + Future createLandmark( var title, var loc, @@ -437,8 +445,6 @@ class HikeScreenViewModel extends BaseModel { )); beacon.landmarks.add(value); await hiveDb.putBeaconInBeaconBox(beacon.id, beacon); - print(hiveDb.beaconsBox.get(beacon.id).landmarks.length.toString() + - 'asdasdasd'); notifyListeners(); }); } diff --git a/lib/view_model/home_view_model.dart b/lib/view_model/home_view_model.dart index 5b6d658b..2b8cd1eb 100644 --- a/lib/view_model/home_view_model.dart +++ b/lib/view_model/home_view_model.dart @@ -19,9 +19,9 @@ class HomeViewModel extends BaseModel { bool hasStarted; //commenting out since its value isnt used anywhere. //TextEditingController _titleController = new TextEditingController(); - TextEditingController durationController = new TextEditingController(); - TextEditingController startsAtDate = new TextEditingController(); - TextEditingController startsAtTime = new TextEditingController(); + TextEditingController durationController = TextEditingController(); + TextEditingController startsAtDate = TextEditingController(); + TextEditingController startsAtTime = TextEditingController(); String enteredPasskey; createHikeRoom(Function reloadList) async { diff --git a/lib/views/auth_screen.dart b/lib/views/auth_screen.dart index 541fc61a..7aaedda4 100644 --- a/lib/views/auth_screen.dart +++ b/lib/views/auth_screen.dart @@ -117,12 +117,12 @@ class _AuthScreenState extends State } }, children: [ - new ConstrainedBox( + ConstrainedBox( constraints: const BoxConstraints.expand(), child: _buildSignIn(context, model), ), - new ConstrainedBox( + ConstrainedBox( constraints: const BoxConstraints.expand(), child: _buildSignUp(context, model), @@ -426,7 +426,7 @@ class _AuthScreenState extends State ), Container( // margin: EdgeInsets.only(top: 300.0), - decoration: new BoxDecoration( + decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(5.0)), ), child: HikeButton( diff --git a/lib/views/hike_screen.dart b/lib/views/hike_screen.dart index 1631d075..d10b1af0 100644 --- a/lib/views/hike_screen.dart +++ b/lib/views/hike_screen.dart @@ -154,9 +154,11 @@ class _HikeScreenState extends State { tilt: CAMERA_TILT, bearing: CAMERA_BEARING), onMapCreated: (GoogleMapController controller) { - setState(() { - model.mapController.complete(controller); - }); + setState( + () { + model.mapController.complete(controller); + }, + ); // setPolyline(); }, onTap: (loc) async { @@ -176,11 +178,12 @@ class _HikeScreenState extends State { }), ), Align( - alignment: Alignment(0.9, -0.98), - child: model.isBeaconExpired - ? Container() - : HikeScreenWidget.shareButton( - context, widget.beacon.shortcode)), + alignment: Alignment(0.9, -0.98), + child: model.isBeaconExpired + ? Container() + : HikeScreenWidget.shareButton( + context, widget.beacon.shortcode), + ), Align( alignment: Alignment(-0.9, -0.98), child: FloatingActionButton( @@ -199,14 +202,28 @@ class _HikeScreenState extends State { //show the routeSharebutton only when beacon is active(?) and mapcontroller is ready. Align( alignment: screenHeight > 800 - ? Alignment(0.9, -0.8) - : Alignment(0.9, -0.77), + ? Alignment(0.9, -0.82) + : Alignment(0.9, -0.79), child: AnimatedOpacity( duration: Duration(milliseconds: 500), opacity: model.mapController.isCompleted ? 1.0 : 0.0, - child: HikeScreenWidget.shareRouteButton(context, - model.beacon, model.mapController, model.route), + child: HikeScreenWidget.shareRouteButton( + context, + model.beacon, + model.mapController, + model.route, + ), + ), + ), + if (model.isLeader && !model.isBeaconExpired) + Align( + alignment: screenHeight > 800 + ? Alignment(0.9, -0.65) + : Alignment(0.9, -0.6), + child: HikeScreenWidget.changeDurationFAB( + context, + model, ), ), ], diff --git a/lib/views/home.dart b/lib/views/home.dart index aeca1d86..39bfd0c4 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -78,7 +78,7 @@ class _MainScreenState extends State with TickerProviderStateMixin { return WillPopScope( onWillPop: _onPopHome, child: BaseView(builder: (context, model, child) { - TabController tabController = new TabController(length: 2, vsync: this); + TabController tabController = TabController(length: 2, vsync: this); return model.isBusy ? LoadingScreen() : Scaffold( diff --git a/pubspec.lock b/pubspec.lock index b5fc2c8a..9ea8baac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -773,6 +773,13 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted + version: "2.0.5" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted version: "2.1.0" petitparser: dependency: transitive