mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-22 04:49:21 +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
|
// Forez presence tab
|
||||||
_GroupPageTab(
|
_GroupPageTab(
|
||||||
widget: (c) => ForezPresenceSection(),
|
widget: (c) => ForezPresenceSection(groupID: _group.id),
|
||||||
label: tr("Presence"),
|
label: tr("Presence"),
|
||||||
visible: _group.isForezGroup,
|
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';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// Forez presence section
|
/// Forez presence section
|
||||||
@ -5,13 +12,77 @@ import 'package:flutter/material.dart';
|
|||||||
/// @author Pierre Hubert
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
class ForezPresenceSection extends StatefulWidget {
|
class ForezPresenceSection extends StatefulWidget {
|
||||||
|
final int groupID;
|
||||||
|
|
||||||
|
const ForezPresenceSection({
|
||||||
|
Key key,
|
||||||
|
@required this.groupID,
|
||||||
|
}) : assert(groupID != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ForezPresenceSectionState createState() => _ForezPresenceSectionState();
|
_ForezPresenceSectionState createState() => _ForezPresenceSectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ForezPresenceSectionState extends State<ForezPresenceSection> {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2+7"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -609,6 +616,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
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:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -14,7 +14,7 @@ description: Comunic client
|
|||||||
version: 1.1.5+9
|
version: 1.1.5+9
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.1.0 <3.0.0"
|
sdk: ">=2.7.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@ -130,6 +130,9 @@ dependencies:
|
|||||||
firebase_core: ^1.0.1
|
firebase_core: ^1.0.1
|
||||||
firebase_messaging: ^9.0.0
|
firebase_messaging: ^9.0.0
|
||||||
|
|
||||||
|
# Forez presence
|
||||||
|
table_calendar: ^2.3.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
Loading…
Reference in New Issue
Block a user