From c3dcbeb3304fb23d1e5d5a95dcf9272bb646d40d Mon Sep 17 00:00:00 2001 From: Aditya Kumar Singh Date: Tue, 18 Oct 2022 00:05:24 +0530 Subject: [PATCH] feat: N beacon 1 Group (#169) * model changes * added group in models * queries updated * queries updated * model changes * service changes * constants updated * yaml file updated * routing changes * readme updates * debugged model changes * queries updated * components added * mutation functions primary * view model additions * screen additions * flutter analyze error resolved * showing user beacons in group screen * back from hike to group screen * todo navigation changes * flutter analyze issues * Flutter version Updated minimum required Flutter version in Readme --- README.md | 5 +- lib/components/create_join_dialog.dart | 156 ++++++- lib/components/dialog_boxes.dart | 11 +- lib/components/group_card.dart | 182 ++++++++ lib/locator.dart | 4 +- lib/models/beacon/beacon.dart | 10 +- lib/models/beacon/beacon.g.dart | 7 +- lib/models/group/group.dart | 59 +++ lib/models/group/group.g.dart | 56 +++ lib/models/user/user_info.dart | 13 +- lib/models/user/user_info.g.dart | 11 +- lib/queries/auth.dart | 16 + lib/queries/beacon.dart | 27 +- lib/queries/group.dart | 143 ++++++ lib/router.dart | 9 +- lib/services/database_mutation_functions.dart | 142 +++++- lib/services/hive_localdb.dart | 7 +- lib/utilities/constants.dart | 1 + ...odel.dart => group_screen_view_model.dart} | 13 +- lib/view_model/home_screen_view_model.dart | 72 +++ lib/views/group_screen.dart | 409 ++++++++++++++++++ lib/views/hike_screen.dart | 4 +- lib/views/{home.dart => home_screen.dart} | 125 +----- pubspec.yaml | 3 +- 24 files changed, 1334 insertions(+), 151 deletions(-) create mode 100644 lib/components/group_card.dart create mode 100644 lib/models/group/group.dart create mode 100644 lib/models/group/group.g.dart create mode 100644 lib/queries/group.dart rename lib/view_model/{home_view_model.dart => group_screen_view_model.dart} (93%) create mode 100644 lib/view_model/home_screen_view_model.dart create mode 100644 lib/views/group_screen.dart rename lib/views/{home.dart => home_screen.dart} (73%) diff --git a/README.md b/README.md index 33f1f317..a0e6a47a 100755 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ You'll need to set up the IDE and mobile device emulator, or any mobile testing 1. [Flutter SDK](https://flutter.dev/docs/get-started/install) 2. [Android Studio](https://developer.android.com/studio) -***Ensure you are testing the app using Flutter version [2.10](https://docs.flutter.dev/development/tools/sdk/releases?tab=windows) and above.*** +***Ensure you are testing the app using Flutter version [3.0.1](https://docs.flutter.dev/development/tools/sdk/releases?tab=windows) and above.*** For checking flutter version: - Run `flutter --version` in terminal @@ -48,7 +48,7 @@ beacon/lib/ ├── components/ # Shared Components such as dialog boxes, button, and other shared widgets ├── enums/ # enum files | └── view_state.dart # defines view states i.e Idle, Busy, Error -├── models/ # model classes: beacon, location, landmark, user +├── models/ # model classes: group, beacon, location, landmark, user ├── queries/ # includes all graphql query strings ├── services/ # services | ├── database_mutation_function.dart/ # Graphql Queries implementations @@ -59,6 +59,7 @@ beacon/lib/ | ├── auth_screen.dart | ├── base_view.dart | ├── hike_screen.dart +| ├── group_screen.dart | ├── home.dart ├── viewmodels/ # Viewmodels layer ├── splash_screen.dart # Very first screen displayed whilst data is loading diff --git a/lib/components/create_join_dialog.dart b/lib/components/create_join_dialog.dart index 40465539..5eca5a3b 100644 --- a/lib/components/create_join_dialog.dart +++ b/lib/components/create_join_dialog.dart @@ -2,14 +2,160 @@ import 'package:beacon/locator.dart'; import 'package:beacon/services/validators.dart'; import 'package:beacon/components/hike_button.dart'; import 'package:beacon/utilities/constants.dart'; -import 'package:beacon/view_model/home_view_model.dart'; +import 'package:beacon/view_model/group_screen_view_model.dart'; import 'package:duration_picker/duration_picker.dart'; import 'package:flutter/material.dart'; import 'package:sizer/sizer.dart'; +import '../view_model/home_screen_view_model.dart'; + +class CreateJoinGroupDialog { + static Future createGroupDialog(BuildContext context, HomeViewModel model) { + bool isSmallSized = MediaQuery.of(context).size.height < 800; + return showDialog( + context: context, + builder: (context) => GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: SingleChildScrollView( + child: Form( + key: model.formKeyCreate, + child: Container( + height: isSmallSized ? 35.h : 25.h, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + child: Column( + children: [ + Container( + height: isSmallSized ? 12.h : 10.h, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: TextFormField( + style: TextStyle(fontSize: 22.0), + validator: (value) => + Validator.validateBeaconTitle(value), + onChanged: (name) { + model.title = name; + }, + decoration: InputDecoration( + border: InputBorder.none, + hintText: 'Enter Title Here', + labelStyle: TextStyle( + fontSize: labelsize, color: kYellow), + hintStyle: TextStyle( + fontSize: hintsize, color: hintColor), + labelText: 'Title', + alignLabelWithHint: true, + floatingLabelBehavior: + FloatingLabelBehavior.always, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none), + ), + ), + color: kLightBlue, + ), + SizedBox( + height: 2.h, + ), + Flexible( + flex: 2, + child: HikeButton( + text: 'Create Group', + textSize: 18.0, + textColor: Colors.white, + buttonColor: kYellow, + onTap: () { + // FocusManager.instance.primaryFocus?.unfocus(); + // navigationService.pop(); + model.createGroupRoom(); + }), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + + static Future joinGroupDialog(BuildContext context, HomeViewModel model) { + bool isSmallSized = MediaQuery.of(context).size.height < 800; + return showDialog( + context: context, + builder: (context) => Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: Form( + key: model.formKeyJoin, + child: Container( + height: isSmallSized ? 35.h : 25.h, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + child: Column( + children: [ + Container( + height: isSmallSized ? 12.h : 10.h, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: TextFormField( + keyboardType: TextInputType.text, + textCapitalization: TextCapitalization.characters, + style: TextStyle(fontSize: 22.0), + validator: (value) => Validator.validatePasskey(value), + onChanged: (key) { + model.enteredGroupCode = key.toUpperCase(); + }, + decoration: InputDecoration( + alignLabelWithHint: true, + floatingLabelBehavior: FloatingLabelBehavior.always, + hintText: 'Enter Group Code Here', + hintStyle: + TextStyle(fontSize: hintsize, color: hintColor), + labelText: 'Code', + labelStyle: + TextStyle(fontSize: labelsize, color: kYellow), + border: InputBorder.none, + ), + ), + ), + color: kLightBlue, + ), + SizedBox( + height: 2.h, + ), + Flexible( + child: HikeButton( + text: 'Join Group', + textSize: 18.0, + textColor: Colors.white, + buttonColor: kYellow, + onTap: () { + // navigationService.pop(); + model.joinGroupRoom(); + }, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} + class CreateJoinBeaconDialog { - static Future createHikeDialog( - BuildContext context, HomeViewModel model, Function reloadList) { + static Future createHikeDialog(BuildContext context, GroupViewModel model, + Function reloadList, String groupID) { bool isSmallSized = MediaQuery.of(context).size.height < 800; model.resultingDuration = Duration(minutes: 30); model.durationController = new TextEditingController(); @@ -280,7 +426,7 @@ class CreateJoinBeaconDialog { "Enter a valid date and time!!"); return; } - model.createHikeRoom(reloadList); + model.createHikeRoom(groupID, reloadList); }), ), ], @@ -295,7 +441,7 @@ class CreateJoinBeaconDialog { } static Future joinBeaconDialog( - BuildContext context, HomeViewModel model, Function reloadList) { + BuildContext context, GroupViewModel model, Function reloadList) { bool isSmallSized = MediaQuery.of(context).size.height < 800; return showDialog( context: context, diff --git a/lib/components/dialog_boxes.dart b/lib/components/dialog_boxes.dart index 6824be78..16f2fb3b 100644 --- a/lib/components/dialog_boxes.dart +++ b/lib/components/dialog_boxes.dart @@ -1,5 +1,4 @@ import 'package:beacon/components/hike_button.dart'; -import 'package:beacon/locator.dart'; import 'package:beacon/utilities/constants.dart'; import 'package:flutter/material.dart'; import 'package:sizer/sizer.dart'; @@ -35,9 +34,13 @@ class DialogBoxes { HikeButton( buttonHeight: 2.5.h, buttonWidth: 8.w, - onTap: () { - navigationService.removeAllAndPush('/main', '/'); - }, + onTap: () => Navigator.of(context).pop(true), + //TODO: + // onTap: () { + // navigationService.removeAllAndPush('/groupScreen', '/', + // arguments: GroupScreen( + // group, + // )); text: 'Yes', textSize: 18.0, ), diff --git a/lib/components/group_card.dart b/lib/components/group_card.dart new file mode 100644 index 00000000..b6eb46d1 --- /dev/null +++ b/lib/components/group_card.dart @@ -0,0 +1,182 @@ +import 'package:beacon/locator.dart'; +import 'package:beacon/utilities/constants.dart'; +import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; +import 'package:skeleton_text/skeleton_text.dart'; + +import '../models/group/group.dart'; +import '../views/group_screen.dart'; + +class GroupCustomWidgets { + static final Color textColor = Color(0xFFAFAFAF); + + static Widget getGroupCard(BuildContext context, Group group) { + String noMembers = group.members.length.toString(); + String noBeacons = group.beacons.length.toString(); + return GestureDetector( + onTap: () async { + bool isMember = false; + for (var i in group.members) { + if (i.id == userConfig.currentUser.id) { + isMember = true; + } + } + if (group.leader.id == userConfig.currentUser.id || isMember) { + navigationService.pushScreen('/groupScreen', + arguments: GroupScreen( + group, + )); + } else { + await databaseFunctions.init(); + final Group _group = + await databaseFunctions.joinGroup(group.shortcode); + if (_group != null) { + navigationService.pushScreen('/groupScreen', + arguments: GroupScreen(group)); + } + //Snackbar is displayed by joinBeacon itself on any error or trying to join expired beacon. + } + }, + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 10.0, + ), + padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 8, top: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 70.w, + child: Text( + '${group?.title} by ${group.leader.name} ', + style: Style.titleTextStyle, + ), + ), + SizedBox(height: 4.0), + Text( + 'Group has $noMembers members ', + style: Style.commonTextStyle, + ), + SizedBox(height: 4.0), + Text( + 'Group has $noBeacons beacons ', + style: Style.commonTextStyle, + ), + SizedBox(height: 4.0), + Text('Passkey: ${group?.shortcode}', + style: Style.commonTextStyle), + ], + ), + ], + ), + decoration: BoxDecoration( + color: kBlue, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(8.0), + boxShadow: [ + BoxShadow( + color: Colors.black26, + blurRadius: 10.0, + offset: Offset(0.0, 10.0), + ), + ], + ), + ), + ); + } + + static ListView getPlaceholder() { + final BorderRadius borderRadius = BorderRadius.circular(10.0); + return ListView.builder( + scrollDirection: Axis.vertical, + physics: BouncingScrollPhysics(), + itemCount: 3, + padding: const EdgeInsets.all(8.0), + itemBuilder: (BuildContext context, int index) { + return Container( + margin: const EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 10.0, + ), + height: 110, + decoration: BoxDecoration( + color: kBlue, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(8.0), + boxShadow: [ + BoxShadow( + color: Colors.black26, + blurRadius: 10.0, + offset: Offset(0.0, 10.0), + ), + ], + ), + padding: + EdgeInsets.only(left: 16.0, right: 16.0, bottom: 10, top: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 15.0, bottom: 10.0, right: 15.0), + child: ClipRRect( + borderRadius: borderRadius, + child: SkeletonAnimation( + child: Container( + height: 15.0, + decoration: BoxDecoration(color: shimmerSkeletonColor), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 30.0, bottom: 10.0), + child: ClipRRect( + borderRadius: borderRadius, + child: SkeletonAnimation( + child: Container( + height: 10.0, + decoration: BoxDecoration(color: shimmerSkeletonColor), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 45.0, bottom: 10.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: SkeletonAnimation( + child: Container( + height: 10.0, + decoration: BoxDecoration(color: shimmerSkeletonColor), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 15.0, right: 60.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: SkeletonAnimation( + child: Container( + height: 10.0, + decoration: BoxDecoration(color: shimmerSkeletonColor), + ), + ), + ), + ), + ], + ), + ); + }); + } +} diff --git a/lib/locator.dart b/lib/locator.dart index 768ee255..eab97ab5 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -7,8 +7,9 @@ import 'package:beacon/services/local_notification.dart'; import 'package:beacon/services/navigation_service.dart'; import 'package:beacon/services/user_config.dart'; import 'package:beacon/view_model/auth_screen_model.dart'; +import 'package:beacon/view_model/home_screen_view_model.dart'; import 'package:beacon/view_model/hike_screen_model.dart'; -import 'package:beacon/view_model/home_view_model.dart'; +import 'package:beacon/view_model/group_screen_view_model.dart'; import 'package:get_it/get_it.dart'; GetIt locator = GetIt.instance; @@ -41,6 +42,7 @@ void setupLocator() { locator.registerFactory(() => AuthViewModel()); locator.registerFactory(() => HomeViewModel()); locator.registerFactory(() => HikeScreenViewModel()); + locator.registerFactory(() => GroupViewModel()); //local Notification locator.registerSingleton(LocalNotification()); diff --git a/lib/models/beacon/beacon.dart b/lib/models/beacon/beacon.dart index a2487e6d..f260258f 100644 --- a/lib/models/beacon/beacon.dart +++ b/lib/models/beacon/beacon.dart @@ -3,6 +3,7 @@ import 'package:beacon/models/location/location.dart'; import 'package:beacon/models/user/user_info.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; + part 'beacon.g.dart'; @HiveType(typeId: 3) @@ -17,7 +18,8 @@ class Beacon extends HiveObject { this.followers, this.route, this.landmarks, - this.location}); + this.location, + this.group}); factory Beacon.fromJson(Map json) { return Beacon( @@ -47,6 +49,10 @@ class Beacon extends HiveObject { .map((e) => Landmark.fromJson(e as Map)) .toList() : [], + // group: json['group'] != null + // ? Group.fromJson(json['group'] as Map) + // : null, + group: json['group'] != null ? json['group']['_id'] : null, ); } @@ -70,6 +76,8 @@ class Beacon extends HiveObject { List landmarks = []; @HiveField(9) Location location; + @HiveField(10) + String group; print() { debugPrint('shortCode: ${this.shortcode}'); diff --git a/lib/models/beacon/beacon.g.dart b/lib/models/beacon/beacon.g.dart index 6e89b399..391c1167 100644 --- a/lib/models/beacon/beacon.g.dart +++ b/lib/models/beacon/beacon.g.dart @@ -27,13 +27,14 @@ class BeaconAdapter extends TypeAdapter { route: (fields[6] as List)?.cast(), landmarks: (fields[8] as List)?.cast(), location: fields[9] as Location, + group: fields[10] as String, ); } @override void write(BinaryWriter writer, Beacon obj) { writer - ..writeByte(10) + ..writeByte(11) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -53,7 +54,9 @@ class BeaconAdapter extends TypeAdapter { ..writeByte(8) ..write(obj.landmarks) ..writeByte(9) - ..write(obj.location); + ..write(obj.location) + ..writeByte(10) + ..write(obj.group); } @override diff --git a/lib/models/group/group.dart b/lib/models/group/group.dart new file mode 100644 index 00000000..c6832ed3 --- /dev/null +++ b/lib/models/group/group.dart @@ -0,0 +1,59 @@ +import 'package:beacon/models/user/user_info.dart'; +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; + +import '../beacon/beacon.dart'; + +part 'group.g.dart'; + +@HiveType(typeId: 5) +class Group extends HiveObject { + Group({ + this.id, + this.shortcode, + this.title, + this.leader, + this.members, + this.beacons, + }); + + factory Group.fromJson(Map json) { + return Group( + id: json['_id'] as String, + title: json['title'] != null ? json['title'] as String : null, + shortcode: json['shortcode'] as String, + leader: json['leader'] != null + ? User.fromJson(json['leader'] as Map) + : null, + members: json['members'] != null + ? (json['members'] as List) + .map((e) => User.fromJson(e as Map)) + .toList() + : [], + beacons: json['beacons'] != null + ? (json['beacons'] as List) + .map((e) => Beacon.fromJson(e as Map)) + .toList() + : [], + ); + } + + @HiveField(0) + String id; + @HiveField(1) + String title; + @HiveField(2) + String shortcode; + @HiveField(3) + User leader; + @HiveField(4) + List members = []; + @HiveField(5) + List beacons = []; + + print() { + debugPrint('shortCode: ${this.shortcode}'); + debugPrint('_id: ${this.id}'); + debugPrint('groupLeader: ${this.leader}'); + } +} diff --git a/lib/models/group/group.g.dart b/lib/models/group/group.g.dart new file mode 100644 index 00000000..6b088374 --- /dev/null +++ b/lib/models/group/group.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'group.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class GroupAdapter extends TypeAdapter { + @override + final int typeId = 5; + + @override + Group read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return Group( + id: fields[0] as String, + shortcode: fields[2] as String, + title: fields[1] as String, + leader: fields[3] as User, + members: (fields[4] as List)?.cast(), + beacons: (fields[5] as List)?.cast(), + ); + } + + @override + void write(BinaryWriter writer, Group obj) { + writer + ..writeByte(6) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.title) + ..writeByte(2) + ..write(obj.shortcode) + ..writeByte(3) + ..write(obj.leader) + ..writeByte(4) + ..write(obj.members) + ..writeByte(5) + ..write(obj.beacons); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is GroupAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/user/user_info.dart b/lib/models/user/user_info.dart index d583fa17..57901cfe 100644 --- a/lib/models/user/user_info.dart +++ b/lib/models/user/user_info.dart @@ -1,4 +1,5 @@ import 'package:beacon/models/beacon/beacon.dart'; +import 'package:beacon/models/group/group.dart'; import 'package:beacon/models/location/location.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; @@ -12,6 +13,7 @@ class User extends HiveObject { this.name, this.location, this.beacon, + this.groups, this.id, this.isGuest}); @@ -28,6 +30,11 @@ class User extends HiveObject { .map((e) => Beacon.fromJson(e as Map)) .toList() : [], + groups: json['groups'] != null + ? (json['groups'] as List) + .map((e) => Group.fromJson(e as Map)) + .toList() + : [], isGuest: json['isGuest'] != null ? json['isGuest'] as bool : false, ); } @@ -43,8 +50,10 @@ class User extends HiveObject { @HiveField(4) List beacon = []; @HiveField(5) - Location location; + List groups = []; @HiveField(6) + Location location; + @HiveField(7) bool isGuest = false; print() { @@ -54,6 +63,7 @@ class User extends HiveObject { debugPrint('email: ${this.email}'); debugPrint('location: ${this.location}'); debugPrint('beacons: ${this.beacon}'); + debugPrint('groups: ${this.groups}'); } // updateBeacon(List beaconList) { @@ -67,5 +77,6 @@ class User extends HiveObject { this.location = details.location; this.beacon = details.beacon; this.isGuest = details.isGuest; + this.groups = details.groups; } } diff --git a/lib/models/user/user_info.g.dart b/lib/models/user/user_info.g.dart index 2ea20c20..22bcbc77 100644 --- a/lib/models/user/user_info.g.dart +++ b/lib/models/user/user_info.g.dart @@ -20,17 +20,18 @@ class UserAdapter extends TypeAdapter { authToken: fields[1] as String, email: fields[3] as String, name: fields[2] as String, - location: fields[5] as Location, + location: fields[6] as Location, beacon: (fields[4] as List)?.cast(), + groups: (fields[5] as List)?.cast(), id: fields[0] as String, - isGuest: fields[6] as bool, + isGuest: fields[7] as bool, ); } @override void write(BinaryWriter writer, User obj) { writer - ..writeByte(7) + ..writeByte(8) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -42,8 +43,10 @@ class UserAdapter extends TypeAdapter { ..writeByte(4) ..write(obj.beacon) ..writeByte(5) - ..write(obj.location) + ..write(obj.groups) ..writeByte(6) + ..write(obj.location) + ..writeByte(7) ..write(obj.isGuest); } diff --git a/lib/queries/auth.dart b/lib/queries/auth.dart index cb159d34..5fd32906 100644 --- a/lib/queries/auth.dart +++ b/lib/queries/auth.dart @@ -47,6 +47,22 @@ class AuthQueries { _id email name + groups{ + _id + title + shortcode + leader { + _id + name + } + members { + _id + name + } + beacons{ + _id + } + } beacons{ _id title diff --git a/lib/queries/beacon.dart b/lib/queries/beacon.dart index 5f5b08a8..2b9622eb 100644 --- a/lib/queries/beacon.dart +++ b/lib/queries/beacon.dart @@ -13,6 +13,10 @@ class BeaconQueries { _id name } + group{ + _id + title + } location{ lat lon @@ -36,8 +40,8 @@ class BeaconQueries { '''; } - String createBeacon( - String title, int startsAt, int expiresAt, String lat, String lon) { + String createBeacon(String title, int startsAt, int expiresAt, String lat, + String lon, String groupID) { return ''' mutation{ createBeacon(beacon: { @@ -47,7 +51,8 @@ class BeaconQueries { startLocation: { lat: "$lat", lon: "$lon" } - }) + }, + groupID:"$groupID") { _id title @@ -56,6 +61,10 @@ class BeaconQueries { _id name } + group { + _id + title + } location{ lat lon @@ -120,6 +129,10 @@ class BeaconQueries { _id title shortcode + group{ + _id + title + } leader { _id name @@ -148,6 +161,10 @@ class BeaconQueries { leader{ name } + group { + _id + title + } followers { _id name @@ -185,6 +202,10 @@ class BeaconQueries { _id title shortcode + group { + _id + title + } leader { name location { diff --git a/lib/queries/group.dart b/lib/queries/group.dart new file mode 100644 index 00000000..1246c913 --- /dev/null +++ b/lib/queries/group.dart @@ -0,0 +1,143 @@ +import 'package:graphql_flutter/graphql_flutter.dart'; + +class GroupQueries { + String createGroup(String title) { + return ''' + mutation{ + createGroup(group: { + title: "$title" + } + ) + { + _id + title + shortcode + leader { + _id + name + } + members { + _id + name + } + beacons + { + _id + title + shortcode + leader { + _id + name + } + location{ + lat + lon + } + followers { + _id + name + } + startsAt + expiresAt + } + } + } + '''; + } + + String joinGroup(String shortcode) { + return ''' + mutation{ + joinGroup( + shortcode: "$shortcode" + ) + { + _id + title + shortcode + leader { + _id + name + } + members { + _id + name + } + beacons + { + _id + title + shortcode + leader { + _id + name + } + location{ + lat + lon + } + followers { + _id + name + } + startsAt + expiresAt + } + } + } + '''; + } + + String groupDetail(String id) { + return ''' + query{ + group(id:"$id") + { + _id + title + shortcode + leader { + _id + name + } + members { + _id + name + } + beacons + { + _id + title + shortcode + leader { + _id + name + } + location{ + lat + lon + } + followers { + _id + name + } + startsAt + expiresAt + } + } + } + '''; + } + + final groupJoinedSubGql = gql(r''' + subscription StreamNewlyJoinedGroups($id: ID!){ + groupJoined(id: $id){ + name + location{ + lat + lon + } + } + } + '''); +} diff --git a/lib/router.dart b/lib/router.dart index 29f97362..7b568bb4 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,8 +1,9 @@ import 'package:beacon/splash_screen.dart'; +import 'package:beacon/views/home_screen.dart'; import 'package:flutter/material.dart'; import 'package:beacon/utilities/constants.dart'; import 'package:beacon/views/auth_screen.dart'; -import 'package:beacon/views/home.dart'; +import 'package:beacon/views/group_screen.dart'; import 'package:beacon/views/hike_screen.dart'; Route generateRoute(RouteSettings settings) { @@ -20,6 +21,12 @@ Route generateRoute(RouteSettings settings) { arguments.beacon, isLeader: arguments.isLeader, )); + case Routes.groupScreen: + GroupScreen arguments = settings.arguments; + return MaterialPageRoute( + builder: (context) => GroupScreen( + arguments.group, + )); default: return MaterialPageRoute( builder: (context) => const SplashScreen(key: Key('SplashScreen'))); diff --git a/lib/services/database_mutation_functions.dart b/lib/services/database_mutation_functions.dart index cf113ae2..8cfd7b53 100644 --- a/lib/services/database_mutation_functions.dart +++ b/lib/services/database_mutation_functions.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:beacon/models/beacon/beacon.dart'; +import 'package:beacon/models/group/group.dart'; import 'package:beacon/models/landmarks/landmark.dart'; import 'package:beacon/models/location/location.dart'; import 'package:beacon/queries/auth.dart'; @@ -11,17 +12,20 @@ import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:beacon/models/user/user_info.dart'; import '../locator.dart'; +import '../queries/group.dart'; class DataBaseMutationFunctions { GraphQLClient clientNonAuth; GraphQLClient clientAuth; AuthQueries _authQuery; BeaconQueries _beaconQuery; + GroupQueries _groupQuery; init() async { clientNonAuth = graphqlConfig.clientToQuery(); clientAuth = await graphqlConfig.authClient(); _authQuery = AuthQueries(); _beaconQuery = BeaconQueries(); + _groupQuery = GroupQueries(); } GraphQLError userNotFound = const GraphQLError(message: 'User not found'); @@ -90,6 +94,7 @@ class DataBaseMutationFunctions { return result.data; } + //Auth Future signup({String name, String email, String password}) async { final QueryResult result = email != null ? await clientNonAuth.mutate(MutationOptions( @@ -146,6 +151,7 @@ class DataBaseMutationFunctions { return otherError; } + // User Info Future fetchCurrentUserInfo() async { await databaseFunctions.init(); final QueryResult result = await clientAuth @@ -169,6 +175,7 @@ class DataBaseMutationFunctions { return false; } + // Beacon Info Future fetchBeaconInfo(String id) async { final QueryResult result = await clientAuth .query(QueryOptions(document: gql(_beaconQuery.fetchBeaconDetail(id)))); @@ -187,8 +194,9 @@ class DataBaseMutationFunctions { return null; } - Future> fetchUserBeacons() async { + Future> fetchUserBeacons(String groupid) async { List beacons = []; + List _userBeacons = []; Set beaconIds = {}; List expiredBeacons = []; if (!await connectionChecker.checkForInternetConnection()) { @@ -198,11 +206,13 @@ class DataBaseMutationFunctions { return beacons; } for (Beacon i in userBeacons) { - if (DateTime.fromMillisecondsSinceEpoch(i.expiresAt) - .isBefore(DateTime.now())) - expiredBeacons.add(i); - else - beacons.add(i); + if (i.group == groupid) { + if (DateTime.fromMillisecondsSinceEpoch(i.expiresAt) + .isBefore(DateTime.now())) + expiredBeacons.add(i); + else + beacons.add(i); + } } beacons.addAll(expiredBeacons); return beacons; @@ -210,7 +220,7 @@ class DataBaseMutationFunctions { //if connected to internet take from internet. final QueryResult result = await clientAuth - .query(QueryOptions(document: gql(_authQuery.fetchUserInfo()))); + .query(QueryOptions(document: gql(_groupQuery.groupDetail(groupid)))); if (result.hasException) { final bool exception = encounteredExceptionOrError(result.exception, showSnackBar: false); @@ -218,10 +228,13 @@ class DataBaseMutationFunctions { print('$exception'); } } else if (result.data != null && result.isConcrete) { - final User userInfo = User.fromJson( - result.data['me'] as Map, - ); - for (var i in userInfo.beacon) { + // print(result.toString() + 'aadeeshmc'); + _userBeacons = (result.data['group']['beacons'] as List) + .map((e) => Beacon.fromJson(e as Map)) + .toList(); + + // userInfo.print(); + for (var i in _userBeacons) { if (!beaconIds.contains(i.id)) { if (!hiveDb.beaconsBox.containsKey(i.id)) { //This only happens if a someone else adds user to their beacon (which currently is not possible). @@ -245,7 +258,8 @@ class DataBaseMutationFunctions { return beacons; } - Future createBeacon(String title, int startsAt, int expiresAt) async { + Future createBeacon( + String title, int startsAt, int expiresAt, String groupID) async { LatLng loc; try { loc = await AppConstants.getLocation(); @@ -256,7 +270,7 @@ class DataBaseMutationFunctions { } final QueryResult result = await clientAuth.mutate(MutationOptions( document: gql(_beaconQuery.createBeacon(title, startsAt, expiresAt, - loc.latitude.toString(), loc.longitude.toString())))); + loc.latitude.toString(), loc.longitude.toString(), groupID)))); if (result.hasException) { navigationService.showSnackBar( "Something went wrong: ${result.exception.graphqlErrors.first.message}"); @@ -342,9 +356,10 @@ class DataBaseMutationFunctions { return null; } - Future> fetchNearbyBeacon() async { + Future> fetchNearbyBeacon(String groupID) async { await databaseFunctions.init(); List _nearbyBeacons = []; + List _nearbyBeaconsinGroup = []; LatLng loc; try { loc = await AppConstants.getLocation(); @@ -365,8 +380,10 @@ class DataBaseMutationFunctions { _nearbyBeacons = (result.data['nearbyBeacons'] as List) .map((e) => Beacon.fromJson(e as Map)) .toList(); - _nearbyBeacons.sort((a, b) => a.startsAt.compareTo(b.startsAt)); - return _nearbyBeacons; + for (Beacon i in _nearbyBeacons) + if (i.group == groupID) _nearbyBeaconsinGroup.add(i); + _nearbyBeaconsinGroup.sort((a, b) => a.startsAt.compareTo(b.startsAt)); + return _nearbyBeaconsinGroup; } return _nearbyBeacons; } @@ -389,4 +406,97 @@ class DataBaseMutationFunctions { }); return null; } + + // Group Info + Future createGroup(String title) async { + final QueryResult result = await clientAuth + .mutate(MutationOptions(document: gql(_groupQuery.createGroup(title)))); + 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 Group group = Group.fromJson( + result.data['createGroup'] as Map, + ); + // hiveDb.putBeaconInBeaconBox(group.id, group); + return group; + } + return null; + } + + Future joinGroup(String shortcode) async { + final QueryResult result = await clientAuth.mutate( + MutationOptions(document: gql(_groupQuery.joinGroup(shortcode)))); + if (result.hasException) { + navigationService.showSnackBar( + "Something went wrong: ${result.exception.graphqlErrors.first.message}"); + print("Something went wrong: ${result.exception}"); + navigationService.removeAllAndPush('/main', '/'); + } else if (result.data != null && result.isConcrete) { + final Group group = Group.fromJson( + result.data['joinBeacon'] as Map, + ); + // hiveDb.putBeaconInBeaconBox(beacon.id, beacon); + return group; + } else { + navigationService.showSnackBar( + "Something went wrong while trying to join Group", + ); + } + return null; + } + + Future> fetchUserGroups() async { + List groups = []; + Set groupIds = {}; + + // if (!await connectionChecker.checkForInternetConnection()) { + // final userBeacons = hiveDb.getAllUserBeacons(); + // if (userBeacons == null) { + // //snackbar has already been shown in getAllUserBeacons; + // return beacons; + // } + // for (Beacon i in userBeacons) { + // if (i.id == groupid) { + // if (DateTime.fromMillisecondsSinceEpoch(i.expiresAt) + // .isBefore(DateTime.now())) + // expiredBeacons.add(i); + // else + // beacons.add(i); + // } + // } + // beacons.addAll(expiredBeacons); + // return beacons; + // } + + //if connected to internet take from internet. + final QueryResult result = await clientAuth + .query(QueryOptions(document: gql(_authQuery.fetchUserInfo()))); + if (result.hasException) { + final bool exception = + encounteredExceptionOrError(result.exception, showSnackBar: false); + if (exception) { + print('$exception'); + } + } else if (result.data != null && result.isConcrete) { + final User userInfo = User.fromJson( + result.data['me'] as Map, + ); + // userInfo.print(); + for (var i in userInfo.groups) { + // print(i.beacons.length.toString() + "hello"); + if (!groupIds.contains(i.id)) { + // if (!hiveDb.beaconsBox.containsKey(i.id)) { + // //This only happens if a someone else adds user to their beacon (which currently is not possible). + // //beacons are put in box when creating or joining. + // await hiveDb.putBeaconInBeaconBox(i.id, i); + // } + groupIds.add(i.id); + groups.add(i); + } + } + } + return groups; + } } diff --git a/lib/services/hive_localdb.dart b/lib/services/hive_localdb.dart index 7f71d1d5..319a5d5b 100644 --- a/lib/services/hive_localdb.dart +++ b/lib/services/hive_localdb.dart @@ -6,9 +6,12 @@ import 'package:beacon/models/user/user_info.dart'; import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart' as path_provider; +import '../models/group/group.dart'; + class HiveLocalDb { Box currentUserBox; Box beaconsBox; + Box groupsBox; Future init() async { final appDocumentDirectory = @@ -18,9 +21,11 @@ class HiveLocalDb { ..registerAdapter(UserAdapter()) ..registerAdapter(BeaconAdapter()) ..registerAdapter(LocationAdapter()) - ..registerAdapter(LandmarkAdapter()); + ..registerAdapter(LandmarkAdapter()) + ..registerAdapter(GroupAdapter()); currentUserBox = await Hive.openBox('currentUser'); beaconsBox = await Hive.openBox('beacons'); + groupsBox = await Hive.openBox('groups'); } Future saveUserInHive(User currentUser) async { diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 3cfe9515..8f272eaf 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -63,6 +63,7 @@ class Routes { static const String authScreen = "/auth"; static const String mainScreen = "/main"; static const String hikeScreen = "/hikeScreen"; + static const String groupScreen = "/groupScreen"; } class Style { diff --git a/lib/view_model/home_view_model.dart b/lib/view_model/group_screen_view_model.dart similarity index 93% rename from lib/view_model/home_view_model.dart rename to lib/view_model/group_screen_view_model.dart index 5b6d658b..bcbc616f 100644 --- a/lib/view_model/home_view_model.dart +++ b/lib/view_model/group_screen_view_model.dart @@ -6,7 +6,7 @@ import 'package:beacon/views/hike_screen.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -class HomeViewModel extends BaseModel { +class GroupViewModel extends BaseModel { final formKeyCreate = GlobalKey(); final formKeyJoin = GlobalKey(); Duration resultingDuration = Duration(minutes: 30); @@ -17,6 +17,7 @@ class HomeViewModel extends BaseModel { bool isCreatingHike = false; String title; bool hasStarted; + String groupID; //commenting out since its value isnt used anywhere. //TextEditingController _titleController = new TextEditingController(); TextEditingController durationController = new TextEditingController(); @@ -24,7 +25,7 @@ class HomeViewModel extends BaseModel { TextEditingController startsAtTime = new TextEditingController(); String enteredPasskey; - createHikeRoom(Function reloadList) async { + createHikeRoom(String groupID, Function reloadList) async { FocusScope.of(navigationService.navigatorKey.currentContext).unfocus(); validate = AutovalidateMode.always; if (formKeyCreate.currentState.validate()) { @@ -33,10 +34,10 @@ class HomeViewModel extends BaseModel { validate = AutovalidateMode.disabled; databaseFunctions.init(); final Beacon beacon = await databaseFunctions.createBeacon( - title, - startsAt.millisecondsSinceEpoch.toInt(), - startsAt.add(resultingDuration).millisecondsSinceEpoch.toInt(), - ); + title, + startsAt.millisecondsSinceEpoch.toInt(), + startsAt.add(resultingDuration).millisecondsSinceEpoch.toInt(), + groupID); // setState(ViewState.idle); if (beacon != null) { hasStarted = DateTime.now() diff --git a/lib/view_model/home_screen_view_model.dart b/lib/view_model/home_screen_view_model.dart new file mode 100644 index 00000000..e5e4fb83 --- /dev/null +++ b/lib/view_model/home_screen_view_model.dart @@ -0,0 +1,72 @@ +import 'package:beacon/enums/view_state.dart'; +import 'package:beacon/locator.dart'; +import 'package:beacon/view_model/base_view_model.dart'; +import 'package:flutter/material.dart'; + +import '../models/group/group.dart'; +import '../views/group_screen.dart'; + +class HomeViewModel extends BaseModel { + final formKeyCreate = GlobalKey(); + final formKeyJoin = GlobalKey(); + AutovalidateMode validate = AutovalidateMode.onUserInteraction; + String title; + bool isCreatingGroup = false; + String enteredGroupCode; + + createGroupRoom() async { + FocusScope.of(navigationService.navigatorKey.currentContext).unfocus(); + validate = AutovalidateMode.always; + if (formKeyCreate.currentState.validate()) { + navigationService.pop(); + setState(ViewState.busy); + validate = AutovalidateMode.disabled; + databaseFunctions.init(); + final Group group = await databaseFunctions.createGroup( + title, + ); + if (group != null) { + navigationService.pushScreen('/groupScreen', + arguments: GroupScreen( + group, + )); + } + } else { + navigationService.showSnackBar('Something went wrong'); + setState(ViewState.idle); + } + } + + joinGroupRoom() async { + FocusScope.of(navigationService.navigatorKey.currentContext).unfocus(); + validate = AutovalidateMode.always; + if (formKeyJoin.currentState.validate()) { + setState(ViewState.busy); + validate = AutovalidateMode.disabled; + databaseFunctions.init(); + final Group group = await databaseFunctions.joinGroup(enteredGroupCode); + // setState(ViewState.idle); + if (group != null) { + navigationService.pushScreen('/groupScreen', + arguments: GroupScreen( + group, + )); + } else { + //there was some error, go back to homescreen. + setState(ViewState.idle); + } + //Snackbar is displayed by joinBeacon itself on any error or trying to join expired beacon. + } else { + navigationService.showSnackBar('Enter Valid Group Code'); + } + } + + logout() async { + setState(ViewState.busy); + await userConfig.currentUser.delete(); + await hiveDb.beaconsBox.clear(); + // setState(ViewState.idle); + await localNotif.deleteNotification(); + navigationService.removeAllAndPush('/auth', '/'); + } +} diff --git a/lib/views/group_screen.dart b/lib/views/group_screen.dart new file mode 100644 index 00000000..68be5672 --- /dev/null +++ b/lib/views/group_screen.dart @@ -0,0 +1,409 @@ +import 'package:beacon/components/beacon_card.dart'; +import 'package:beacon/components/create_join_dialog.dart'; +import 'package:beacon/components/hike_button.dart'; +import 'package:beacon/components/loading_screen.dart'; +import 'package:beacon/components/shape_painter.dart'; +import 'package:beacon/locator.dart'; +import 'package:beacon/models/beacon/beacon.dart'; +import 'package:beacon/utilities/constants.dart'; +import 'package:beacon/view_model/group_screen_view_model.dart'; +import 'package:beacon/views/base_view.dart'; +import 'package:flutter/material.dart'; +import 'package:modal_progress_hud/modal_progress_hud.dart'; +import 'package:sizer/sizer.dart'; + +import '../models/group/group.dart'; + +class GroupScreen extends StatefulWidget { + final Group group; + GroupScreen(this.group); + + @override + _GroupScreenState createState() => _GroupScreenState(); +} + +class _GroupScreenState extends State + with TickerProviderStateMixin { + var fetchingUserBeacons; + var fetchingNearbyBeacons; + + @override + void initState() { + fetchingUserBeacons = databaseFunctions.fetchUserBeacons(widget.group.id); + fetchingNearbyBeacons = + databaseFunctions.fetchNearbyBeacon(widget.group.id); + super.initState(); + } + + void reloadList() { + setState(() { + fetchingUserBeacons = databaseFunctions.fetchUserBeacons(widget.group.id); + fetchingNearbyBeacons = + databaseFunctions.fetchNearbyBeacon(widget.group.id); + }); + } + + @override + Widget build(BuildContext context) { + return BaseView(builder: (context, model, child) { + TabController tabController = new TabController(length: 2, vsync: this); + return model.isBusy + ? LoadingScreen() + : Scaffold( + resizeToAvoidBottomInset: false, + body: SafeArea( + child: ModalProgressHUD( + inAsyncCall: model.isCreatingHike, + child: Stack( + children: [ + CustomPaint( + size: Size(MediaQuery.of(context).size.width, + MediaQuery.of(context).size.height - 200), + painter: ShapePainter(), + ), + // Creating a back button + // Align( + // alignment: Alignment(-0.9, -0.8), + // child: FloatingActionButton( + // onPressed: () => navigationService.pop(), + // backgroundColor: kYellow, + // child: Icon(Icons.arrow_back_rounded), + // ), + // ), + Align( + alignment: Alignment(-0.7, -0.95), + child: Container( + width: MediaQuery.of(context).size.width * 0.6, + child: Text( + 'Welcome to Group ' + widget.group.title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 25, + color: Colors.white, + ), + ), + ), + ), + Align( + alignment: Alignment(0.9, -0.8), + child: FloatingActionButton( + onPressed: () => showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + actionsAlignment: + MainAxisAlignment.spaceEvenly, + title: Text( + (userConfig.currentUser.isGuest) + ? 'Create Account' + : 'Logout', + style: TextStyle( + fontSize: 25, color: kYellow), + ), + content: Text( + (userConfig.currentUser.isGuest) + ? 'Would you like to create an account?' + : 'Are you sure you wanna logout?', + style: TextStyle( + fontSize: 16, color: kBlack), + ), + actions: [ + HikeButton( + buttonHeight: 2.5.h, + buttonWidth: 8.w, + onTap: () => + Navigator.of(context).pop(false), + text: 'No', + textSize: 18.0, + ), + HikeButton( + buttonHeight: 2.5.h, + buttonWidth: 8.w, + onTap: () { + navigationService.pop(); + model.logout(); + }, + text: 'Yes', + textSize: 18.0, + ), + ], + )), + backgroundColor: kYellow, + child: (userConfig.currentUser.isGuest) + ? Icon(Icons.person) + : Icon(Icons.logout), + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(4.w, 25.h, 4.w, 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 45.w, + child: HikeButton( + buttonWidth: homebwidth, + buttonHeight: homebheight - 2, + text: 'Create Hike', + textColor: Colors.white, + borderColor: Colors.white, + buttonColor: kYellow, + onTap: () { + if (userConfig.currentUser.isGuest) { + navigationService.showSnackBar( + 'You need to login with credentials to start a hike'); + } else { + CreateJoinBeaconDialog.createHikeDialog( + context, + model, + reloadList, + widget.group.id); + } + }, + ), + ), + SizedBox( + width: 1.w, + ), + Container( + width: 45.w, + child: HikeButton( + buttonWidth: homebwidth, + buttonHeight: homebheight - 2, + text: 'Join a Hike', + textColor: kYellow, + borderColor: kYellow, + buttonColor: Colors.white, + onTap: () async { + CreateJoinBeaconDialog.joinBeaconDialog( + context, model, reloadList); + }, + ), + ), + ], + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: MediaQuery.of(context).size.height * 0.565, + margin: EdgeInsets.only(top: 20), + decoration: BoxDecoration( + color: kLightBlue, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(50.0), + topRight: const Radius.circular(50.0), + ), + ), + child: Column( + children: [ + TabBar( + indicatorSize: TabBarIndicatorSize.tab, + indicatorColor: kBlue, + labelColor: kBlack, + tabs: [ + Tab(text: 'Your Beacons'), + Tab(text: 'Nearby Beacons'), + ], + controller: tabController, + ), + Expanded( + child: TabBarView( + controller: tabController, + children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: FutureBuilder( + future: fetchingUserBeacons, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done) { + if (snapshot.hasError) { + return Center( + child: Text( + snapshot.error.toString(), + textAlign: TextAlign.center, + textScaleFactor: 1.3, + ), + ); + } + final List posts = + snapshot.data; + return Container( + alignment: Alignment.center, + child: posts.length == 0 + ? SingleChildScrollView( + physics: + AlwaysScrollableScrollPhysics(), + child: Column( + children: [ + Text( + 'You haven\'t joined or created any beacon yet', + textAlign: + TextAlign + .center, + style: TextStyle( + color: + kBlack, + fontSize: + 20), + ), + SizedBox( + height: 2.h, + ), + RichText( + text: TextSpan( + // textAlign: + // TextAlign + // .center, + style: TextStyle( + color: + kBlack, + fontSize: + 20), + children: [ + TextSpan( + text: + 'Join', + style: TextStyle( + fontWeight: + FontWeight.bold)), + TextSpan( + text: + ' a Hike or '), + TextSpan( + text: + 'Create', + style: TextStyle( + fontWeight: + FontWeight.bold)), + TextSpan( + text: + ' a new one! '), + ], + ), + ), + ], + ), + ) + : ListView.builder( + physics: + AlwaysScrollableScrollPhysics(), + scrollDirection: + Axis.vertical, + itemCount: + posts?.length, + padding: + EdgeInsets.all(8), + itemBuilder: + (context, index) { + return BeaconCustomWidgets + .getBeaconCard( + context, + posts[ + index]); + }, + )); + } else { + return Center( + child: BeaconCustomWidgets + .getPlaceholder(), + ); + } + }, + ), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: Container( + alignment: Alignment.center, + child: FutureBuilder( + future: fetchingNearbyBeacons, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) + return Center( + child: BeaconCustomWidgets + .getPlaceholder(), + ); + if (snapshot.connectionState == + ConnectionState.done) { + if (snapshot.hasError) { + return Center( + child: Text( + snapshot.error.toString(), + textAlign: + TextAlign.center, + textScaleFactor: 1.3, + ), + ); + } + + final posts = snapshot.data; + if (posts == null || + posts.length == 0) { + return SingleChildScrollView( + physics: + AlwaysScrollableScrollPhysics(), + child: Center( + child: Text( + 'No nearby beacons found :(', + style: TextStyle( + color: kBlack, + fontSize: 20), + ), + ), + ); + } + return ListView.builder( + physics: + AlwaysScrollableScrollPhysics(), + scrollDirection: + Axis.vertical, + itemCount: posts.length, + padding: EdgeInsets.all(8), + itemBuilder: + (context, index) { + return BeaconCustomWidgets + .getBeaconCard(context, + posts[index]); + }, + ); + } else { + return SingleChildScrollView( + physics: + AlwaysScrollableScrollPhysics(), + child: Center( + child: Text( + 'No nearby beacons found :(', + style: TextStyle( + color: kBlack, + fontSize: 18))), + ); + } + }, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ); + }); + } +} diff --git a/lib/views/hike_screen.dart b/lib/views/hike_screen.dart index 1631d075..67a20dfe 100644 --- a/lib/views/hike_screen.dart +++ b/lib/views/hike_screen.dart @@ -1,4 +1,5 @@ import 'package:beacon/components/loading_screen.dart'; +import 'package:beacon/locator.dart'; import 'package:beacon/view_model/hike_screen_model.dart'; import 'package:beacon/views/base_view.dart'; import 'package:flutter/material.dart'; @@ -185,7 +186,8 @@ class _HikeScreenState extends State { alignment: Alignment(-0.9, -0.98), child: FloatingActionButton( onPressed: () { - model.onWillPop(context); + //TODO: back to group screen + navigationService.pop(); }, backgroundColor: kYellow, child: Icon( diff --git a/lib/views/home.dart b/lib/views/home_screen.dart similarity index 73% rename from lib/views/home.dart rename to lib/views/home_screen.dart index aeca1d86..c855ff4d 100644 --- a/lib/views/home.dart +++ b/lib/views/home_screen.dart @@ -4,25 +4,25 @@ import 'package:beacon/components/hike_button.dart'; import 'package:beacon/components/loading_screen.dart'; import 'package:beacon/components/shape_painter.dart'; import 'package:beacon/locator.dart'; -import 'package:beacon/models/beacon/beacon.dart'; +import 'package:beacon/models/group/group.dart'; import 'package:beacon/utilities/constants.dart'; -import 'package:beacon/view_model/home_view_model.dart'; import 'package:beacon/views/base_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:modal_progress_hud/modal_progress_hud.dart'; import 'package:sizer/sizer.dart'; +import '../components/group_card.dart'; +import '../view_model/home_screen_view_model.dart'; + class MainScreen extends StatefulWidget { const MainScreen({Key key}) : super(key: key); - @override _MainScreenState createState() => _MainScreenState(); } class _MainScreenState extends State with TickerProviderStateMixin { - var fetchingUserBeacons; - var fetchingNearbyBeacons; + var fetchingUserGroups; Future _onPopHome() async { return showDialog( context: context, @@ -61,15 +61,13 @@ class _MainScreenState extends State with TickerProviderStateMixin { @override void initState() { - fetchingUserBeacons = databaseFunctions.fetchUserBeacons(); - fetchingNearbyBeacons = databaseFunctions.fetchNearbyBeacon(); + fetchingUserGroups = databaseFunctions.fetchUserGroups(); super.initState(); } void reloadList() { setState(() { - fetchingUserBeacons = databaseFunctions.fetchUserBeacons(); - fetchingNearbyBeacons = databaseFunctions.fetchNearbyBeacon(); + fetchingUserGroups = databaseFunctions.fetchUserGroups(); }); } @@ -78,14 +76,14 @@ 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 = new TabController(length: 1, vsync: this); return model.isBusy ? LoadingScreen() : Scaffold( resizeToAvoidBottomInset: false, body: SafeArea( child: ModalProgressHUD( - inAsyncCall: model.isCreatingHike, + inAsyncCall: model.isCreatingGroup, child: Stack( children: [ CustomPaint( @@ -156,19 +154,19 @@ class _MainScreenState extends State with TickerProviderStateMixin { Container( width: 45.w, child: HikeButton( - buttonWidth: homebwidth, + buttonWidth: homebwidth - 10, buttonHeight: homebheight - 2, - text: 'Create Hike', + text: 'Create Group', textColor: Colors.white, borderColor: Colors.white, buttonColor: kYellow, onTap: () { if (userConfig.currentUser.isGuest) { navigationService.showSnackBar( - 'You need to login with credentials to start a hike'); + 'You need to login with credentials to be able to create a group'); } else { - CreateJoinBeaconDialog.createHikeDialog( - context, model, reloadList); + CreateJoinGroupDialog.createGroupDialog( + context, model); } }, ), @@ -181,13 +179,13 @@ class _MainScreenState extends State with TickerProviderStateMixin { child: HikeButton( buttonWidth: homebwidth, buttonHeight: homebheight - 2, - text: 'Join a Hike', + text: 'Join a Group', textColor: kYellow, borderColor: kYellow, buttonColor: Colors.white, onTap: () async { - CreateJoinBeaconDialog.joinBeaconDialog( - context, model, reloadList); + CreateJoinGroupDialog.joinGroupDialog( + context, model); }, ), ), @@ -214,8 +212,7 @@ class _MainScreenState extends State with TickerProviderStateMixin { indicatorColor: kBlue, labelColor: kBlack, tabs: [ - Tab(text: 'Your Beacons'), - Tab(text: 'Nearby Beacons'), + Tab(text: 'Your Groups'), ], controller: tabController, ), @@ -226,7 +223,7 @@ class _MainScreenState extends State with TickerProviderStateMixin { Padding( padding: const EdgeInsets.all(12.0), child: FutureBuilder( - future: fetchingUserBeacons, + future: fetchingUserGroups, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { @@ -240,7 +237,7 @@ class _MainScreenState extends State with TickerProviderStateMixin { ), ); } - final List posts = + final List posts = snapshot.data; return Container( alignment: Alignment.center, @@ -251,7 +248,7 @@ class _MainScreenState extends State with TickerProviderStateMixin { child: Column( children: [ Text( - 'You haven\'t joined or created any beacon yet', + 'You haven\'t joined or created any group yet', textAlign: TextAlign .center, @@ -283,7 +280,7 @@ class _MainScreenState extends State with TickerProviderStateMixin { TextStyle(fontWeight: FontWeight.bold)), TextSpan( text: - ' a Hike or '), + ' a Group or '), TextSpan( text: 'Create', @@ -311,8 +308,8 @@ class _MainScreenState extends State with TickerProviderStateMixin { itemBuilder: (context, index) { - return BeaconCustomWidgets - .getBeaconCard( + return GroupCustomWidgets + .getGroupCard( context, posts[ index]); @@ -327,80 +324,6 @@ class _MainScreenState extends State with TickerProviderStateMixin { }, ), ), - Padding( - padding: const EdgeInsets.all(12.0), - child: Container( - alignment: Alignment.center, - child: FutureBuilder( - future: fetchingNearbyBeacons, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.waiting) - return Center( - child: BeaconCustomWidgets - .getPlaceholder(), - ); - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.hasError) { - return Center( - child: Text( - snapshot.error - .toString(), - textAlign: - TextAlign.center, - textScaleFactor: 1.3, - ), - ); - } - - final posts = snapshot.data; - if (posts == null || - posts.length == 0) { - return SingleChildScrollView( - physics: - AlwaysScrollableScrollPhysics(), - child: Center( - child: Text( - 'No nearby beacons found :(', - style: TextStyle( - color: kBlack, - fontSize: 20), - ), - ), - ); - } - return ListView.builder( - physics: - AlwaysScrollableScrollPhysics(), - scrollDirection: - Axis.vertical, - itemCount: posts.length, - padding: EdgeInsets.all(8), - itemBuilder: - (context, index) { - return BeaconCustomWidgets - .getBeaconCard( - context, - posts[index]); - }, - ); - } else { - return SingleChildScrollView( - physics: - AlwaysScrollableScrollPhysics(), - child: Center( - child: Text( - 'No nearby beacons found :(', - style: TextStyle( - color: kBlack, - fontSize: 18))), - ); - } - }, - ), - ), - ), ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 9c38407c..4339d91c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: modal_progress_hud: ^0.1.3 overlay_support: ^2.0.1 path_provider: ^2.0.9 - provider: ^6.0.2 + provider: ^6.0.3 rxdart: ^0.27.3 share_plus: ^4.0.8 shared_preferences: ^2.0.13 @@ -46,7 +46,6 @@ dependencies: sliding_up_panel: ^2.0.0+1 uni_links: ^0.5.1 - dev_dependencies: build_runner: ^2.1.7 flutter_launcher_icons: ^0.9.2