diff --git a/lib/controller/page/create_sprint_page_controller.dart b/lib/controller/page/create_sprint_page_controller.dart new file mode 100644 index 0000000..2dd64fa --- /dev/null +++ b/lib/controller/page/create_sprint_page_controller.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:idea_tracker/locator.dart'; +import 'package:idea_tracker/model/sprint.dart'; +import 'package:idea_tracker/service/sprint_service.dart'; + +class CreateSprintPageController extends ChangeNotifier{ + String _sprintTitle; + String _sprintDescription; + String _sprintTeamleader; + Function(String) onSprintCreated; + + final _sprintService = locator(); + + void setSprintTitle(String title){ + _sprintTitle = title; + } + + void setSprintDescription(String description){ + _sprintDescription = description; + } + + void setSprintTeamleader(String teamleader){ + _sprintTeamleader = teamleader; + } + + String validateTitle(String value) { + if (value.isEmpty) { + return 'Please enter idea title'; + } else if (value.length < 3) { + return 'Title must be at least 3 characters'; + } + return null; + } + + String validateDescription(String value) { + if (value.isEmpty) { + return 'Please enter idea description'; + } else if (value.length < 3) { + return 'Description must be at least 3 characters'; + } + return null; + } + + String validateTeamleader(String value) { + if (value.isEmpty) { + return 'Please enter a teamleader'; + } + return null; + } + + void createSprint() async { + final sprint = await _sprintService.create( + Sprint( + title: _sprintTitle, + description: _sprintDescription, + teamLeader: _sprintTeamleader, + ), + ); + if(onSprintCreated != null) onSprintCreated("Succes!"); + print(sprint.toString()); + } +} \ No newline at end of file diff --git a/lib/controller/page/sprints_details_controller.dart b/lib/controller/page/sprints_details_controller.dart new file mode 100644 index 0000000..7a468e6 --- /dev/null +++ b/lib/controller/page/sprints_details_controller.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:idea_tracker/locator.dart'; +import 'package:idea_tracker/model/sprint.dart'; +import 'package:idea_tracker/service/authentication_service.dart'; +import 'package:idea_tracker/service/sprint_service.dart'; + +class SprintsDetailsController extends ChangeNotifier { + Sprint _currentSprint; + final _sprintService = locator(); + final _authService = locator(); + + Sprint get currentSprint => _currentSprint; + + set currentSprint(Sprint sprint){ + _currentSprint = sprint; + notifyListeners(); + } + + UpdateSprintMember() async { + currentSprint = await _sprintService.update( + currentSprint, [UpdateSprint.member], [_authService + .getAuthenticatedUser() + .id + ]); + } + + initialize(){ + + } +} \ No newline at end of file diff --git a/lib/locator.dart b/lib/locator.dart index 26f597e..ef1e882 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -2,9 +2,11 @@ import 'package:get_it/get_it.dart'; import 'package:idea_tracker/controller/dialog/idea_edit_details_page_delete_dialog_controller.dart'; import 'package:idea_tracker/controller/dialog/landing_page_recover_password_dialog_controller.dart'; import 'package:idea_tracker/controller/page/create_idea_page_controller.dart'; +import 'package:idea_tracker/controller/page/create_sprint_page_controller.dart'; import 'package:idea_tracker/controller/page/idea_edit_details_page_controller.dart'; import 'package:idea_tracker/controller/page/ideas_main_page_controller.dart'; import 'package:idea_tracker/controller/page/main_page_controller.dart'; +import 'package:idea_tracker/controller/page/sprints_details_controller.dart'; import 'service/services.dart'; import 'package:idea_tracker/controller/page/sprints_page_controller.dart'; @@ -21,6 +23,8 @@ void setupLocator() { locator.registerFactory(() => LandingPageRecoverPasswordDialogController()); locator.registerFactory(() => ProfilePageController()); locator.registerFactory(() => SprintsPageController()); + locator.registerFactory(() => SprintsDetailsController()); + locator.registerFactory(() => CreateSprintPageController()); /// Services locator.registerSingleton(IdeaService()); diff --git a/lib/model/placeholder_model.dart b/lib/model/placeholder_model.dart new file mode 100644 index 0000000..7e5cf10 --- /dev/null +++ b/lib/model/placeholder_model.dart @@ -0,0 +1 @@ +// Git does not track empty directories. After a new file exists in this directory, this file can safely be deleted. diff --git a/lib/service/sprint_service.dart b/lib/service/sprint_service.dart index eeacf83..16934d3 100644 --- a/lib/service/sprint_service.dart +++ b/lib/service/sprint_service.dart @@ -1,6 +1,6 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/cupertino.dart'; -import 'package:idea_tracker/model/sprint.dart'; +import '../model/sprint.dart'; ///Use these enums to update parts of the Sprint Object enum UpdateSprint { @@ -8,8 +8,7 @@ enum UpdateSprint { description, addPotentialLeader, deletePotentialLeader, - addMember, - deleteMember, + member, teamLeader } enum UpdatePost { create, delete } @@ -40,73 +39,92 @@ class SprintService { debugPrint('removed Sprint ${sprint.id} from DB'); } + /// Uses the switch to update multiple properties within a sprint + /// Requires use of enum above this class + /// Use Case example: + /// _sprintServices.update(sprint, [UpdateSprint.title, + /// UpdateSprint.description], ['New Title', 'New Description sequence']) Future update(Sprint sprint, List updates, List updateStrings) async { - for (var i = 0; i < updates.length; i++) { - sprint = await _update(sprint, updates[i], updateStrings[i]); + if (updates.length == updateStrings.length) { + for (var i = 0; i < updates.length; i++) { + sprint = await _update(sprint, updates[i], updateStrings[i]); + } + return sprint; + } else { + debugPrint( + 'The update will not work unless both lists have the same length'); } - return sprint; } - ///Switch to update sprint object...use case: sprintService.update(idea, UpdateSprint.title, 'new Title'); - ///returns the updated Sprint + /// Switch to update sprint object... + /// use case: sprintService.update(idea, UpdateSprint.title, 'new Title'); + /// returns the updated Sprint Future _update( Sprint sprint, UpdateSprint update, String updateString) async { + Sprint updatedSprint = sprint; switch (update) { case UpdateSprint.title: { - sprint = await _updateTitle(sprint, updateString); - return sprint; + updatedSprint = await _updateTitle(updatedSprint, updateString); + return updatedSprint; } break; case UpdateSprint.description: { - sprint = await _updateDescription(sprint, updateString); - return sprint; + updatedSprint = await _updateDescription(updatedSprint, updateString); + return updatedSprint; } break; case UpdateSprint.teamLeader: { - sprint = await _updateTeamLeader(sprint, updateString); - return sprint; + updatedSprint = await _updateTeamLeader(updatedSprint, updateString); + return updatedSprint; } break; case UpdateSprint.addPotentialLeader: { - sprint = await _addPotentialLeaders(sprint, updateString); - return sprint; + updatedSprint = await _addPotentialLeaders(updatedSprint, updateString); + return updatedSprint; } break; case UpdateSprint.deletePotentialLeader: { - sprint = await _deletePotentialLeaders(sprint, updateString); - return sprint; + updatedSprint = await _deletePotentialLeaders(updatedSprint, updateString); + return updatedSprint; } break; - case UpdateSprint.addMember: + case UpdateSprint.member: { - sprint = await _addMember(sprint, updateString); - return sprint; - } - break; - case UpdateSprint.deleteMember: - { - sprint = await _deleteMember(sprint, updateString); - return sprint; + /// This checks to see if user already a member. + /// To get the number of members use list.length(); on the member list + /// within the Sprint object + if (!updatedSprint.members.contains(updateString)) { + updatedSprint = await _addMember(updatedSprint, updateString); + debugPrint('User $updateString added to member list for this sprint.'); + } else { + debugPrint('User $updateString is already member of this sprint.'); + updatedSprint = await _deleteMember(updatedSprint, updateString); + debugPrint('User $updateString removed from voters for this idea.'); + } + return updatedSprint; } break; default: { print( - "Nothing was updated, please use Enum UpdateSprint your Sprint ${sprint.id}."); - return sprint; + "Nothing was updated, please use Enum UpdateSprint your Sprint ${updatedSprint.id}."); + return updatedSprint; } } } - ///update posts within Sprint + /// update posts within Sprint ///...to update a post first delete post then create new post + /// list class here just adds new posts to the end of the list, + /// Firestore can only use arrays for lists so it follows typical array + /// addition and deletion functionality Future updatePost( Sprint sprint, UpdatePost updatePost, SprintPost sprintPost) async { switch (updatePost) { @@ -174,7 +192,7 @@ class SprintService { debugPrint('getAllCurrent() performing...'); QuerySnapshot querySnapshot = await sprintRef .where('createdAt', - isGreaterThanOrEqualTo: startOfMonth.millisecondsSinceEpoch) + isGreaterThanOrEqualTo: startOfMonth.millisecondsSinceEpoch) .get() .catchError( (error) => print("Failed to get all Current Sprints: $error")); @@ -185,7 +203,7 @@ class SprintService { Sprint _fromFirestore(DocumentSnapshot doc) { ///converts _InternalLinkedHashMap to List List> sprintPostDataMaps = - List>.from(doc.data()['posts']); + List>.from(doc.data()['posts']); List sprintPostList = sprintPostDataMaps .map((post) => _sprintPostFromFirestore(post)) .toList() @@ -229,13 +247,13 @@ class SprintService { _sprintToJson(Sprint sprint, String id) { return { 'id': sprint.id ?? id, - "title": sprint.title, + "title": sprint.title ?? '', "titleArray": - sprint.title.toLowerCase().split(new RegExp('\\s+')).toList(), - "description": sprint.description, + sprint.title.toLowerCase().split(new RegExp('\\s+')).toList(), + "description": sprint.description ?? '', "createdAt": DateTime.now().millisecondsSinceEpoch, - "teamLeader": sprint.teamLeader, - "updatedAt": sprint.updatedAt, + "teamLeader": sprint.teamLeader ?? '', + "updatedAt": sprint.updatedAt ?? DateTime.now().millisecondsSinceEpoch, "potentialLeaders": sprint.potentialLeaders ?? new List(), "members": sprint.members ?? new List(), "posts": sprint.posts ?? new List(), @@ -301,7 +319,7 @@ class SprintService { 'Added potentialLeader: $potentialLeader to sprint ${sprint.id}'); await _updateUpdatedAt(sprint); }).catchError( - (error) => debugPrint("Failed to add potential leader: $error")); + (error) => debugPrint("Failed to add potential leader: $error")); ///to get and store correct properties into class sprint object return sprint = await get(sprint.id); @@ -318,7 +336,7 @@ class SprintService { 'Deleted potentialLeaders: $potentialLeader to sprint ${sprint.id}'); await _updateUpdatedAt(sprint); }).catchError( - (error) => debugPrint("Failed to delete potential leader $error")); + (error) => debugPrint("Failed to delete potential leader $error")); ///to get and store correct properties into class sprint object return sprint = await get(sprint.id); @@ -385,4 +403,4 @@ class SprintService { return sprint = await get(sprint.id); } -} +} \ No newline at end of file diff --git a/lib/view/page/create_sprint_page.dart b/lib/view/page/create_sprint_page.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/view/page/sprints_details.dart b/lib/view/page/sprints_details.dart new file mode 100644 index 0000000..4a75169 --- /dev/null +++ b/lib/view/page/sprints_details.dart @@ -0,0 +1,404 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:idea_tracker/controller/page/sprints_details_controller.dart'; +import 'package:idea_tracker/model/sprint.dart'; +import 'package:idea_tracker/view/widget/state_management/base_view.dart'; +import 'package:time_formatter/time_formatter.dart'; + +class SprintsDetails extends StatefulWidget { + + final Sprint sprint; + SprintsDetails(this.sprint); + + @override + _SprintsDetailsState createState() => _SprintsDetailsState(); +} + +class _SprintsDetailsState extends State { + @override + Widget build(BuildContext context) { + return BaseView( + onControllerReady: (controller){ + controller.initialize(); + }, + builder: (context, controller, child){ + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back), + tooltip: 'go to the previous page', + onPressed: () => Navigator.pop(context), + ), + actions: [ + IconButton( + icon: Icon(Icons.share), + tooltip: 'share this sprint', + onPressed: () => null, + ), + Padding( + padding: const EdgeInsets.only(left:8.0), + child: IconButton( + icon: Icon(Icons.more_vert), + onPressed: () => null, + ), + ) + ], + backgroundColor: Colors.black87, + ), + body: AnnotatedRegion( + value: SystemUiOverlayStyle.light, + child: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Stack( + children: [ + Container( + height: double.infinity, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + ), + ), + Container( + height: double.infinity, + child: SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.fromLTRB(0, 15.0, 0, 30.0), + child: Padding( + padding: const EdgeInsets.only(top: 10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 15.0), + child: Column( + children: [ + Row( + children: [ + Icon( + Icons.account_circle, + size: 15.0, + color: Colors.black.withOpacity(0.6), + ), + SizedBox(width: 10.0,), + Text( + _teamleader(widget.sprint), + style: TextStyle(color: Colors.black.withOpacity(0.6), fontSize: 12.0), + ), + ], + ), + SizedBox(height: 3.0,), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + _title(widget.sprint), + maxLines: 1, + style: TextStyle(fontSize: 25.0, fontWeight: FontWeight.bold), + ), + SizedBox(width: 15.0,), + _createdAt(widget.sprint), + ], + ), + SizedBox(height: 10.0,), + Row( + children: [ + Flexible( + child: Container( + width: 365, + child: Text( + _description(widget.sprint), + maxLines: 5, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ), + SizedBox(height: 10.0,), + Row( + children: [ + Icon(Icons.star_border , size: 18.0, color: Colors.black.withOpacity(0.6),), + SizedBox(width: 5.0,), + Text( + //TODO: make it work with button. + '69 Stars', + style: TextStyle(color: Colors.black.withOpacity(0.6)), + ), + SizedBox(width: 25.0,), + Icon(Icons.person_outline , size: 18.0, color: Colors.black.withOpacity(0.6),), + SizedBox(width: 5.0,), + Text( + '${_numberOfContributers(widget.sprint.members, widget.sprint)} contributers', + style: TextStyle(color: Colors.black.withOpacity(0.6)), + ), + ], + ), + Row( + children: [ + ButtonBar( + mainAxisSize: MainAxisSize.max, + children: [ + RaisedButton( + child: Row( + children: [ + Icon(Icons.star_border), + SizedBox(width: 5.0,), + Text('STAR') + ], + ), + onPressed: () {}, + color: Colors.white, + ), + RaisedButton( + child: Row( + children: [ + Icon( + Icons.hail, + //color: Colors.lightGreen, + ), + SizedBox(width: 5.0,), + Text('I\'M IN'), + ], + ), + onPressed: () => controller.UpdateSprintMember(), + + color: Colors.white, + ), + ], + ), + ], + ), + ], + ), + ), + + // NOT A USED FEATURE!! IGNORE !! + + //Divider( + //height: 20.0, + //color: Colors.grey, + //), + //_latestTeamleaderPost(), + //SizedBox(height: 10.0,), + + Divider( + height: 20.0, + color: Colors.grey, + ), + Padding( + padding: const EdgeInsets.only(left: 15.0), + child: Column( + children: [ + Row( + children: [ + Text('Members', style: TextStyle(fontSize: 25.0),), + ], + ), + SizedBox(height: 10.0,), + _membersListTile(widget.sprint), + ], + ), + ), + + // NOT A USED FEATURE!! IGNORE !! + + //Divider( + //height: 20.0, + //color: Colors.grey, + //), + //_teamleaderPosts(), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + + Widget _membersListTile(Sprint sprint){ + if(sprint.members == null){ + return Row( + children: [ + Text( + 'There are no contributers', + style: TextStyle(color: Colors.black.withOpacity(0.6),), + ), + ], + ); + } else { + return Column( + children: [ + ListTile( + leading: Icon(Icons.military_tech), + title: Text(_teamleader(sprint)), + subtitle: Text("Teamleader"), + ), + Column( + children: _loadMembersOnListTiles(sprint), + ), + ], + ); + } + } + + List _loadMembersOnListTiles(Sprint sprint){ + List members = sprint.members; + return members.map((e) => _members(e)).toList(); + } + + Widget _members(String contributer){ + return ListTile( + leading: Icon(Icons.account_circle), + title: Text(contributer), + subtitle: Text("Contributer"), + ); + } + + String _numberOfContributers(List members, Sprint sprint){ + if(sprint.members == null || sprint.members.length == 0){ + return '0'; + } else { + return '${sprint.members.length}'; + } + } + + String _title(Sprint sprint){ + if(sprint.title == null){ + return 'Nameless Sprint'; + } else { + return sprint.title; + } + } + + String _description(Sprint sprint){ + if(sprint.description == null){ + return 'No description'; + } else { + return sprint.description; + } + } + + String _teamleader(Sprint sprint){ + if(sprint.teamLeader == null){ + return 'No teamleader'; + } else { + return sprint.teamLeader; + } + } + + Widget _createdAt(Sprint sprint){ + if(sprint.createdAt == null){ + return null; + } else { + return Text( + '- ${formatTime(sprint.createdAt)}', + style: TextStyle(color: Colors.black.withOpacity(0.6)), + ); + } + } + + Widget _teamleaderPost(String teamleader, String timeSincePosted, String post){ + return Padding( + padding: const EdgeInsets.only(right:15.0), + child: Card( + clipBehavior: Clip.antiAlias, + child: Padding( + padding: EdgeInsets.fromLTRB(10.0, 10.0, 0.0, 10.0), + child: Column( + children: [ + Row( + children: [ + Icon( + Icons.account_circle, + size: 40.0, + ), + Padding( + padding: const EdgeInsets.only(left: 15.0,), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + teamleader, + style: TextStyle( + fontSize: 20.0, + ), + ), + SizedBox(width: 10.0,), + Text( + '- ${timeSincePosted}', + style: TextStyle( + color: Colors.black.withOpacity(0.6), + ), + ), + ], + ), + Container( + width: 280, + child: Text( + post, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + // NOT A USED FEATURE !! IGNORE !! + Widget _latestTeamleaderPost(){ + return Padding( + padding: const EdgeInsets.only(left: 15.0), + child: Column( + children: [ + Row( + children: [ + Text('Latest Teamleader Post', style: TextStyle(fontSize: 25.0),), + ], + ), + SizedBox(height: 10.0,), + _teamleaderPost( + 'DanielQuick', + '3d', + 'sup' + ), + ], + ), + ); + } + + // NOT A USED FEATURE !! IGNORE !! + Widget _teamleaderPosts(){ + return Padding( + padding: const EdgeInsets.only(left: 15.0), + child: Column( + children: [ + Row( + children: [ + Text('Teamleader Post\'s', style: TextStyle(fontSize: 25.0),), + ], + ), + SizedBox(height: 10.0,), + _teamleaderPost('DanielQuick', '6d', 'hey'), + _teamleaderPost('DanielQuick', '7d', 'heyyyy'), + _teamleaderPost('DanielQuick', '8d', 'hallo'), + _teamleaderPost('DanielQuick', '9d', 'bonjour'), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/view/page/sprints_page.dart b/lib/view/page/sprints_page.dart index e1f4318..b6965e6 100644 --- a/lib/view/page/sprints_page.dart +++ b/lib/view/page/sprints_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:idea_tracker/controller/page/sprints_page_controller.dart'; import 'package:idea_tracker/model/sprint.dart'; +import 'package:idea_tracker/view/page/sprints_details.dart'; import 'package:idea_tracker/view/widget/state_management/base_view.dart'; class SprintsPage extends StatefulWidget { @@ -52,7 +53,6 @@ class _SprintsPageState extends State { ), Divider( height: 20.0, - color: Colors.grey, ), Column( @@ -75,12 +75,36 @@ class _SprintsPageState extends State { } List _loadActiveSprintsOnCard(List sprints, BuildContext ctx){ - return sprints - .map((e) => _cardmaker(e.title, e.teamLeader, e.description, ctx)).toList(); + if(sprints.isNotEmpty) { + return sprints + .map((e) => _cardmaker(e.title, e.teamLeader, e.description,e.members, ctx, e)) + .toList(); + } else { + return [ + Padding( + padding: const EdgeInsets.only(top: 10.0), + child: Row( + children: [ + SizedBox(width: 15.0,), + Text( + 'There are no active sprints', + style: TextStyle(color: Colors.black.withOpacity(0.6)), + ), + ], + ), + )]; + } } + String _numberOfContributers(List members, Sprint sprint){ + if(sprint.members == null || sprint.members.length == 0){ + return '0'; + } else { + return '${sprint.members.length}'; + } + } - Widget _cardmaker(String title, String teamleader, String description, BuildContext ctx){ + Widget _cardmaker(String title, String teamleader, String description, List members, BuildContext ctx, Sprint sprint){ return Card( clipBehavior: Clip.antiAlias, child: Column( @@ -89,7 +113,7 @@ class _SprintsPageState extends State { leading: Icon(Icons.run_circle), title: Text(title), subtitle: Text( - 'Team leader: ${teamleader}', + 'Team leader: ${_teamleader(sprint)}', style: TextStyle(color: Colors.black.withOpacity(0.6)), ), ), @@ -100,12 +124,28 @@ class _SprintsPageState extends State { style: TextStyle(color: Colors.black.withOpacity(0.6)), ), ), + Padding( + padding: const EdgeInsets.only(left:14.0), + child: Row( + children: [ + Icon(Icons.person_outline , size: 18.0, color: Colors.black.withOpacity(0.6),), + SizedBox(width: 5.0,), + Text( + '${_numberOfContributers(members, sprint)} contributers', + style: TextStyle(color: Colors.black.withOpacity(0.6)), + ), + ], + ), + ), ButtonBar( alignment: MainAxisAlignment.start, children: [ FlatButton( onPressed: () { - // Perform some action + Navigator.push( + ctx, + MaterialPageRoute(builder: (context) => SprintsDetails(sprint)), + ); }, child: const Text('DETAILS'), ), @@ -115,4 +155,12 @@ class _SprintsPageState extends State { ), ); } + + String _teamleader(Sprint sprint){ + if(sprint.teamLeader == null){ + return ''; + } else { + return sprint.teamLeader; + } + } } diff --git a/pubspec.lock b/pubspec.lock index 1fd5ac5..62b02ab 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,35 +7,35 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0-nullsafety.3" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0-nullsafety.5" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" cloud_firestore: dependency: "direct main" description: @@ -63,7 +63,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0-nullsafety.5" convert: dependency: transitive description: @@ -91,7 +91,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" firebase: dependency: transitive description: @@ -253,21 +253,21 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.3-nullsafety.3" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10-nullsafety.3" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.6" nested: dependency: transitive description: @@ -281,7 +281,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0-nullsafety.3" pedantic: dependency: transitive description: @@ -321,56 +321,63 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0-nullsafety.4" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0-nullsafety.6" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0-nullsafety.3" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0-nullsafety.3" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0-nullsafety.3" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.2.19-nullsafety.6" + time_formatter: + dependency: "direct main" + description: + name: time_formatter + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0+5" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.5" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0-nullsafety.5" sdks: - dart: ">=2.10.0-110 <2.11.0" + dart: ">=2.12.0-0.0 <3.0.0" flutter: ">=1.20.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0500897..b548092 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,9 @@ dependencies: # Passwordless authentication with email link use: https://medium.com/firebase-developers/dive-into-firebase-auth-on-flutter-email-and-link-sign-in-e51603eb08f8#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1MmZjYjk3ZGY1YjZiNGY2ZDFhODg1ZjFlNjNkYzRhOWNkMjMwYzUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2MTAxNzE0ODQsImF1ZCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjEwMjQ2MzU0NDQ2MDM4OTg5NTYwOSIsImVtYWlsIjoidGFyYWNlbGxlcnlAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjIxNjI5NjAzNTgzNC1rMWs2cWUwNjBzMnRwMmEyamFtNGxqZGNtczAwc3R0Zy5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsIm5hbWUiOiJUYXJhIENlbGxlcnkiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDYuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1xT2NZVm9HUjNyby9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS9BTVp1dWNtR3hZTGZkeVdaZnhqR2F4ZzRvZW5lMllVWWl3L3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJUYXJhIiwiZmFtaWx5X25hbWUiOiJDZWxsZXJ5IiwiaWF0IjoxNjEwMTcxNzg0LCJleHAiOjE2MTAxNzUzODQsImp0aSI6IjIyMGFmMWEyNGNkODc0MjZkNjQ5NTExMmZiOTI4Y2ViNWM4ZjJmMTYifQ.jgD6alVVFRBK8gkPOv6FpghHHfjxJwflLrx8TDMfIRz9yWlJIalLQy7MXZ68VNIQgvume8BnORrY2bQSF4vDU5UcHdzehZwxnEe3UcYdaYP-NKduYfPzqiwE2HxLgd4bEgvSCje5xqX20V6l3UZYkal3izqeSydj_FIcA7ANymy74aPBehS1-x3huXg39FGU5k-xu2dTs4QWVJTRn1GArHt86cdHuEr2vAyEzCLGYT_2kfwKE99HooBHUusImokpTui9QRApUcfR0DmMw-FhW-xRddi1jLribT1FODujPREjX02BnIMX1jzd9JgOCYMBdHQoYMUUhSl534izKrt2aQ firebase_dynamic_links: ^0.7.0+1 + # UNIX timestamps and converts them to readable time formats https://pub.dev/packages/time_formatter + time_formatter: ^1.0.0+5 + dev_dependencies: flutter_test: