mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-12-26 12:58:51 +00:00
Display Forez presence
This commit is contained in:
parent
1f0abe9c2b
commit
96fb14e7de
76
lib/helpers/forez_presence_helper.dart
Normal file
76
lib/helpers/forez_presence_helper.dart
Normal 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
30
lib/lists/base_set.dart
Normal 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();
|
||||
}
|
51
lib/lists/forez_presences_set.dart
Normal file
51
lib/lists/forez_presences_set.dart
Normal 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();
|
||||
}
|
29
lib/models/forez_presence.dart
Normal file
29
lib/models/forez_presence.dart
Normal 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);
|
||||
}
|
@ -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,
|
||||
),
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
165
lib/ui/widgets/forez_presence_calendar_widget.dart
Normal file
165
lib/ui/widgets/forez_presence_calendar_widget.dart
Normal 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,
|
||||
);
|
||||
}
|
14
pubspec.lock
14
pubspec.lock
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user