Skip to content

Commit

Permalink
feat: group invites
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Czizikow committed Apr 14, 2020
1 parent 65a3eb1 commit c3868ee
Show file tree
Hide file tree
Showing 14 changed files with 687 additions and 92 deletions.
50 changes: 35 additions & 15 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
import 'dart:async';

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:sentry/sentry.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:provider/provider.dart';
import 'package:sentry/sentry.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sliceit/providers/invites.dart';

import './services/api.dart';
import './providers/auth.dart';
import './providers/account.dart';
import './providers/theme.dart';
import './providers/auth.dart';
import './providers/groups.dart';
import './screens/root.dart';
import './screens/login.dart';
import './providers/theme.dart';
import './screens/edit_email.dart';
import './screens/edit_name.dart';
import './screens/forgot_password.dart';
import './screens/register.dart';
import './screens/group.dart';
import './screens/group_invites.dart';
import './screens/login.dart';
import './screens/register.dart';
import './screens/root.dart';
import './screens/settings.dart';
import './screens/edit_name.dart';
import './screens/edit_email.dart';
import './services/api.dart';
import './widgets/no_animation_material_page_route.dart';

Future<Null> main() async {
await DotEnv().load('.env');
final SharedPreferences prefs = await SharedPreferences.getInstance();
final SentryClient _sentry =
new SentryClient(dsn: DotEnv().env['SENTRY_DNS']);
FlutterError.onError = (FlutterErrorDetails details) async {
Expand All @@ -41,7 +45,7 @@ Future<Null> main() async {
// an error handler that captures errors and reports them.
// https://api.dartlang.org/stable/1.24.2/dart-async/Zone-class.html
runZoned<Future<Null>>(() async {
runApp(new MyApp());
runApp(new MyApp(prefs));
}, onError: (error, stackTrace) async {
if (!kReleaseMode) {
print(stackTrace);
Expand All @@ -56,17 +60,21 @@ Future<Null> main() async {
}

class MyApp extends StatefulWidget {
final SharedPreferences prefs;
MyApp(this.prefs);

@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
final Api _api = Api();
ThemeProvider themeProvider = ThemeProvider();
ThemeProvider themeProvider;

@override
void initState() {
super.initState();
themeProvider = ThemeProvider(widget.prefs);
_loadPreferredTheme();
}

Expand Down Expand Up @@ -108,7 +116,17 @@ class _MyAppState extends State<MyApp> {
case GroupScreen.routeName:
return platformPageRoute(
context: context,
builder: (context) => GroupScreen(arguments: settings.arguments),
builder: (context) => GroupScreen(
arguments: settings.arguments,
),
settings: settings,
);
case GroupInvitesScreen.routeName:
return platformPageRoute(
context: context,
builder: (context) => GroupInvitesScreen(
groupId: settings.arguments,
),
settings: settings,
);
case SettingsScreen.routeName:
Expand Down Expand Up @@ -152,9 +170,11 @@ class _MyAppState extends State<MyApp> {
update: (_, auth, previous) => GroupsProvider(
api: _api,
isAuthenticated: auth.isAuthenticated,
prev: previous.groups,
),
),
ChangeNotifierProvider(
create: (_) => InvitesProvider(_api),
),
],
child: Consumer<ThemeProvider>(
builder: (_, theme, __) => PlatformApp(
Expand Down
19 changes: 12 additions & 7 deletions lib/models/group.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';

import './member.dart';

class Group {
final String id;
final String creatorId;
String name;
String currency;
bool isDeleted;
List<Member> members;
final DateTime createdAt;
DateTime updatedAt;

Group({
@required this.id,
@required this.name,
@required this.creatorId,
this.currency,
this.isDeleted = false,
this.members = const [],
@required this.createdAt,
this.updatedAt,
});

factory Group.fromJson(Map<String, dynamic> json) {
return Group(
id: json['id'],
creatorId: json['creatorId'],
name: json['name'],
currency: json['currency'],
isDeleted: json['isDeleted'],
members: json.containsKey('members')
? json['members']
.map<Member>((member) => Member.fromJson(member))
.toList()
: [],
createdAt: DateTime.parse(json['createdAt']),
updatedAt:
json['updatedAt'] != null ? DateTime.parse(json['updatedAt']) : null,
Expand All @@ -35,10 +46,4 @@ class Group {
json.map<Group>((json) => Group.fromJson(json)).toList();
return result;
}

static Group parseGroup(String responseBody) {
final Map<String, dynamic> parsed =
jsonDecode(responseBody) as Map<String, dynamic>;
return Group.fromJson(parsed);
}
}
29 changes: 29 additions & 0 deletions lib/models/invite.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:flutter/foundation.dart';

@immutable
class Invite {
final String id;
final String email;
final String groupId;
final DateTime createdAt;
final DateTime updatedAt;

Invite({
@required this.id,
@required this.email,
@required this.groupId,
@required this.createdAt,
this.updatedAt,
});

factory Invite.fromJson(Map<String, dynamic> json) {
return Invite(
id: json['id'],
email: json['email'],
groupId: json['groupId'],
createdAt: DateTime.parse(json['createdAt']),
updatedAt:
json['updatedAt'] != null ? DateTime.parse(json['updatedAt']) : null,
);
}
}
38 changes: 38 additions & 0 deletions lib/models/member.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/foundation.dart';

class Member {
final String id;
final String userId;
final String groupId;
final String firstName;
final String lastName;
final String avatar;
int balance;

Member({
@required this.id,
@required this.userId,
@required this.groupId,
@required this.firstName,
@required this.lastName,
this.avatar,
this.balance = 0,
});

factory Member.fromJson(Map<String, dynamic> json) {
return Member(
id: json['user']['id'],
userId: json['user']['id'],
groupId: json['groupId'],
firstName: json['user']['firstName'],
lastName: json['user']['lastName'],
avatar: json['user']['avatar'],
balance: json['balance'],
);
}

get fullName => lastName.isNotEmpty ? "$firstName $lastName" : firstName;

get initials => RegExp(r'\S+').allMatches(fullName).fold('',
(acc, match) => acc + fullName.substring(match.start, match.start + 1));
}
57 changes: 52 additions & 5 deletions lib/providers/groups.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import 'package:flutter/foundation.dart';
import './base.dart';
import '../services/api.dart';

import '../models/group.dart';
import '../models/account.dart';
import '../models/member.dart';
import '../services/api.dart';

class GroupsProvider extends BaseProvider {
class GroupsProvider with ChangeNotifier {
final Api api;
final List<Group> _groups = [];
int _selectedGroupIndex = 0;
// FIXME: Figure out better way of doing this
// Not using BaseProvider class, due to notifyListeners() called after provider is disposed()
Status _status = Status.IDLE;

String _selectedGroupId;
int _lastFetchedTimestamp;

GroupsProvider({
@required this.api,
@required bool isAuthenticated,
List<Group> prev = const [],
}) {
if (isAuthenticated) {
fetchGroups();
}
_groups.addAll(prev);
}

get status => _status;

set status(Status newStatus) {
_status = newStatus;
notifyListeners();
}

List<Group> get groups {
Expand All @@ -30,6 +43,16 @@ class GroupsProvider extends BaseProvider {
return _lastFetchedTimestamp == null;
}

List<Member> get selectedGroupMembers {
return _selectedGroupIndex < _groups.length
? _groups[_selectedGroupIndex].members
: [];
}

String get selectedGroupId {
return _selectedGroupId;
}

Group get selectedGroup {
return _selectedGroupIndex < _groups.length
? _groups[_selectedGroupIndex]
Expand All @@ -40,6 +63,7 @@ class GroupsProvider extends BaseProvider {
int groupIndex = _groups.indexWhere((group) => group.id == id);
if (groupIndex != -1) {
_selectedGroupIndex = groupIndex;
_selectedGroupId = id;
notifyListeners();
}
}
Expand All @@ -53,7 +77,11 @@ class GroupsProvider extends BaseProvider {

try {
final List<Group> groups = await api.fetchGroups();
_groups.clear();
_groups.addAll(groups);
if (_groups.isNotEmpty) {
_selectedGroupId = _groups[_selectedGroupIndex].id;
}
_lastFetchedTimestamp = DateTime.now().millisecondsSinceEpoch;
status = Status.RESOLVED;
} catch (e) {
Expand All @@ -62,10 +90,21 @@ class GroupsProvider extends BaseProvider {
}
}

Future<void> createGroup({String name, String currency}) async {
Future<void> fetchGroup(String id) async {
int groupIndex = _groups.indexWhere((group) => group.id == id);
if (groupIndex != -1) {
final Group group = await api.fetchGroup(id);
_groups[groupIndex] = group;
notifyListeners();
}
}

Future<void> createGroup(
{String name, String currency, Account member}) async {
final Group group = await api.createGroup(name: name, currency: currency);
_groups.add(group);
_selectedGroupIndex = _groups.length - 1;
_selectedGroupId = group.id;
notifyListeners();
}

Expand All @@ -86,12 +125,20 @@ class GroupsProvider extends BaseProvider {
Future<void> deleteGroup(String groupId) async {
await api.deleteGroup(groupId);
_groups.removeWhere((group) => group.id == groupId);
if (_groups.isNotEmpty) {
_selectedGroupIndex = 0;
_selectedGroupId = _groups[0].id;
} else {
_selectedGroupIndex = 0;
_selectedGroupId = null;
}
notifyListeners();
}

void reset() {
_groups.clear();
_selectedGroupIndex = 0;
_selectedGroupId = null;
_lastFetchedTimestamp = null;
status = Status.IDLE;
}
Expand Down
Loading

0 comments on commit c3868ee

Please sign in to comment.