From 8544745b838c9b215c225e0accc893543e5fcce7 Mon Sep 17 00:00:00 2001 From: Andrei-Constantin Mirciu Date: Sat, 29 Aug 2020 10:53:04 +0300 Subject: [PATCH 1/9] Add people page file --- lib/pages/people/people_page.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/pages/people/people_page.dart diff --git a/lib/pages/people/people_page.dart b/lib/pages/people/people_page.dart new file mode 100644 index 000000000..e69de29bb From bab790d1eb04cb7d8e03afdf1cf17642f7614cbf Mon Sep 17 00:00:00 2001 From: Andrei-Constantin Mirciu Date: Thu, 3 Sep 2020 00:31:59 +0300 Subject: [PATCH 2/9] Add people page tab --- lib/generated/intl/messages_en.dart | 1 + lib/generated/intl/messages_ro.dart | 1 + lib/generated/l10n.dart | 10 ++ lib/l10n/intl_en.arb | 1 + lib/l10n/intl_ro.arb | 1 + lib/navigation/bottom_navigation_bar.dart | 9 +- lib/pages/people/model/person.dart | 10 ++ lib/pages/people/people_page.dart | 0 lib/pages/people/service/person_provider.dart | 79 +++++++++++ lib/pages/people/view/people_page.dart | 15 +++ lib/pages/people/view/person_view.dart | 124 ++++++++++++++++++ pubspec.lock | 50 +++---- 12 files changed, 275 insertions(+), 26 deletions(-) create mode 100644 lib/pages/people/model/person.dart delete mode 100644 lib/pages/people/people_page.dart create mode 100644 lib/pages/people/service/person_provider.dart create mode 100644 lib/pages/people/view/people_page.dart create mode 100644 lib/pages/people/view/person_view.dart diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 24a50f3eb..e6e9f931a 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -147,6 +147,7 @@ class MessageLookup extends MessageLookupByLibrary { "navigationFilter" : MessageLookupByLibrary.simpleMessage("Filter"), "navigationHome" : MessageLookupByLibrary.simpleMessage("Home"), "navigationMap" : MessageLookupByLibrary.simpleMessage("Map"), + "navigationPeople" : MessageLookupByLibrary.simpleMessage("People"), "navigationPortal" : MessageLookupByLibrary.simpleMessage("Portal"), "navigationProfile" : MessageLookupByLibrary.simpleMessage("Profile"), "navigationSettings" : MessageLookupByLibrary.simpleMessage("Settings"), diff --git a/lib/generated/intl/messages_ro.dart b/lib/generated/intl/messages_ro.dart index 5af541959..17a550992 100644 --- a/lib/generated/intl/messages_ro.dart +++ b/lib/generated/intl/messages_ro.dart @@ -147,6 +147,7 @@ class MessageLookup extends MessageLookupByLibrary { "navigationFilter" : MessageLookupByLibrary.simpleMessage("Filtru"), "navigationHome" : MessageLookupByLibrary.simpleMessage("Acasă"), "navigationMap" : MessageLookupByLibrary.simpleMessage("Hartă"), + "navigationPeople" : MessageLookupByLibrary.simpleMessage("Persoane"), "navigationPortal" : MessageLookupByLibrary.simpleMessage("Portal"), "navigationProfile" : MessageLookupByLibrary.simpleMessage("Profil"), "navigationSettings" : MessageLookupByLibrary.simpleMessage("Setări"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 74f6074fa..cd5c750dc 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1153,6 +1153,16 @@ class S { ); } + /// `People` + String get navigationPeople { + return Intl.message( + 'People', + name: 'navigationPeople', + desc: '', + args: [], + ); + } + /// `Settings` String get navigationSettings { return Intl.message( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index ddcbfdcff..f5a778e4d 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -119,6 +119,7 @@ "navigationPortal": "Portal", "navigationMap": "Map", "navigationProfile": "Profile", + "navigationPeople": "People", "navigationSettings": "Settings", "navigationFilter": "Filter", "navigationClasses": "Classes", diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb index 9e5b918f7..486eddc05 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -119,6 +119,7 @@ "navigationPortal": "Portal", "navigationMap": "Hartă", "navigationProfile": "Profil", + "navigationPeople": "Persoane", "navigationSettings": "Setări", "navigationFilter": "Filtru", "navigationClasses": "Materii", diff --git a/lib/navigation/bottom_navigation_bar.dart b/lib/navigation/bottom_navigation_bar.dart index 444630d13..ceab847ab 100644 --- a/lib/navigation/bottom_navigation_bar.dart +++ b/lib/navigation/bottom_navigation_bar.dart @@ -1,6 +1,7 @@ import 'package:acs_upb_mobile/generated/l10n.dart'; import 'package:acs_upb_mobile/pages/classes/view/classes_page.dart'; import 'package:acs_upb_mobile/pages/home/home_page.dart'; +import 'package:acs_upb_mobile/pages/people/view/people_page.dart'; import 'package:acs_upb_mobile/pages/portal/view/portal_page.dart'; import 'package:acs_upb_mobile/pages/profile/profile_page.dart'; import 'package:flutter/material.dart'; @@ -22,12 +23,13 @@ class _AppBottomNavigationBarState extends State @override void initState() { super.initState(); - tabController = TabController(vsync: this, length: 4); + tabController = TabController(vsync: this, length: 5); tabs = [ HomePage(), ClassesPage(), PortalPage(), ProfilePage(), + PeoplePage(), ]; } @@ -69,6 +71,11 @@ class _AppBottomNavigationBarState extends State text: S.of(context).navigationProfile, iconMargin: EdgeInsets.all(0), ), + Tab( + icon: Icon(Icons.people), + text: S.of(context).navigationPeople, + iconMargin: EdgeInsets.all(0), + ), ], labelColor: Theme.of(context).accentColor, labelPadding: EdgeInsets.all(0), diff --git a/lib/pages/people/model/person.dart b/lib/pages/people/model/person.dart new file mode 100644 index 000000000..6094ae1f6 --- /dev/null +++ b/lib/pages/people/model/person.dart @@ -0,0 +1,10 @@ +class Person { + final String name; + final String email; + final String phone; + final String office; + final String position; + final String photo; + + Person({this.name, this.email, this.phone, this.office, this.position, this.photo}); +} \ No newline at end of file diff --git a/lib/pages/people/people_page.dart b/lib/pages/people/people_page.dart deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/pages/people/service/person_provider.dart b/lib/pages/people/service/person_provider.dart new file mode 100644 index 000000000..2e9a4702a --- /dev/null +++ b/lib/pages/people/service/person_provider.dart @@ -0,0 +1,79 @@ +import 'package:acs_upb_mobile/pages/people/model/person.dart'; +import 'package:acs_upb_mobile/pages/people/view/person_view.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/material.dart'; + +extension PersonExtension on Person { + static Person fromSnap(DocumentSnapshot snap) { + return Person( + name: snap.data['name'], + email: snap.data['email'], + phone: snap.data['phone'], + office: snap.data['office'], + position: snap.data['position'], + photo: snap.data['photo'], + ); + } +} + +class ListPage extends StatefulWidget { + @override + _ListPageState createState() => _ListPageState(); +} + +class _ListPageState extends State { + Future _data; + + Future getPeople() async { + QuerySnapshot qn = await Firestore.instance.collection("people").getDocuments(); + return qn.documents; + } + + @override + void initState() { + super.initState(); + _data = getPeople(); + } + + @override + Widget build(BuildContext context) { + return Container( + child: FutureBuilder( + future: _data, + builder: (_, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center( + child: Text("Loading..."), + ); + } else { + return ListView.builder( + itemCount: snapshot.data.length, + itemBuilder: (_, index) { + return ListTile( + leading: CircleAvatar( + backgroundImage: + NetworkImage(snapshot.data[index].data["photo"]), + ), + title: Text(snapshot.data[index].data["name"]), + subtitle: Text(snapshot.data[index].data["email"]), + onTap: () => navigateToDetail(PersonExtension.fromSnap(snapshot.data[index])), + ); + }); + } + }), + ); + } + + navigateToDetail(Person person) { + showModalBottomSheet( + isScrollControlled: true, + context: context, + // TODO: Fix size for long position name + builder: (BuildContext buildContext) { + return PersonView( + person: person, + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/pages/people/view/people_page.dart b/lib/pages/people/view/people_page.dart new file mode 100644 index 000000000..0bbd30947 --- /dev/null +++ b/lib/pages/people/view/people_page.dart @@ -0,0 +1,15 @@ +import 'package:acs_upb_mobile/generated/l10n.dart'; +import 'package:acs_upb_mobile/pages/people/service/person_provider.dart'; +import 'package:acs_upb_mobile/widgets/scaffold.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class PeoplePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return AppScaffold( + title: S.of(context).navigationPeople, + body: ListPage(), + ); + } +} diff --git a/lib/pages/people/view/person_view.dart b/lib/pages/people/view/person_view.dart new file mode 100644 index 000000000..a946567df --- /dev/null +++ b/lib/pages/people/view/person_view.dart @@ -0,0 +1,124 @@ +import 'package:acs_upb_mobile/pages/people/model/person.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class PersonView extends StatelessWidget { + final Person person; + + const PersonView({Key key, this.person}) : super(key: key); + @override + Widget build(BuildContext context) { + return IntrinsicHeight( + child: Column( + children: [ + Container( + width: MediaQuery.of(context).size.width, + decoration: new BoxDecoration( + color: const Color(0xFF43ADCD), + shape: BoxShape.rectangle, + border: new Border.all(color: Color(0xFF43ADCD), width: 10), + ), + child: Center( + child: RichText( + text: TextSpan(text: person.name, + style: Theme.of(context).textTheme.headline6 + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: IntrinsicHeight( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 8.0, right: 16.0), + child: person.photo != null + ? CircleAvatar( + maxRadius: 50, + backgroundImage: CachedNetworkImageProvider( + person.photo), + ) + : CircleAvatar( + radius: 50, + child: Icon( + Icons.person, + size: 50, + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + maxLines: 2, + softWrap: true, + text: TextSpan(children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.email), + ) + ), + TextSpan(text: person.email ?? '-') + ]), + ), + SizedBox(height: 8), + RichText( + maxLines: 2, + softWrap: true, + text: TextSpan(children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.phone), + ) + ), + TextSpan(text: person.phone ?? '-') + ]), + ), + SizedBox(height: 8), + RichText( + maxLines: 2, + softWrap: true, + text: TextSpan(children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.location_on), + ) + ), + TextSpan(text: person.office ?? '-') + ]), + ), + SizedBox(height: 8), + RichText( + maxLines: 2, + softWrap: true, + text: TextSpan(children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.work), + ) + ), + TextSpan(text: person.position ?? '-') + ]), + ) + ], + ), + ], + ), + ), + ), + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index e48ae615a..039a7df5f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -35,7 +35,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety" + version: "2.4.1" auto_size_text: dependency: "direct main" description: @@ -56,14 +56,14 @@ packages: name: black_hole_flutter url: "https://pub.dartlang.org" source: hosted - version: "0.2.10" + version: "0.2.13" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety" + version: "2.0.0" cached_network_image: dependency: "direct main" description: @@ -77,21 +77,21 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.2" + version: "1.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety" + version: "1.1.3" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.0.1" cloud_firestore: dependency: "direct main" description: @@ -119,7 +119,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.2" + version: "1.14.12" color: dependency: transitive description: @@ -196,7 +196,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.1.0" firebase: dependency: "direct main" description: @@ -391,14 +391,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety" + version: "0.12.6" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.1.8" mockito: dependency: "direct dev" description: @@ -454,7 +454,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety" + version: "1.7.0" path_provider: dependency: transitive description: @@ -559,14 +559,14 @@ packages: name: resource url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "2.1.7" rxdart: dependency: transitive description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.24.0" + version: "0.24.1" shared_preferences: dependency: transitive description: @@ -606,7 +606,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety" + version: "1.7.0" sqflite: dependency: transitive description: @@ -620,21 +620,21 @@ packages: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety" + version: "1.9.3" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety" + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety" + version: "1.0.5" synchronized: dependency: transitive description: @@ -648,14 +648,14 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety" + version: "1.1.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety" + version: "0.2.16" time: dependency: transitive description: @@ -669,21 +669,21 @@ packages: name: time_machine url: "https://pub.dartlang.org" source: hosted - version: "0.9.12" + version: "0.9.13" timetable: dependency: "direct main" description: name: timetable url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.2.7" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.1.6" url_launcher: dependency: "direct main" description: @@ -732,7 +732,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.2" + version: "2.0.8" watcher: dependency: transitive description: @@ -755,5 +755,5 @@ packages: source: hosted version: "2.2.0" sdks: - dart: ">=2.10.0-0.0.dev <2.10.0" - flutter: ">=1.12.13+hotfix.4 <2.0.0" + dart: ">=2.7.0 <3.0.0" + flutter: ">=1.17.0 <2.0.0" From f746c5fdda7afd6cc830d918412d0e54731cd970 Mon Sep 17 00:00:00 2001 From: Andrei-Constantin Mirciu Date: Sat, 5 Sep 2020 15:28:27 +0300 Subject: [PATCH 3/9] Separate UI from the database --- lib/main.dart | 2 + lib/navigation/bottom_navigation_bar.dart | 2 +- lib/pages/people/model/person.dart | 2 +- lib/pages/people/service/person_provider.dart | 83 +++++-------------- lib/pages/people/view/people_page.dart | 74 ++++++++++++++++- lib/pages/people/view/person_view.dart | 78 +++++++++-------- pubspec.lock | 38 ++++----- 7 files changed, 155 insertions(+), 124 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 6b0f7ea7d..3b4954a8f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:acs_upb_mobile/navigation/routes.dart'; import 'package:acs_upb_mobile/pages/classes/service/class_provider.dart'; import 'package:acs_upb_mobile/pages/filter/service/filter_provider.dart'; import 'package:acs_upb_mobile/pages/filter/view/filter_page.dart'; +import 'package:acs_upb_mobile/pages/people/service/person_provider.dart'; import 'package:acs_upb_mobile/pages/portal/service/website_provider.dart'; import 'package:acs_upb_mobile/pages/settings/settings_page.dart'; import 'package:acs_upb_mobile/resources/locale_provider.dart'; @@ -33,6 +34,7 @@ main() async { ChangeNotifierProvider(create: (_) => StorageProvider()), ChangeNotifierProvider(create: (_) => WebsiteProvider()), ChangeNotifierProvider(create: (_) => ClassProvider()), + ChangeNotifierProvider(create: (_) => PersonProvider()), ChangeNotifierProvider( create: (_) => FilterProvider(global: true)), ], child: MyApp())); diff --git a/lib/navigation/bottom_navigation_bar.dart b/lib/navigation/bottom_navigation_bar.dart index ceab847ab..46e94545f 100644 --- a/lib/navigation/bottom_navigation_bar.dart +++ b/lib/navigation/bottom_navigation_bar.dart @@ -23,7 +23,6 @@ class _AppBottomNavigationBarState extends State @override void initState() { super.initState(); - tabController = TabController(vsync: this, length: 5); tabs = [ HomePage(), ClassesPage(), @@ -31,6 +30,7 @@ class _AppBottomNavigationBarState extends State ProfilePage(), PeoplePage(), ]; + tabController = TabController(vsync: this, length: tabs.length); } @override diff --git a/lib/pages/people/model/person.dart b/lib/pages/people/model/person.dart index 6094ae1f6..479c4b037 100644 --- a/lib/pages/people/model/person.dart +++ b/lib/pages/people/model/person.dart @@ -7,4 +7,4 @@ class Person { final String photo; Person({this.name, this.email, this.phone, this.office, this.position, this.photo}); -} \ No newline at end of file +} diff --git a/lib/pages/people/service/person_provider.dart b/lib/pages/people/service/person_provider.dart index 2e9a4702a..6a37a1536 100644 --- a/lib/pages/people/service/person_provider.dart +++ b/lib/pages/people/service/person_provider.dart @@ -1,6 +1,8 @@ +import 'package:acs_upb_mobile/generated/l10n.dart'; import 'package:acs_upb_mobile/pages/people/model/person.dart'; -import 'package:acs_upb_mobile/pages/people/view/person_view.dart'; +import 'package:acs_upb_mobile/widgets/toast.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; extension PersonExtension on Person { @@ -16,64 +18,23 @@ extension PersonExtension on Person { } } -class ListPage extends StatefulWidget { - @override - _ListPageState createState() => _ListPageState(); -} - -class _ListPageState extends State { - Future _data; - - Future getPeople() async { - QuerySnapshot qn = await Firestore.instance.collection("people").getDocuments(); - return qn.documents; - } - - @override - void initState() { - super.initState(); - _data = getPeople(); - } - - @override - Widget build(BuildContext context) { - return Container( - child: FutureBuilder( - future: _data, - builder: (_, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center( - child: Text("Loading..."), - ); - } else { - return ListView.builder( - itemCount: snapshot.data.length, - itemBuilder: (_, index) { - return ListTile( - leading: CircleAvatar( - backgroundImage: - NetworkImage(snapshot.data[index].data["photo"]), - ), - title: Text(snapshot.data[index].data["name"]), - subtitle: Text(snapshot.data[index].data["email"]), - onTap: () => navigateToDetail(PersonExtension.fromSnap(snapshot.data[index])), - ); - }); - } - }), - ); - } - - navigateToDetail(Person person) { - showModalBottomSheet( - isScrollControlled: true, - context: context, - // TODO: Fix size for long position name - builder: (BuildContext buildContext) { - return PersonView( - person: person, - ); - }, - ); +class PersonProvider with ChangeNotifier { + Future> fetchPeople(BuildContext context) async { + try { + List people = []; + List documents = []; + QuerySnapshot qSnapshot = + await Firestore.instance.collection("people").getDocuments(); + documents.addAll(qSnapshot.documents); + people.addAll(documents.map((doc) => PersonExtension.fromSnap(doc))); + //return List.from(qSnapshot.documents ?? []); + return people; + } catch (e) { + print(e); + if (context != null) { + AppToast.show(S.of(context).errorSomethingWentWrong); + } + return null; + } } -} \ No newline at end of file +} diff --git a/lib/pages/people/view/people_page.dart b/lib/pages/people/view/people_page.dart index 0bbd30947..3a0ef5dfd 100644 --- a/lib/pages/people/view/people_page.dart +++ b/lib/pages/people/view/people_page.dart @@ -1,15 +1,85 @@ import 'package:acs_upb_mobile/generated/l10n.dart'; +import 'package:acs_upb_mobile/pages/people/model/person.dart'; import 'package:acs_upb_mobile/pages/people/service/person_provider.dart'; +import 'package:acs_upb_mobile/pages/people/view/person_view.dart'; import 'package:acs_upb_mobile/widgets/scaffold.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'dart:developer' as developer; + +class PeoplePage extends StatefulWidget { + @override + _PeoplePageState createState() => _PeoplePageState(); +} + +class _PeoplePageState extends State { + Future> people; + + @override + void initState() { + super.initState(); + PersonProvider personProvider = + Provider.of(context, listen: false); + people = personProvider.fetchPeople(context); + if (people == null) { + developer.log("mda"); + } + } -class PeoplePage extends StatelessWidget { @override Widget build(BuildContext context) { return AppScaffold( title: S.of(context).navigationPeople, - body: ListPage(), + body: Container( + child: FutureBuilder( + future: people, + builder: (_, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting || + snapshot.connectionState == ConnectionState.none) { + return Center( + child: Text("Loading..."), + ); + } else if (snapshot.connectionState == ConnectionState.done) { + return ListView.builder( + itemCount: snapshot.data.length, + itemBuilder: (_, index) { + return ListTile( + leading: CircleAvatar( + backgroundImage: NetworkImage( + PersonExtension.fromSnap(snapshot.data[index]) + .photo), + ), + title: Text( + PersonExtension.fromSnap(snapshot.data[index]) + .name), + subtitle: Text( + PersonExtension.fromSnap(snapshot.data[index]) + .email), + onTap: () => showPersonInfo( + PersonExtension.fromSnap(snapshot.data[index])), + ); + }); + } else { + return Center( + child: Text("Loading..."), + ); + } + }), + ), + ); + } + + showPersonInfo(Person person) { + showModalBottomSheet( + isScrollControlled: true, + context: context, + // TODO: Fix size for long position name + builder: (BuildContext buildContext) { + return PersonView( + person: person, + ); + }, ); } } diff --git a/lib/pages/people/view/person_view.dart b/lib/pages/people/view/person_view.dart index a946567df..26391de81 100644 --- a/lib/pages/people/view/person_view.dart +++ b/lib/pages/people/view/person_view.dart @@ -7,6 +7,7 @@ class PersonView extends StatelessWidget { final Person person; const PersonView({Key key, this.person}) : super(key: key); + @override Widget build(BuildContext context) { return IntrinsicHeight( @@ -15,15 +16,16 @@ class PersonView extends StatelessWidget { Container( width: MediaQuery.of(context).size.width, decoration: new BoxDecoration( - color: const Color(0xFF43ADCD), - shape: BoxShape.rectangle, - border: new Border.all(color: Color(0xFF43ADCD), width: 10), + color: Theme.of(context).accentColor, + shape: BoxShape.rectangle, + border: new Border.all( + color: Theme.of(context).accentColor, width: 10), ), child: Center( child: RichText( - text: TextSpan(text: person.name, - style: Theme.of(context).textTheme.headline6 - ), + text: TextSpan( + text: person.name, + style: Theme.of(context).textTheme.headline6), ), ), ), @@ -39,17 +41,17 @@ class PersonView extends StatelessWidget { padding: const EdgeInsets.only(left: 8.0, right: 16.0), child: person.photo != null ? CircleAvatar( - maxRadius: 50, - backgroundImage: CachedNetworkImageProvider( - person.photo), - ) + maxRadius: 50, + backgroundImage: + CachedNetworkImageProvider(person.photo), + ) : CircleAvatar( - radius: 50, - child: Icon( - Icons.person, - size: 50, - ), - ), + radius: 50, + child: Icon( + Icons.person, + size: 50, + ), + ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -59,12 +61,11 @@ class PersonView extends StatelessWidget { softWrap: true, text: TextSpan(children: [ WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.email), - ) - ), - TextSpan(text: person.email ?? '-') + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.email), + )), + TextSpan(text: person.email ?? '-'), ]), ), SizedBox(height: 8), @@ -73,12 +74,11 @@ class PersonView extends StatelessWidget { softWrap: true, text: TextSpan(children: [ WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.phone), - ) - ), - TextSpan(text: person.phone ?? '-') + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.phone), + )), + TextSpan(text: person.phone ?? '-'), ]), ), SizedBox(height: 8), @@ -87,12 +87,11 @@ class PersonView extends StatelessWidget { softWrap: true, text: TextSpan(children: [ WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.location_on), - ) - ), - TextSpan(text: person.office ?? '-') + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.location_on), + )), + TextSpan(text: person.office ?? '-'), ]), ), SizedBox(height: 8), @@ -102,11 +101,10 @@ class PersonView extends StatelessWidget { text: TextSpan(children: [ WidgetSpan( child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.work), - ) - ), - TextSpan(text: person.position ?? '-') + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.work), + )), + TextSpan(text: person.position ?? '-'), ]), ) ], @@ -121,4 +119,4 @@ class PersonView extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/pubspec.lock b/pubspec.lock index 039a7df5f..fa3fabff1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -35,7 +35,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.5.0-nullsafety" auto_size_text: dependency: "direct main" description: @@ -63,7 +63,7 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety" cached_network_image: dependency: "direct main" description: @@ -77,21 +77,21 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0-nullsafety.2" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0-nullsafety" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0-nullsafety" cloud_firestore: dependency: "direct main" description: @@ -119,7 +119,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.15.0-nullsafety.2" color: dependency: transitive description: @@ -196,7 +196,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.0-nullsafety" firebase: dependency: "direct main" description: @@ -391,14 +391,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10-nullsafety" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.2" mockito: dependency: "direct dev" description: @@ -454,7 +454,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety" path_provider: dependency: transitive description: @@ -606,7 +606,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety" sqflite: dependency: transitive description: @@ -620,21 +620,21 @@ packages: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0-nullsafety" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0-nullsafety" synchronized: dependency: transitive description: @@ -648,14 +648,14 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.16" + version: "0.2.19-nullsafety" time: dependency: transitive description: @@ -683,7 +683,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0-nullsafety.2" url_launcher: dependency: "direct main" description: @@ -732,7 +732,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.2" watcher: dependency: transitive description: @@ -755,5 +755,5 @@ packages: source: hosted version: "2.2.0" sdks: - dart: ">=2.7.0 <3.0.0" + dart: ">=2.10.0-0.0.dev <2.10.0" flutter: ">=1.17.0 <2.0.0" From ea6375ed18bb369a4242990a9ae42269471875cf Mon Sep 17 00:00:00 2001 From: Andrei-Constantin Mirciu Date: Sat, 5 Sep 2020 19:29:21 +0300 Subject: [PATCH 4/9] Separate UI from the database --- lib/pages/people/service/person_provider.dart | 1 - lib/pages/people/view/people_page.dart | 39 ++++----- lib/pages/people/view/person_view.dart | 80 ++++++++++--------- 3 files changed, 59 insertions(+), 61 deletions(-) diff --git a/lib/pages/people/service/person_provider.dart b/lib/pages/people/service/person_provider.dart index 6a37a1536..5436c8444 100644 --- a/lib/pages/people/service/person_provider.dart +++ b/lib/pages/people/service/person_provider.dart @@ -27,7 +27,6 @@ class PersonProvider with ChangeNotifier { await Firestore.instance.collection("people").getDocuments(); documents.addAll(qSnapshot.documents); people.addAll(documents.map((doc) => PersonExtension.fromSnap(doc))); - //return List.from(qSnapshot.documents ?? []); return people; } catch (e) { print(e); diff --git a/lib/pages/people/view/people_page.dart b/lib/pages/people/view/people_page.dart index 3a0ef5dfd..e7709f3b0 100644 --- a/lib/pages/people/view/people_page.dart +++ b/lib/pages/people/view/people_page.dart @@ -3,10 +3,10 @@ import 'package:acs_upb_mobile/pages/people/model/person.dart'; import 'package:acs_upb_mobile/pages/people/service/person_provider.dart'; import 'package:acs_upb_mobile/pages/people/view/person_view.dart'; import 'package:acs_upb_mobile/widgets/scaffold.dart'; +import 'package:acs_upb_mobile/widgets/toast.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'dart:developer' as developer; class PeoplePage extends StatefulWidget { @override @@ -22,14 +22,15 @@ class _PeoplePageState extends State { PersonProvider personProvider = Provider.of(context, listen: false); people = personProvider.fetchPeople(context); - if (people == null) { - developer.log("mda"); - } } @override Widget build(BuildContext context) { return AppScaffold( + actions: [ + AppScaffoldAction( + icon: Icons.search, onPressed: () => AppToast.show("buna")), + ], title: S.of(context).navigationPeople, body: Container( child: FutureBuilder( @@ -41,25 +42,17 @@ class _PeoplePageState extends State { child: Text("Loading..."), ); } else if (snapshot.connectionState == ConnectionState.done) { - return ListView.builder( - itemCount: snapshot.data.length, - itemBuilder: (_, index) { - return ListTile( - leading: CircleAvatar( - backgroundImage: NetworkImage( - PersonExtension.fromSnap(snapshot.data[index]) - .photo), - ), - title: Text( - PersonExtension.fromSnap(snapshot.data[index]) - .name), - subtitle: Text( - PersonExtension.fromSnap(snapshot.data[index]) - .email), - onTap: () => showPersonInfo( - PersonExtension.fromSnap(snapshot.data[index])), - ); - }); + return ListView.builder(itemBuilder: (_, index) { + List peopleData = snapshot.data; + return ListTile( + leading: CircleAvatar( + backgroundImage: NetworkImage(peopleData[index].photo), + ), + title: Text(peopleData[index].name), + subtitle: Text(peopleData[index].email), + onTap: () => showPersonInfo(peopleData[index]), + ); + }); } else { return Center( child: Text("Loading..."), diff --git a/lib/pages/people/view/person_view.dart b/lib/pages/people/view/person_view.dart index 26391de81..bc64ea29c 100644 --- a/lib/pages/people/view/person_view.dart +++ b/lib/pages/people/view/person_view.dart @@ -22,11 +22,8 @@ class PersonView extends StatelessWidget { color: Theme.of(context).accentColor, width: 10), ), child: Center( - child: RichText( - text: TextSpan( - text: person.name, - style: Theme.of(context).textTheme.headline6), - ), + child: Text(person.name, + style: Theme.of(context).textTheme.headline6), ), ), Padding( @@ -59,53 +56,62 @@ class PersonView extends StatelessWidget { RichText( maxLines: 2, softWrap: true, - text: TextSpan(children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.email), - )), - TextSpan(text: person.email ?? '-'), - ]), + text: TextSpan( + style: Theme.of(context).textTheme.bodyText1, + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.email), + )), + TextSpan(text: person.email ?? '-'), + ]), ), SizedBox(height: 8), RichText( maxLines: 2, softWrap: true, - text: TextSpan(children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.phone), - )), - TextSpan(text: person.phone ?? '-'), - ]), + text: TextSpan( + style: Theme.of(context).textTheme.bodyText1, + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.phone), + )), + TextSpan(text: person.phone ?? '-'), + ]), ), SizedBox(height: 8), RichText( maxLines: 2, softWrap: true, - text: TextSpan(children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.location_on), - )), - TextSpan(text: person.office ?? '-'), - ]), + text: TextSpan( + style: Theme.of(context).textTheme.bodyText1, + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.location_on), + )), + TextSpan(text: person.office ?? '-'), + ]), ), SizedBox(height: 8), RichText( maxLines: 2, softWrap: true, - text: TextSpan(children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.work), - )), - TextSpan(text: person.position ?? '-'), - ]), + text: TextSpan( + //TODO This line generates RenderFlex Overflow + //style: Theme.of(context).textTheme.bodyText1, + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 4.0), + child: Icon(Icons.work), + )), + TextSpan(text: person.position ?? '-'), + ]), ) ], ), From 9f3325b0ebdb8e296491c632c4dd9ebc3191b7e3 Mon Sep 17 00:00:00 2001 From: Andrei-Constantin Mirciu Date: Fri, 11 Sep 2020 14:49:33 +0300 Subject: [PATCH 5/9] Fix RenderFlex overflow at ModalBottomSheet --- lib/pages/people/service/person_provider.dart | 8 +- lib/pages/people/view/people_page.dart | 15 +- lib/pages/people/view/person_view.dart | 175 +++++++----------- 3 files changed, 73 insertions(+), 125 deletions(-) diff --git a/lib/pages/people/service/person_provider.dart b/lib/pages/people/service/person_provider.dart index 5436c8444..eced430db 100644 --- a/lib/pages/people/service/person_provider.dart +++ b/lib/pages/people/service/person_provider.dart @@ -21,13 +21,11 @@ extension PersonExtension on Person { class PersonProvider with ChangeNotifier { Future> fetchPeople(BuildContext context) async { try { - List people = []; - List documents = []; QuerySnapshot qSnapshot = await Firestore.instance.collection("people").getDocuments(); - documents.addAll(qSnapshot.documents); - people.addAll(documents.map((doc) => PersonExtension.fromSnap(doc))); - return people; + return qSnapshot.documents + .map((doc) => PersonExtension.fromSnap(doc)) + .toList(); } catch (e) { print(e); if (context != null) { diff --git a/lib/pages/people/view/people_page.dart b/lib/pages/people/view/people_page.dart index 120074e72..d40a52c89 100644 --- a/lib/pages/people/view/people_page.dart +++ b/lib/pages/people/view/people_page.dart @@ -3,7 +3,6 @@ import 'package:acs_upb_mobile/pages/people/model/person.dart'; import 'package:acs_upb_mobile/pages/people/service/person_provider.dart'; import 'package:acs_upb_mobile/pages/people/view/person_view.dart'; import 'package:acs_upb_mobile/widgets/scaffold.dart'; -import 'package:acs_upb_mobile/widgets/toast.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -29,21 +28,12 @@ class _PeoplePageState extends State { @override Widget build(BuildContext context) { return AppScaffold( - actions: [ - AppScaffoldAction( - icon: Icons.search, onPressed: () => AppToast.show("buna")), - ], title: S.of(context).navigationPeople, body: Container( child: FutureBuilder( future: people, builder: (_, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting || - snapshot.connectionState == ConnectionState.none) { - return Center( - child: Text("Loading..."), - ); - } else if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.connectionState == ConnectionState.done) { return ListView.builder(itemBuilder: (_, index) { List peopleData = snapshot.data; return ListTile( @@ -57,7 +47,7 @@ class _PeoplePageState extends State { }); } else { return Center( - child: Text("Loading..."), + child: CircularProgressIndicator(), ); } }), @@ -69,7 +59,6 @@ class _PeoplePageState extends State { showModalBottomSheet( isScrollControlled: true, context: context, - // TODO: Fix size for long position name builder: (BuildContext buildContext) { return PersonView( person: person, diff --git a/lib/pages/people/view/person_view.dart b/lib/pages/people/view/person_view.dart index bc64ea29c..bcb393ac1 100644 --- a/lib/pages/people/view/person_view.dart +++ b/lib/pages/people/view/person_view.dart @@ -1,4 +1,5 @@ import 'package:acs_upb_mobile/pages/people/model/person.dart'; +import 'package:acs_upb_mobile/widgets/icon_text.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -10,119 +11,79 @@ class PersonView extends StatelessWidget { @override Widget build(BuildContext context) { - return IntrinsicHeight( - child: Column( - children: [ - Container( - width: MediaQuery.of(context).size.width, - decoration: new BoxDecoration( - color: Theme.of(context).accentColor, - shape: BoxShape.rectangle, - border: new Border.all( - color: Theme.of(context).accentColor, width: 10), - ), - child: Center( - child: Text(person.name, - style: Theme.of(context).textTheme.headline6), - ), + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: Theme.of(context).accentColor, + shape: BoxShape.rectangle, + border: Border.all(color: Theme.of(context).accentColor, width: 10), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: IntrinsicHeight( - child: Row( - children: [ - Padding( - padding: const EdgeInsets.only(left: 8.0, right: 16.0), - child: person.photo != null - ? CircleAvatar( - maxRadius: 50, - backgroundImage: - CachedNetworkImageProvider(person.photo), - ) - : CircleAvatar( - radius: 50, - child: Icon( - Icons.person, - size: 50, - ), + child: Center( + child: + Text(person.name, style: Theme.of(context).textTheme.headline6), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.0, right: 16.0), + child: person.photo != null + ? CircleAvatar( + maxRadius: 50, + backgroundImage: + CachedNetworkImageProvider(person.photo), + ) + : CircleAvatar( + radius: 50, + child: Icon( + Icons.person, + size: 50, ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - maxLines: 2, - softWrap: true, - text: TextSpan( - style: Theme.of(context).textTheme.bodyText1, - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.email), - )), - TextSpan(text: person.email ?? '-'), - ]), - ), - SizedBox(height: 8), - RichText( - maxLines: 2, - softWrap: true, - text: TextSpan( - style: Theme.of(context).textTheme.bodyText1, - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.phone), - )), - TextSpan(text: person.phone ?? '-'), - ]), - ), - SizedBox(height: 8), - RichText( - maxLines: 2, - softWrap: true, - text: TextSpan( - style: Theme.of(context).textTheme.bodyText1, - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.location_on), - )), - TextSpan(text: person.office ?? '-'), - ]), - ), - SizedBox(height: 8), - RichText( - maxLines: 2, - softWrap: true, - text: TextSpan( - //TODO This line generates RenderFlex Overflow - //style: Theme.of(context).textTheme.bodyText1, - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: Icon(Icons.work), - )), - TextSpan(text: person.position ?? '-'), - ]), - ) - ], - ), - ], + ), + ), + ), + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconText( + icon: Icons.email, + text: person.email ?? '-', + style: Theme.of(context).textTheme.bodyText1), + SizedBox(height: 8), + IconText( + icon: Icons.phone, + text: person.phone ?? '-', + style: Theme.of(context).textTheme.bodyText1), + SizedBox(height: 8), + IconText( + icon: Icons.location_on, + text: person.office ?? '-', + style: Theme.of(context).textTheme.bodyText1), + SizedBox(height: 8), + IconText( + icon: Icons.work, + text: person.position ?? '-', + style: Theme.of(context).textTheme.bodyText1), + ], + ), ), - ), + ], ), ), ), - ], - ), + ), + ], ); } } From d523ab56df93d918ea4456f417ecf0b98caaaeb5 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Wed, 16 Sep 2020 11:16:55 +0300 Subject: [PATCH 6/9] test people page --- lib/pages/people/service/person_provider.dart | 2 +- lib/pages/people/view/people_page.dart | 27 ++++--- pubspec.lock | 7 ++ pubspec.yaml | 3 + test/integration_test.dart | 74 +++++++++++++++++++ 5 files changed, 100 insertions(+), 13 deletions(-) diff --git a/lib/pages/people/service/person_provider.dart b/lib/pages/people/service/person_provider.dart index eced430db..12eea23cb 100644 --- a/lib/pages/people/service/person_provider.dart +++ b/lib/pages/people/service/person_provider.dart @@ -19,7 +19,7 @@ extension PersonExtension on Person { } class PersonProvider with ChangeNotifier { - Future> fetchPeople(BuildContext context) async { + Future> fetchPeople({BuildContext context}) async { try { QuerySnapshot qSnapshot = await Firestore.instance.collection("people").getDocuments(); diff --git a/lib/pages/people/view/people_page.dart b/lib/pages/people/view/people_page.dart index d40a52c89..710145eda 100644 --- a/lib/pages/people/view/people_page.dart +++ b/lib/pages/people/view/people_page.dart @@ -22,7 +22,7 @@ class _PeoplePageState extends State { super.initState(); PersonProvider personProvider = Provider.of(context, listen: false); - people = personProvider.fetchPeople(context); + people = personProvider.fetchPeople(context: context); } @override @@ -34,17 +34,20 @@ class _PeoplePageState extends State { future: people, builder: (_, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - return ListView.builder(itemBuilder: (_, index) { - List peopleData = snapshot.data; - return ListTile( - leading: CircleAvatar( - backgroundImage: NetworkImage(peopleData[index].photo), - ), - title: Text(peopleData[index].name), - subtitle: Text(peopleData[index].email), - onTap: () => showPersonInfo(peopleData[index]), - ); - }); + List peopleData = snapshot.data; + return ListView.builder( + itemCount: peopleData.length, + itemBuilder: (_, index) { + return ListTile( + leading: CircleAvatar( + backgroundImage: + NetworkImage(peopleData[index].photo), + ), + title: Text(peopleData[index].name), + subtitle: Text(peopleData[index].email), + onTap: () => showPersonInfo(peopleData[index]), + ); + }); } else { return Center( child: CircularProgressIndicator(), diff --git a/pubspec.lock b/pubspec.lock index fa3fabff1..c3ac6ca3a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -413,6 +413,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.4" + network_image_mock: + dependency: "direct dev" + description: + name: network_image_mock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" node_interop: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 19da43f3b..31c0ee2e0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -114,6 +114,9 @@ dev_dependencies: # Provider platform interfaces of federated flutter plugins, for testing (like url_launcher) plugin_platform_interface: ^1.0.0 + # Testing utils + network_image_mock: ^1.0.1 + dependency_overrides: # Workaround because timetable depends on pedantic 1.9.0 while flutter_test depends on pedantic 1.8.0 pedantic: 1.9.0 diff --git a/test/integration_test.dart b/test/integration_test.dart index 5f5a7d5fa..f01873ba7 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -11,6 +11,10 @@ import 'package:acs_upb_mobile/pages/filter/model/filter.dart'; import 'package:acs_upb_mobile/pages/filter/service/filter_provider.dart'; import 'package:acs_upb_mobile/pages/filter/view/filter_page.dart'; import 'package:acs_upb_mobile/pages/home/home_page.dart'; +import 'package:acs_upb_mobile/pages/people/model/person.dart'; +import 'package:acs_upb_mobile/pages/people/service/person_provider.dart'; +import 'package:acs_upb_mobile/pages/people/view/people_page.dart'; +import 'package:acs_upb_mobile/pages/people/view/person_view.dart'; import 'package:acs_upb_mobile/pages/portal/model/website.dart'; import 'package:acs_upb_mobile/pages/portal/service/website_provider.dart'; import 'package:acs_upb_mobile/pages/portal/view/portal_page.dart'; @@ -22,6 +26,7 @@ import 'package:acs_upb_mobile/resources/storage_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:network_image_mock/network_image_mock.dart'; import 'package:preferences/preferences.dart'; import 'package:provider/provider.dart'; @@ -38,6 +43,8 @@ class MockFilterProvider extends Mock implements FilterProvider {} class MockClassProvider extends Mock implements ClassProvider {} +class MockPersonProvider extends Mock implements PersonProvider {} + class MockNavigatorObserver extends Mock implements NavigatorObserver {} void main() { @@ -46,6 +53,7 @@ void main() { WebsiteProvider mockWebsiteProvider; FilterProvider mockFilterProvider; ClassProvider mockClassProvider; + PersonProvider mockPersonProvider; // Test layout for different screen sizes List screenSizes = [ @@ -81,6 +89,8 @@ void main() { create: (_) => mockFilterProvider), ChangeNotifierProvider( create: (_) => mockClassProvider), + ChangeNotifierProvider( + create: (_) => mockPersonProvider), ], child: MyApp(), ); @@ -317,6 +327,37 @@ void main() { }, ), )); + + mockPersonProvider = MockPersonProvider(); + // ignore: invalid_use_of_protected_member + when(mockPersonProvider.hasListeners).thenReturn(false); + when(mockPersonProvider.fetchPeople(context: anyNamed('context'))) + .thenAnswer((_) => Future.value([ + Person( + name: 'John Doe', + email: 'john.doe@cs.pub.ro', + phone: '0712345678', + office: 'AB123', + position: 'Associate Professor, Dr., Department Council', + photo: 'https://cdn.worldvectorlogo.com/logos/flutter-logo.svg', + ), + Person( + name: 'Jane Doe', + email: 'jane.doe@cs.pub.ro', + phone: '-', + office: 'Narnia', + position: 'Professor, Dr.', + photo: 'https://cdn.worldvectorlogo.com/logos/flutter-logo.svg', + ), + Person( + name: 'Mary Poppins', + email: 'supercalifragilistic.expialidocious@cs.pub.ro', + phone: '0712-345-678', + office: 'Mary Poppins\' office', + position: 'Professor, Dr., Head of Department', + photo: 'https://cdn.worldvectorlogo.com/logos/flutter-logo.svg', + ), + ])); }); group('Home', () { @@ -657,4 +698,37 @@ void main() { }); } }); + + group('People page', () { + setUp(() { + when(mockAuthProvider.isAuthenticatedFromCache).thenReturn(true); + when(mockAuthProvider.isAnonymous).thenReturn(true); + }); + + for (var size in screenSizes) { + testWidgets('${size.width}x${size.height}', (WidgetTester tester) async { + await binding.setSurfaceSize(size); + + mockNetworkImagesFor(() async { + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + + // Open people page + await tester.tap(find.byIcon(Icons.people)); + await tester.pumpAndSettle(); + + expect(find.byType(PeoplePage), findsOneWidget); + + // Open bottom sheet with person info + var names = ['John Doe', 'Jane Doe', 'Mary Poppins']; + for (var name in names) { + await tester.tap(find.text(name)); + await tester.pumpAndSettle(); + } + + expect(find.byType(PersonView), findsOneWidget); + }); + }); + } + }); } From 0e9b070f8e506bbc9c5e7c46c1074f1526c4a300 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Wed, 16 Sep 2020 11:17:35 +0300 Subject: [PATCH 7/9] move people page before profile page --- lib/generated/l10n.dart | 1 + lib/navigation/bottom_navigation_bar.dart | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 916a4b83f..b9a9605b1 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -1,6 +1,7 @@ // GENERATED CODE - DO NOT MODIFY BY HAND import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; + import 'intl/messages_all.dart'; // ************************************************************************** diff --git a/lib/navigation/bottom_navigation_bar.dart b/lib/navigation/bottom_navigation_bar.dart index a8f448011..ab66d61c6 100644 --- a/lib/navigation/bottom_navigation_bar.dart +++ b/lib/navigation/bottom_navigation_bar.dart @@ -71,13 +71,13 @@ class _AppBottomNavigationBarState extends State iconMargin: EdgeInsets.all(0), ), Tab( - icon: Icon(Icons.person), - text: S.of(context).navigationProfile, + icon: Icon(Icons.people), + text: S.of(context).navigationPeople, iconMargin: EdgeInsets.all(0), ), Tab( - icon: Icon(Icons.people), - text: S.of(context).navigationPeople, + icon: Icon(Icons.person), + text: S.of(context).navigationProfile, iconMargin: EdgeInsets.all(0), ), ], From aec89047b2cb35b0488c165d7fccb47058315c03 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Wed, 16 Sep 2020 11:21:20 +0300 Subject: [PATCH 8/9] bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 31c0ee2e0..aa36f287f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: A mobile application for students at ACS UPB. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.5.3+2 +version: 0.6.0+1 environment: sdk: ">=2.6.0 <3.0.0" From e6c697a13d449c162dd3da6580ce64f45e5def45 Mon Sep 17 00:00:00 2001 From: Ioana Alexandru Date: Wed, 16 Sep 2020 11:48:02 +0300 Subject: [PATCH 9/9] fix broken tests --- lib/navigation/bottom_navigation_bar.dart | 2 +- test/authentication_test.dart | 17 +++++++++++++++++ test/integration_test.dart | 14 ++++++++------ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/navigation/bottom_navigation_bar.dart b/lib/navigation/bottom_navigation_bar.dart index ab66d61c6..dbbbe6bc4 100644 --- a/lib/navigation/bottom_navigation_bar.dart +++ b/lib/navigation/bottom_navigation_bar.dart @@ -28,8 +28,8 @@ class _AppBottomNavigationBarState extends State HomePage(key: PageStorageKey('Home')), ClassesPage(key: PageStorageKey('Classes')), PortalPage(key: PageStorageKey('Portal')), - ProfilePage(key: PageStorageKey('Profile')), PeoplePage(key: PageStorageKey('People')), + ProfilePage(key: PageStorageKey('Profile')), ]; tabController = TabController(vsync: this, length: tabs.length); } diff --git a/test/authentication_test.dart b/test/authentication_test.dart index 9396b66cd..a6e773024 100644 --- a/test/authentication_test.dart +++ b/test/authentication_test.dart @@ -6,7 +6,9 @@ import 'package:acs_upb_mobile/main.dart'; import 'package:acs_upb_mobile/pages/filter/model/filter.dart'; import 'package:acs_upb_mobile/pages/filter/service/filter_provider.dart'; import 'package:acs_upb_mobile/pages/home/home_page.dart'; +import 'package:acs_upb_mobile/pages/people/service/person_provider.dart'; import 'package:acs_upb_mobile/pages/portal/service/website_provider.dart'; +import 'package:acs_upb_mobile/pages/profile/profile_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; @@ -21,10 +23,13 @@ class MockFilterProvider extends Mock implements FilterProvider {} class MockWebsiteProvider extends Mock implements WebsiteProvider {} +class MockPersonProvider extends Mock implements PersonProvider {} + void main() { AuthProvider mockAuthProvider; WebsiteProvider mockWebsiteProvider; FilterProvider mockFilterProvider; + PersonProvider mockPersonProvider; setUp(() async { WidgetsFlutterBinding.ensureInitialized(); @@ -57,6 +62,12 @@ void main() { .thenAnswer((_) => Future.value(Filter(localizedLevelNames: [ {'en': 'Level', 'ro': 'Nivel'} ], root: FilterNode(name: 'root')))); + + mockPersonProvider = MockPersonProvider(); + // ignore: invalid_use_of_protected_member + when(mockPersonProvider.hasListeners).thenReturn(false); + when(mockPersonProvider.fetchPeople(context: anyNamed('context'))) + .thenAnswer((_) => Future.value([])); }); group('Login', () { @@ -376,6 +387,8 @@ void main() { create: (_) => mockFilterProvider), ChangeNotifierProvider( create: (_) => mockWebsiteProvider), + ChangeNotifierProvider( + create: (_) => mockPersonProvider), ], child: MyApp(navigationObservers: [mockObserver]))); await tester.pumpAndSettle(); @@ -386,6 +399,7 @@ void main() { await tester.tap(find.byIcon(Icons.person)); await tester.pumpAndSettle(); + expect(find.byType(ProfilePage), findsOneWidget); expect(find.text('Anonymous'), findsOneWidget); // Press log in button @@ -407,6 +421,8 @@ void main() { create: (_) => mockFilterProvider), ChangeNotifierProvider( create: (_) => mockWebsiteProvider), + ChangeNotifierProvider( + create: (_) => mockPersonProvider), ], child: MyApp(navigationObservers: [mockObserver]))); await tester.pumpAndSettle(); @@ -417,6 +433,7 @@ void main() { await tester.tap(find.byIcon(Icons.person)); await tester.pumpAndSettle(); + expect(find.byType(ProfilePage), findsOneWidget); expect(find.text('John Doe'), findsOneWidget); // Press log out button diff --git a/test/integration_test.dart b/test/integration_test.dart index f01873ba7..29a6f89c4 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -494,14 +494,16 @@ void main() { testWidgets('${size.width}x${size.height}', (WidgetTester tester) async { await binding.setSurfaceSize(size); - await tester.pumpWidget(buildApp()); - await tester.pumpAndSettle(); + mockNetworkImagesFor(() async { + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); - // Open profile - await tester.tap(find.byIcon(Icons.person)); - await tester.pumpAndSettle(); + // Open profile + await tester.tap(find.byIcon(Icons.person)); + await tester.pumpAndSettle(); - expect(find.byType(ProfilePage), findsNWidgets(1)); + expect(find.byType(ProfilePage), findsNWidgets(1)); + }); }); } });