1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-22 04:49:21 +00:00

Display Forez presence

This commit is contained in:
Pierre HUBERT 2021-04-22 15:41:35 +02:00
parent 1f0abe9c2b
commit 96fb14e7de
9 changed files with 442 additions and 3 deletions

View File

@ -0,0 +1,76 @@
import 'package:comunic/helpers/websocket_helper.dart';
import 'package:comunic/lists/forez_presences_set.dart';
import 'package:comunic/models/forez_presence.dart';
/// Presence helper
///
/// @author Pierre Hubert
int _cachedGroup;
PresenceSet _cache;
class ForezPresenceHelper {
/// Refresh presence cache
///
/// Throws in case of failure
static Future<void> refreshCache(int groupID) async {
final response = await ws("forez_presence/list", {"group": groupID});
final list = response
.cast<String>()
.map((element) {
final cut = element.split(",").map((e) => int.parse(e)).toList();
assert(cut.length == 4);
return Presence(
userID: cut[0], year: cut[1], month: cut[2], day: cut[3]);
})
.toList()
.cast<Presence>();
_cachedGroup = groupID;
_cache = PresenceSet()..addAll(list);
}
/// Initialize cache if required
static Future<void> _checkCache(int groupID) async {
if (_cache == null || _cachedGroup != groupID) await refreshCache(groupID);
}
/// Get the presences of a given user
///
/// Throws in case of failure
static Future<PresenceSet> getForUser(int groupID, int userID) async {
await _checkCache(groupID);
return _cache.getForUser(userID);
}
/// Get all the available presences
///
/// Throws in case of failure
static Future<PresenceSet> getAll(int groupID) async {
await _checkCache(groupID);
return _cache;
}
/// Add a new day of presence
///
/// Throws in case of failure
static Future<void> addDay(DateTime dt) async =>
await ws("forez_presence/add_day", {
"year": dt.year,
"month": dt.month,
"day": dt.day,
});
/// Remove a new day of presence
///
/// Throws in case of failure
static Future<void> delDay(DateTime dt) async =>
await ws("forez_presence/del_day", {
"year": dt.year,
"month": dt.month,
"day": dt.day,
});
}

30
lib/lists/base_set.dart Normal file
View File

@ -0,0 +1,30 @@
import 'dart:collection';
/// Base set
///
/// @author pierre Hubert
class BaseSet<T> extends SetBase<T> {
final _set = new Set<T>();
@override
bool add(T value) => _set.add(value);
@override
bool contains(Object element) => _set.contains(element);
@override
Iterator<T> get iterator => _set.iterator;
@override
int get length => _set.length;
@override
T lookup(Object element) => _set.lookup(element);
@override
bool remove(Object value) => _set.remove(value);
@override
Set<T> toSet() => _set.toSet();
}

View File

@ -0,0 +1,51 @@
import 'package:comunic/lists/base_set.dart';
import 'package:comunic/models/forez_presence.dart';
/// Forez presence set
///
/// @author Pierre Hubert
class PresenceSet extends BaseSet<Presence> {
/// Get the presence of a specific user
PresenceSet getForUser(int userID) =>
PresenceSet()..addAll(where((element) => element.userID == userID));
bool containsDate(DateTime dt) => any(
(element) =>
element.year == dt.year &&
element.month == dt.month &&
element.day == dt.day,
);
void removeDate(DateTime dt) => removeWhere(
(element) =>
element.year == dt.year &&
element.month == dt.month &&
element.day == dt.day,
);
void toggleDate(DateTime dt, int userID) {
if (containsDate(dt))
removeDate(dt);
else
add(Presence.fromDateTime(dt, userID));
}
int countAtDate(DateTime dt) => where(
(element) =>
element.year == dt.year &&
element.month == dt.month &&
element.day == dt.day,
).length;
/// Get the list of users present at a specified date
List<int> getUsersAtDate(DateTime dt) => where(
(element) =>
element.year == dt.year &&
element.month == dt.month &&
element.day == dt.day,
).map((e) => e.userID).toList();
/// Get the ID of all the users referenced in this set
Set<int> get usersID => map((element) => element.userID).toSet();
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
/// Single presence information
///
/// @author Pierre Hubert
class Presence {
final int userID;
final int year;
final int month;
final int day;
const Presence({
@required this.userID,
@required this.year,
@required this.month,
@required this.day,
}) : assert(userID != null),
assert(year != null),
assert(month != null),
assert(day != null);
Presence.fromDateTime(DateTime dt, this.userID)
: assert(dt != null),
year = dt.year,
month = dt.month,
day = dt.day,
assert(userID != null);
}

View File

@ -60,7 +60,7 @@ class _AuthorizedGroupPageScreenState
// Forez presence tab
_GroupPageTab(
widget: (c) => ForezPresenceSection(),
widget: (c) => ForezPresenceSection(groupID: _group.id),
label: tr("Presence"),
visible: _group.isForezGroup,
),

View File

@ -1,3 +1,10 @@
import 'package:comunic/helpers/forez_presence_helper.dart';
import 'package:comunic/helpers/users_helper.dart';
import 'package:comunic/lists/forez_presences_set.dart';
import 'package:comunic/lists/users_list.dart';
import 'package:comunic/ui/widgets/account_image_widget.dart';
import 'package:comunic/ui/widgets/async_screen_widget.dart';
import 'package:comunic/ui/widgets/forez_presence_calendar_widget.dart';
import 'package:flutter/material.dart';
/// Forez presence section
@ -5,13 +12,77 @@ import 'package:flutter/material.dart';
/// @author Pierre Hubert
class ForezPresenceSection extends StatefulWidget {
final int groupID;
const ForezPresenceSection({
Key key,
@required this.groupID,
}) : assert(groupID != null),
super(key: key);
@override
_ForezPresenceSectionState createState() => _ForezPresenceSectionState();
}
class _ForezPresenceSectionState extends State<ForezPresenceSection> {
PresenceSet _presences;
UsersList _users;
DateTime _currentDay = DateTime.now();
List<int> get _currentListOfUsers => _presences.getUsersAtDate(_currentDay);
Future<void> _refresh() async {
await ForezPresenceHelper.refreshCache(widget.groupID);
_presences = await ForezPresenceHelper.getAll(widget.groupID);
_users = await UsersHelper().getList(_presences.usersID);
}
@override
Widget build(BuildContext context) {
return Container();
return Stack(
children: <Widget>[
AsyncScreenWidget(
onReload: _refresh,
onBuild: _buildList,
errorMessage: "Erreur lors du chargement des présences !",
),
Positioned(
right: 10,
bottom: 10,
child: FloatingActionButton(
backgroundColor: Colors.green,
onPressed: () {},
child: Icon(Icons.edit),
),
)
],
);
}
Widget _buildList() {
final currentList = _currentListOfUsers;
return ListView.builder(
itemCount: currentList.length + 1,
itemBuilder: (c, i) =>
i == 0 ? _buildCalendar() : _buildUser(currentList[i - 1]),
);
}
Widget _buildCalendar() => PresenceCalendarWidget(
presenceSet: _presences,
mode: CalendarDisplayMode.MULTIPLE_USERS,
selectedDay: _currentDay,
onDayClicked: _selectDay,
);
void _selectDay(DateTime dt) => setState(() => _currentDay = dt);
Widget _buildUser(int userID) {
final user = _users.getUser(userID);
return ListTile(
leading: AccountImageWidget(user: user),
title: Text(user.fullName),
);
}
}

View File

@ -0,0 +1,165 @@
import 'package:comunic/lists/forez_presences_set.dart';
import 'package:comunic/utils/date_utils.dart' as date_utils;
/// Forez presence calendar widget
///
/// This widget is used only by Forez groups
///
/// @author Pierre Hubert
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
enum CalendarDisplayMode { SINGLE_USER, MULTIPLE_USERS }
extension DateOnlyCompare on DateTime {
bool isSameDate(DateTime other) => date_utils.isSameDate(this, other);
}
class PresenceCalendarWidget extends StatefulWidget {
final PresenceSet presenceSet;
final void Function(DateTime) onDayClicked;
final CalendarDisplayMode mode;
final DateTime selectedDay;
const PresenceCalendarWidget({
Key key,
@required this.presenceSet,
this.onDayClicked,
this.mode = CalendarDisplayMode.SINGLE_USER,
this.selectedDay,
}) : assert(presenceSet != null),
assert(mode != null),
super(key: key);
@override
_PresenceCalendarWidgetState createState() => _PresenceCalendarWidgetState();
}
class _PresenceCalendarWidgetState extends State<PresenceCalendarWidget> {
CalendarController _calendarController;
@override
void initState() {
super.initState();
_calendarController = CalendarController();
}
@override
void dispose() {
_calendarController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TableCalendar(
calendarController: _calendarController,
locale: "fr_FR",
weekendDays: [],
onHeaderTapped: _pickDate,
builders: CalendarBuilders(dayBuilder: _dayBuilder),
onDaySelected: _selectedDay,
availableCalendarFormats: const {CalendarFormat.month: "Mois"},
);
}
void _pickDate(DateTime date) async {
final pickedDate = await showDatePicker(
context: context,
initialDate: date,
firstDate: DateTime.now().subtract(Duration(days: 20)),
lastDate: DateTime.now().add(Duration(days: 365 * 5)),
);
if (pickedDate != null) {
_calendarController.setSelectedDay(pickedDate, animate: true);
setState(() {});
}
}
Widget _dayBuilder(
BuildContext context, DateTime date, List<dynamic> events) {
if (widget.presenceSet.containsDate(date)) {
// Show the number of users who are present
if (widget.mode == CalendarDisplayMode.MULTIPLE_USERS)
return Stack(
children: [
CellWidget(
text: date.day.toString(),
color: Colors.green,
textColor: Colors.white,
circle: false,
selected: date.isSameDate(widget.selectedDay),
),
Positioned(
child: Material(
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Text(widget.presenceSet.countAtDate(date).toString()),
),
textStyle: TextStyle(color: Colors.white),
color: Colors.red,
),
bottom: 4,
right: 4,
),
],
);
// Only show green circle
else
return CellWidget(
text: date.day.toString(),
color: Colors.green,
textColor: Colors.white,
);
}
return CellWidget(
text: date.day.toString(),
selected: date.isSameDate(widget.selectedDay),
);
}
void _selectedDay(
DateTime day, List<dynamic> events, List<dynamic> holidays) {
if (widget.onDayClicked != null) widget.onDayClicked(day);
}
}
class CellWidget extends StatelessWidget {
final String text;
final Color color;
final Color textColor;
final bool circle;
final bool selected;
const CellWidget({
Key key,
@required this.text,
this.color,
this.textColor,
this.circle = true,
this.selected,
}) : assert(text != null),
super(key: key);
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 250),
decoration: _buildCellDecoration(),
margin: const EdgeInsets.all(6.0),
alignment: Alignment.center,
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(color: selected ?? false ? Colors.white : textColor),
),
);
}
Decoration _buildCellDecoration() => BoxDecoration(
shape: circle ? BoxShape.circle : BoxShape.rectangle,
color: selected ?? false ? Colors.deepPurple : color,
);
}

View File

@ -555,6 +555,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2+7"
simple_gesture_detector:
dependency: transitive
description:
name: simple_gesture_detector
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.6"
sky_engine:
dependency: transitive
description: flutter
@ -609,6 +616,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
table_calendar:
dependency: "direct main"
description:
name: table_calendar
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.3"
term_glyph:
dependency: transitive
description:

View File

@ -14,7 +14,7 @@ description: Comunic client
version: 1.1.5+9
environment:
sdk: ">=2.1.0 <3.0.0"
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
@ -130,6 +130,9 @@ dependencies:
firebase_core: ^1.0.1
firebase_messaging: ^9.0.0
# Forez presence
table_calendar: ^2.3.3
dev_dependencies:
flutter_test:
sdk: flutter