mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-11-04 12:14:11 +00:00 
			
		
		
		
	Display Forez presence
This commit is contained in:
		
							
								
								
									
										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,
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user