Skip to content

Commit

Permalink
Added the ability to add a user to a group when subscribing.
Browse files Browse the repository at this point in the history
This extends the subscribe button (currently used in the search and
profile screens) to allow selecting one or more groups to add the
user to.

It also includes a new library (multi_select_flutter) which can be
used in other places where we have a list of checkboxes.

Fixes #114.
  • Loading branch information
jonjomckay committed Jun 10, 2021
1 parent edceabc commit 0462aa5
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 16 deletions.
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/changelogs/next.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Added the ability to add a user to a group when subscribing
- Improved the layout of the video player, making it possible to seek on narrower screens
- Fixed poll colors hiding text in light mode
- Fixed tweets not opening when a profile, search, or tweet screen is already open
19 changes: 19 additions & 0 deletions lib/home_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,25 @@ class HomeModel extends ChangeNotifier {
.toList(growable: false);
}

Future saveUserGroupMembership(int user, List<String> memberships) async {
var database = await Repository.writable();

var batch = database.batch();

// First, clear all the memberships for the user
batch.delete(TABLE_SUBSCRIPTION_GROUP_MEMBER, where: 'profile_id = ?', whereArgs: [user]);

// Then add all the new memberships
for (var group in memberships) {
batch.insert(TABLE_SUBSCRIPTION_GROUP_MEMBER, {
'group_id': group,
'profile_id': user
});
}

await batch.commit();
}

Future<SubscriptionGroupEdit> loadSubscriptionGroupEdit(String? id) async {
var database = await Repository.readOnly();

Expand Down
114 changes: 98 additions & 16 deletions lib/user.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:fritter/database/entities.dart';
import 'package:fritter/home_model.dart';
import 'package:fritter/ui/errors.dart';
import 'package:fritter/ui/futures.dart';
import 'package:multi_select_flutter/multi_select_flutter.dart';
import 'package:sqflite/sqflite.dart';

import 'database/repository.dart';
Expand Down Expand Up @@ -55,6 +60,8 @@ class UserTile extends StatelessWidget {

}

// TODO: This is a very stateful widget. It can probably be extracted out into a
// model so this widget can become a little simpler.
class FollowButton extends StatefulWidget {
final String id;
final String name;
Expand Down Expand Up @@ -98,6 +105,36 @@ class _FollowButtonState extends State<FollowButton> {
return result.first.values.first == 1;
}

Future<List<SubscriptionGroup>> listGroups() async {
return await HomeModel().listSubscriptionGroups(orderBy: 'name', orderByAscending: true);
}

Future<List<String>> listGroupsForUser(int id) async {
Database database = await Repository.readOnly();

return (await database.query(TABLE_SUBSCRIPTION_GROUP_MEMBER, columns: ['group_id'], where: 'profile_id = ?', whereArgs: [id]))
.map((e) => e['group_id'] as String)
.toList(growable: false);
}

Future toggleSubscribe(int id, bool currentlyFollowed) async {
Database database = await Repository.writable();

if (currentlyFollowed) {
await database.delete(TABLE_SUBSCRIPTION, where: 'id = ?', whereArgs: [id]);
await database.delete(TABLE_SUBSCRIPTION_GROUP_MEMBER, where: 'profile_id = ?', whereArgs: [id]);
} else {
await database.insert(TABLE_SUBSCRIPTION, {
'id': id,
'screen_name': widget.screenName,
'name': widget.name,
'profile_image_url_https': widget.imageUri
});
}

await fetchFollowed();
}

@override
Widget build(BuildContext context) {
var id = int.parse(widget.id);
Expand All @@ -111,21 +148,66 @@ class _FollowButtonState extends State<FollowButton> {
? Icon(Icons.person_remove)
: Icon(Icons.person_add);

return IconButton(icon: icon, onPressed: () async {
Database database = await Repository.writable();

if (followed) {
await database.delete(TABLE_SUBSCRIPTION, where: 'id = ?', whereArgs: [id]);
} else {
await database.insert(TABLE_SUBSCRIPTION, {
'id': id,
'screen_name': widget.screenName,
'name': widget.name,
'profile_image_url_https': widget.imageUri
});
}

await fetchFollowed();
});
var text = followed
? 'Unsubscribe'
: 'Subscribe';

return PopupMenuButton<String>(
icon: icon,
itemBuilder: (context) => [
PopupMenuItem(child: Text(text), value: 'toggle_subscribe'),
PopupMenuItem(child: Text('Add to group'), value: 'add_to_group'),
],
onSelected: (value) async {
switch (value) {
case 'add_to_group':
showDialog(context: context, builder: (context) {
var futures = [
listGroups(),
listGroupsForUser(id)
];

// TODO: Add types
return FutureBuilderWrapper<List<dynamic>>(
future: Future.wait(futures),
onError: (error, stackTrace) => FullPageErrorWidget(
error: error,
stackTrace: stackTrace,
prefix: 'Unable to load subscription groups',
),
onReady: (data) {
var groups = data[0] as List<SubscriptionGroup>;
var existing = data[1] as List<String>;

return MultiSelectDialog(
itemsTextStyle: Theme.of(context).primaryTextTheme.bodyText1,
selectedColor: Theme.of(context).accentColor,
unselectedColor: Theme.of(context).brightness == Brightness.dark
? Colors.white70
: Colors.black54,
selectedItemsTextStyle: Theme.of(context).primaryTextTheme.bodyText1,
items: groups.map((e) => MultiSelectItem(e.id, e.name)).toList(),
initialValue: existing,
onConfirm: (List<String> memberships) async {
// If we're not currently following the user, follow them first
if (followed == false) {
await toggleSubscribe(id, followed);
}

// Then add them to all the selected groups
await HomeModel()
.saveUserGroupMembership(id, memberships);
},
);
},
);
});
break;
case 'toggle_subscribe':
await toggleSubscribe(id, followed);
break;
}
},
);
}
}
7 changes: 7 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
multi_select_flutter:
dependency: "direct main"
description:
name: multi_select_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
nested:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies:
http: ^0.13.1
infinite_scroll_pagination: ^3.0.1
intl: ^0.17.0
multi_select_flutter: ^4.0.0
package_info: ^2.0.0
pref: ^2.0.2
quiver: ^3.0.1
Expand Down

0 comments on commit 0462aa5

Please sign in to comment.