mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-11-03 19:54:12 +00:00 
			
		
		
		
	Start to fix null safety migration errors
This commit is contained in:
		@@ -8,16 +8,16 @@ import 'package:comunic/models/advanced_group_info.dart';
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
AdvancedGroupInfo _forezGroup;
 | 
			
		||||
AdvancedGroupInfo? _forezGroup;
 | 
			
		||||
 | 
			
		||||
class ForezGroupHelper {
 | 
			
		||||
  static Future<void> setId(int groupID) async {
 | 
			
		||||
    (await PreferencesHelper.getInstance())
 | 
			
		||||
    (await PreferencesHelper.getInstance())!
 | 
			
		||||
        .setInt(PreferencesKeyList.FOREZ_GROUP, groupID);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Future<int> getId() async {
 | 
			
		||||
    return (await PreferencesHelper.getInstance())
 | 
			
		||||
  static Future<int?> getId() async {
 | 
			
		||||
    return (await PreferencesHelper.getInstance())!
 | 
			
		||||
        .getInt(PreferencesKeyList.FOREZ_GROUP);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -27,7 +27,7 @@ class ForezGroupHelper {
 | 
			
		||||
    _forezGroup = res.info;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static AdvancedGroupInfo getGroup() => _forezGroup;
 | 
			
		||||
  static AdvancedGroupInfo? getGroup() => _forezGroup;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AdvancedGroupInfo get forezGroup => ForezGroupHelper.getGroup();
 | 
			
		||||
AdvancedGroupInfo? get forezGroup => ForezGroupHelper.getGroup();
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@ import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class ForezConfig extends Config {
 | 
			
		||||
  ForezConfig({
 | 
			
		||||
    @required String apiServerName,
 | 
			
		||||
    @required String apiServerUri,
 | 
			
		||||
    @required bool apiServerSecure,
 | 
			
		||||
    @required String clientName,
 | 
			
		||||
    required String apiServerName,
 | 
			
		||||
    required String apiServerUri,
 | 
			
		||||
    required bool apiServerSecure,
 | 
			
		||||
    required String clientName,
 | 
			
		||||
  }) : super(
 | 
			
		||||
          apiServerName: apiServerName,
 | 
			
		||||
          apiServerUri: apiServerUri,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import 'package:comunic/models/config.dart';
 | 
			
		||||
/// Fix HTTPS issue
 | 
			
		||||
class MyHttpOverride extends HttpOverrides {
 | 
			
		||||
  @override
 | 
			
		||||
  HttpClient createHttpClient(SecurityContext context) {
 | 
			
		||||
  HttpClient createHttpClient(SecurityContext? context) {
 | 
			
		||||
    return super.createHttpClient(context)
 | 
			
		||||
      ..badCertificateCallback = (cert, host, port) {
 | 
			
		||||
        return host == "devweb.local"; // Forcefully trust local website
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ List<Widget> buildTour(TourRouteState state) {
 | 
			
		||||
      msgTwo: tr("Let's first join a Forez group!"),
 | 
			
		||||
    ),
 | 
			
		||||
    JoinForezGroupPane(
 | 
			
		||||
      key: state.pubKeys[_JOIN_GROUP_KEY_ID],
 | 
			
		||||
      key: state.pubKeys[_JOIN_GROUP_KEY_ID] as GlobalKey<JoinGroupPaneBodyState>?,
 | 
			
		||||
      onUpdated: () => state.rebuild(),
 | 
			
		||||
    ),
 | 
			
		||||
    FirstTourPane(
 | 
			
		||||
@@ -51,7 +51,7 @@ List<Widget> buildTour(TourRouteState state) {
 | 
			
		||||
    // Forez specific features
 | 
			
		||||
    PresentationPane(
 | 
			
		||||
      icon: Icons.calendar_today,
 | 
			
		||||
      title: tr("Presence in Forez"),
 | 
			
		||||
      title: tr("Presence in Forez")!,
 | 
			
		||||
      text: tr(
 | 
			
		||||
          "Easily specify the days you are in Forez plain, so that everyone can know it!"),
 | 
			
		||||
      actionTitle: tr("Do it now!"),
 | 
			
		||||
@@ -62,7 +62,7 @@ List<Widget> buildTour(TourRouteState state) {
 | 
			
		||||
    // Chat pane
 | 
			
		||||
    PresentationPane(
 | 
			
		||||
      icon: Icons.question_answer,
 | 
			
		||||
      title: tr("Conversations"),
 | 
			
		||||
      title: tr("Conversations")!,
 | 
			
		||||
      text: tr(
 | 
			
		||||
          "#Forez now integrates the conversation system of Comunic, so you have access both to public and private conversations!"),
 | 
			
		||||
    ),
 | 
			
		||||
 
 | 
			
		||||
@@ -16,17 +16,17 @@ import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class JoinForezGroupPane extends PresentationPane {
 | 
			
		||||
  JoinForezGroupPane({
 | 
			
		||||
    @required Function() onUpdated,
 | 
			
		||||
    @required GlobalKey<JoinGroupPaneBodyState> key,
 | 
			
		||||
    required Function() onUpdated,
 | 
			
		||||
    required GlobalKey<JoinGroupPaneBodyState>? key,
 | 
			
		||||
  }) : super(
 | 
			
		||||
          icon: Icons.login,
 | 
			
		||||
          title: tr("Join a Forez group"),
 | 
			
		||||
          title: tr("Join a Forez group")!,
 | 
			
		||||
          child: (c) => _JoinGroupPaneBody(
 | 
			
		||||
            key: key,
 | 
			
		||||
            onUpdated: onUpdated,
 | 
			
		||||
          ),
 | 
			
		||||
          canGoNext: key?.currentState?.canGoNext ?? false,
 | 
			
		||||
          onTapNext: (c) => key.currentState.validateChoice(),
 | 
			
		||||
          onTapNext: (c) => key!.currentState!.validateChoice(),
 | 
			
		||||
        );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -34,8 +34,8 @@ class _JoinGroupPaneBody extends StatefulWidget {
 | 
			
		||||
  final Function() onUpdated;
 | 
			
		||||
 | 
			
		||||
  const _JoinGroupPaneBody({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.onUpdated,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.onUpdated,
 | 
			
		||||
  })  : assert(onUpdated != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -46,10 +46,10 @@ class _JoinGroupPaneBody extends StatefulWidget {
 | 
			
		||||
class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> {
 | 
			
		||||
  final _key = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
 | 
			
		||||
  List<Group> _groups;
 | 
			
		||||
  int _currChoice;
 | 
			
		||||
  late List<Group> _groups;
 | 
			
		||||
  int? _currChoice;
 | 
			
		||||
 | 
			
		||||
  bool get canGoNext => _currChoice != null && _currChoice > 0;
 | 
			
		||||
  bool get canGoNext => _currChoice != null && _currChoice! > 0;
 | 
			
		||||
 | 
			
		||||
  Group get _currGroup => _groups.firstWhere((e) => e.id == _currChoice);
 | 
			
		||||
 | 
			
		||||
@@ -65,17 +65,17 @@ class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> {
 | 
			
		||||
      key: _key,
 | 
			
		||||
      onReload: _load,
 | 
			
		||||
      onBuild: onBuild,
 | 
			
		||||
      errorMessage: tr("Failed to load the list of Forez groups!"));
 | 
			
		||||
      errorMessage: tr("Failed to load the list of Forez groups!")!);
 | 
			
		||||
 | 
			
		||||
  Widget onBuild() => ConstrainedBox(
 | 
			
		||||
        constraints: BoxConstraints(maxWidth: 300),
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            Text(tr("Please choose now the Forez group you want to join...")),
 | 
			
		||||
            Text(tr("Please choose now the Forez group you want to join...")!),
 | 
			
		||||
          ]..addAll(_groups.map((e) => RadioListTile(
 | 
			
		||||
                value: e.id,
 | 
			
		||||
                groupValue: _currChoice,
 | 
			
		||||
                onChanged: (v) => setState(() => _currChoice = e.id),
 | 
			
		||||
                onChanged: (dynamic v) => setState(() => _currChoice = e.id),
 | 
			
		||||
                title: Text(e.name),
 | 
			
		||||
                subtitle: Text(e.membershipText),
 | 
			
		||||
              ))),
 | 
			
		||||
@@ -112,7 +112,7 @@ class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> {
 | 
			
		||||
        await alert(context,
 | 
			
		||||
            "${tr("You can not access this group yet, please wait for a member of the group to accept your request.")}\n${tr("Hopefully this will not be too long.")}\n${tr("Please check back soon!")}");
 | 
			
		||||
 | 
			
		||||
        _key.currentState.refresh();
 | 
			
		||||
        _key.currentState!.refresh();
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
@@ -121,8 +121,8 @@ class JoinGroupPaneBodyState extends State<_JoinGroupPaneBody> {
 | 
			
		||||
      return true;
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to register to group!"));
 | 
			
		||||
      _key.currentState.refresh();
 | 
			
		||||
      snack(context, tr("Failed to register to group!")!);
 | 
			
		||||
      _key.currentState!.refresh();
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@ class ForezMemberProfileRoute extends StatefulWidget {
 | 
			
		||||
  final int userID;
 | 
			
		||||
 | 
			
		||||
  const ForezMemberProfileRoute({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
  })  : assert(userID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -39,13 +39,13 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
 | 
			
		||||
 | 
			
		||||
  final _key = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
 | 
			
		||||
  AdvancedUserInfo _user;
 | 
			
		||||
  PresenceSet _presence;
 | 
			
		||||
  late AdvancedUserInfo _user;
 | 
			
		||||
  late PresenceSet _presence;
 | 
			
		||||
 | 
			
		||||
  Future<void> _load() async {
 | 
			
		||||
    _user = await ForezGroupsHelper.getMemberInfo(forezGroup.id, widget.userID);
 | 
			
		||||
    _user = await ForezGroupsHelper.getMemberInfo(forezGroup!.id, widget.userID);
 | 
			
		||||
    _presence =
 | 
			
		||||
        await ForezPresenceHelper.getForUser(forezGroup.id, widget.userID);
 | 
			
		||||
        await ForezPresenceHelper.getForUser(forezGroup!.id, widget.userID);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -56,25 +56,25 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
 | 
			
		||||
      loadingWidget: _buildLoading(),
 | 
			
		||||
      errorWidget: _buildError(),
 | 
			
		||||
      errorMessage: tr(
 | 
			
		||||
          "Failed to load user information, maybe it is not a Forez member yet?"));
 | 
			
		||||
          "Failed to load user information, maybe it is not a Forez member yet?")!);
 | 
			
		||||
 | 
			
		||||
  Widget _buildLoading() => Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text(tr("Loading...")),
 | 
			
		||||
          title: Text(tr("Loading...")!),
 | 
			
		||||
        ),
 | 
			
		||||
        body: buildCenteredProgressBar(),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  Widget _buildError() => Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text(tr("Error")),
 | 
			
		||||
          title: Text(tr("Error")!),
 | 
			
		||||
        ),
 | 
			
		||||
        body: buildErrorCard(
 | 
			
		||||
            tr("Failed to load user information, maybe it is not a Forez member yet?"),
 | 
			
		||||
            actions: [
 | 
			
		||||
              MaterialButton(
 | 
			
		||||
                onPressed: () => _key.currentState.refresh(),
 | 
			
		||||
                child: Text(tr("Try again")),
 | 
			
		||||
                onPressed: () => _key.currentState!.refresh(),
 | 
			
		||||
                child: Text(tr("Try again")!),
 | 
			
		||||
                textColor: Colors.white,
 | 
			
		||||
              )
 | 
			
		||||
            ]),
 | 
			
		||||
@@ -101,7 +101,7 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
 | 
			
		||||
                  ? Container()
 | 
			
		||||
                  : CachedNetworkImage(
 | 
			
		||||
                      fit: BoxFit.cover,
 | 
			
		||||
                      imageUrl: _user.accountImageURL,
 | 
			
		||||
                      imageUrl: _user.accountImageURL!,
 | 
			
		||||
                      height: _appBarHeight,
 | 
			
		||||
                    ),
 | 
			
		||||
              // This gradient ensures that the toolbar icons are distinct
 | 
			
		||||
@@ -135,7 +135,7 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
 | 
			
		||||
            : ListTile(
 | 
			
		||||
                leading: Icon(Icons.note),
 | 
			
		||||
                title: TextWidget(content: DisplayedString(_user.publicNote)),
 | 
			
		||||
                subtitle: Text(tr("Note")),
 | 
			
		||||
                subtitle: Text(tr("Note")!),
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
        // Email address
 | 
			
		||||
@@ -143,9 +143,9 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
 | 
			
		||||
            ? Container()
 | 
			
		||||
            : ListTile(
 | 
			
		||||
                leading: Icon(Icons.email),
 | 
			
		||||
                title: Text(_user.emailAddress),
 | 
			
		||||
                subtitle: Text(tr("Email address")),
 | 
			
		||||
                trailing: CopyIcon(_user.emailAddress),
 | 
			
		||||
                title: Text(_user.emailAddress!),
 | 
			
		||||
                subtitle: Text(tr("Email address")!),
 | 
			
		||||
                trailing: CopyIcon(_user.emailAddress!),
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
        // Location
 | 
			
		||||
@@ -153,9 +153,9 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
 | 
			
		||||
            ? Container()
 | 
			
		||||
            : ListTile(
 | 
			
		||||
                leading: Icon(Icons.location_on),
 | 
			
		||||
                title: Text(_user.location),
 | 
			
		||||
                subtitle: Text(tr("Location")),
 | 
			
		||||
                trailing: CopyIcon(_user.location),
 | 
			
		||||
                title: Text(_user.location!),
 | 
			
		||||
                subtitle: Text(tr("Location")!),
 | 
			
		||||
                trailing: CopyIcon(_user.location!),
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
        // Website
 | 
			
		||||
@@ -164,7 +164,7 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
 | 
			
		||||
            : ListTile(
 | 
			
		||||
                leading: Icon(Icons.link),
 | 
			
		||||
                title: Text(_user.personalWebsite),
 | 
			
		||||
                subtitle: Text(tr("Website")),
 | 
			
		||||
                subtitle: Text(tr("Website")!),
 | 
			
		||||
                trailing: IconButton(
 | 
			
		||||
                  icon: Icon(Icons.open_in_new),
 | 
			
		||||
                  onPressed: () => launch(_user.personalWebsite),
 | 
			
		||||
@@ -174,10 +174,10 @@ class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
 | 
			
		||||
        Divider(),
 | 
			
		||||
        ListTile(
 | 
			
		||||
          leading: Icon(Icons.calendar_today),
 | 
			
		||||
          title: Text(tr("Presence in Forez")),
 | 
			
		||||
          title: Text(tr("Presence in Forez")!),
 | 
			
		||||
          subtitle: Text(_presence.containsDate(DateTime.now())
 | 
			
		||||
              ? tr("Present today")
 | 
			
		||||
              : tr("Absent")),
 | 
			
		||||
              ? tr("Present today")!
 | 
			
		||||
              : tr("Absent")!),
 | 
			
		||||
        ),
 | 
			
		||||
        PresenceCalendarWidget(presenceSet: _presence),
 | 
			
		||||
        Divider(),
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import 'package:flutter/material.dart';
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class ForezRoute extends StatefulWidget implements MainRoute {
 | 
			
		||||
  const ForezRoute({Key key}) : super(key: key);
 | 
			
		||||
  const ForezRoute({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<StatefulWidget> createState() => _MainRouteState();
 | 
			
		||||
@@ -40,7 +40,7 @@ class _MainRouteState extends MainController {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (forezGroup == null) return Text(tr("Missing Forez group!"));
 | 
			
		||||
    if (forezGroup == null) return Text(tr("Missing Forez group!")!);
 | 
			
		||||
 | 
			
		||||
    return StatusWidget(
 | 
			
		||||
      child: (c) => SafeArea(
 | 
			
		||||
@@ -67,11 +67,11 @@ class _MainRouteState extends MainController {
 | 
			
		||||
  @override
 | 
			
		||||
  void openConversation(Conversation conv, {fullScreen: false}) {
 | 
			
		||||
    // Forcefully open conversations in a "normal" way (do not display groups)
 | 
			
		||||
    openConversationById(conv.id, fullScreen: fullScreen);
 | 
			
		||||
    openConversationById(conv.id!, fullScreen: fullScreen);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void openGroup(int groupID, {int conversationID}) => _unsupportedFeature();
 | 
			
		||||
  void openGroup(int groupID, {int? conversationID}) => _unsupportedFeature();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void openUserPage(int userID) => pushPage(PageInfo(
 | 
			
		||||
@@ -92,7 +92,7 @@ class _MainRouteState extends MainController {
 | 
			
		||||
enum _PopupMenuItems { ACTION_SETTINGS, ACTION_SIGN_OUT }
 | 
			
		||||
 | 
			
		||||
class ForezRouteBody extends StatefulWidget {
 | 
			
		||||
  ForezRouteBody({Key key}) : super(key: key);
 | 
			
		||||
  ForezRouteBody({Key? key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _ForezRouteBodyState createState() => _ForezRouteBodyState();
 | 
			
		||||
@@ -112,7 +112,7 @@ class _ForezRouteBodyState extends SafeState<ForezRouteBody> {
 | 
			
		||||
      length: _tabs.length,
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text(forezGroup.name),
 | 
			
		||||
          title: Text(forezGroup!.name),
 | 
			
		||||
          actions: <Widget>[_buildPopupMenuButton()],
 | 
			
		||||
          bottom: TabBar(tabs: _tabs),
 | 
			
		||||
        ),
 | 
			
		||||
@@ -129,11 +129,11 @@ class _ForezRouteBodyState extends SafeState<ForezRouteBody> {
 | 
			
		||||
  Widget _buildPopupMenuButton() => PopupMenuButton<_PopupMenuItems>(
 | 
			
		||||
        itemBuilder: (c) => [
 | 
			
		||||
          PopupMenuItem(
 | 
			
		||||
            child: Text(tr("Settings")),
 | 
			
		||||
            child: Text(tr("Settings")!),
 | 
			
		||||
            value: _PopupMenuItems.ACTION_SETTINGS,
 | 
			
		||||
          ),
 | 
			
		||||
          PopupMenuItem(
 | 
			
		||||
            child: Text(tr("Sign out")),
 | 
			
		||||
            child: Text(tr("Sign out")!),
 | 
			
		||||
            value: _PopupMenuItems.ACTION_SIGN_OUT,
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
@@ -143,10 +143,10 @@ class _ForezRouteBodyState extends SafeState<ForezRouteBody> {
 | 
			
		||||
  void _onMenuSelection(_PopupMenuItems value) {
 | 
			
		||||
    switch (value) {
 | 
			
		||||
      case _PopupMenuItems.ACTION_SETTINGS:
 | 
			
		||||
        MainController.of(context).openSettings();
 | 
			
		||||
        MainController.of(context)!.openSettings();
 | 
			
		||||
        break;
 | 
			
		||||
      case _PopupMenuItems.ACTION_SIGN_OUT:
 | 
			
		||||
        MainController.of(context).requestLogout();
 | 
			
		||||
        MainController.of(context)!.requestLogout();
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -155,29 +155,29 @@ class _ForezRouteBodyState extends SafeState<ForezRouteBody> {
 | 
			
		||||
    // Posts tab
 | 
			
		||||
    _Tab(
 | 
			
		||||
      icon: Icons.auto_stories,
 | 
			
		||||
      title: tr("Posts"),
 | 
			
		||||
      widget: () => GroupPostsSection(group: forezGroup),
 | 
			
		||||
      title: tr("Posts")!,
 | 
			
		||||
      widget: () => GroupPostsSection(group: forezGroup!),
 | 
			
		||||
    ),
 | 
			
		||||
 | 
			
		||||
    // Presence tab
 | 
			
		||||
    _Tab(
 | 
			
		||||
      icon: Icons.calendar_today,
 | 
			
		||||
      title: tr("Presence"),
 | 
			
		||||
      widget: () => ForezPresenceSection(groupID: forezGroup.id),
 | 
			
		||||
      title: tr("Presence")!,
 | 
			
		||||
      widget: () => ForezPresenceSection(groupID: forezGroup!.id),
 | 
			
		||||
    ),
 | 
			
		||||
 | 
			
		||||
    // Conversations tab
 | 
			
		||||
    _Tab(
 | 
			
		||||
      icon: Icons.question_answer,
 | 
			
		||||
      title: tr("Conversations"),
 | 
			
		||||
      title: tr("Conversations")!,
 | 
			
		||||
      widget: () => ConversationsListScreen(),
 | 
			
		||||
      isUnread: (c) => StatusWidgetState.of(c).unreadConversations > 0,
 | 
			
		||||
      isUnread: (c) => StatusWidgetState.of(c)!.unreadConversations! > 0,
 | 
			
		||||
    ),
 | 
			
		||||
 | 
			
		||||
    // Directory tab
 | 
			
		||||
    _Tab(
 | 
			
		||||
      icon: Icons.import_contacts,
 | 
			
		||||
      title: tr("Directory"),
 | 
			
		||||
      title: tr("Directory")!,
 | 
			
		||||
      widget: () => ForezDirectoryScreen(),
 | 
			
		||||
    ),
 | 
			
		||||
  ];
 | 
			
		||||
@@ -203,12 +203,12 @@ class _Tab {
 | 
			
		||||
  final IconData icon;
 | 
			
		||||
  final String title;
 | 
			
		||||
  final Widget Function() widget;
 | 
			
		||||
  final bool Function(BuildContext) isUnread;
 | 
			
		||||
  final bool Function(BuildContext)? isUnread;
 | 
			
		||||
 | 
			
		||||
  const _Tab({
 | 
			
		||||
    @required this.icon,
 | 
			
		||||
    @required this.title,
 | 
			
		||||
    @required this.widget,
 | 
			
		||||
    required this.icon,
 | 
			
		||||
    required this.title,
 | 
			
		||||
    required this.widget,
 | 
			
		||||
    this.isUnread,
 | 
			
		||||
  })  : assert(icon != null),
 | 
			
		||||
        assert(title != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -7,18 +7,18 @@ import 'package:flutter/material.dart';
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
Future<User> searchUser(BuildContext context, UsersList users) async {
 | 
			
		||||
  return await showSearch<User>(
 | 
			
		||||
Future<User?> searchUser(BuildContext context, UsersList users) async {
 | 
			
		||||
  return await showSearch<User?>(
 | 
			
		||||
      context: context, delegate: _SearchDelegate(users));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SearchDelegate extends SearchDelegate<User> {
 | 
			
		||||
class _SearchDelegate extends SearchDelegate<User?> {
 | 
			
		||||
  final UsersList _usersList;
 | 
			
		||||
 | 
			
		||||
  _SearchDelegate(this._usersList) : assert(_usersList != null);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<Widget> buildActions(BuildContext context) => null;
 | 
			
		||||
  List<Widget>? buildActions(BuildContext context) => null;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget buildLeading(BuildContext context) => IconButton(
 | 
			
		||||
@@ -28,7 +28,7 @@ class _SearchDelegate extends SearchDelegate<User> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget buildSuggestions(BuildContext context) {
 | 
			
		||||
    final list = _usersList
 | 
			
		||||
    final List<User> list = _usersList
 | 
			
		||||
        .where((element) =>
 | 
			
		||||
            element.fullName.toLowerCase().contains(query.toLowerCase()))
 | 
			
		||||
        .toList();
 | 
			
		||||
 
 | 
			
		||||
@@ -35,11 +35,11 @@ class ForezDirectoryScreen extends StatefulWidget {
 | 
			
		||||
class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
 | 
			
		||||
  final _key = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
 | 
			
		||||
  UsersList _users;
 | 
			
		||||
  GroupMembersList _members;
 | 
			
		||||
  late UsersList _users;
 | 
			
		||||
  late GroupMembersList _members;
 | 
			
		||||
 | 
			
		||||
  Future<void> _load() async {
 | 
			
		||||
    _members = await GroupsHelper.getMembersList(forezGroup.id);
 | 
			
		||||
    _members = await GroupsHelper.getMembersList(forezGroup!.id);
 | 
			
		||||
    _users = await UsersHelper().getListWithThrow(_members.usersID);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -49,7 +49,7 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
 | 
			
		||||
          AsyncScreenWidget(
 | 
			
		||||
            onReload: _load,
 | 
			
		||||
            onBuild: onBuild,
 | 
			
		||||
            errorMessage: tr("Failed to load members list!"),
 | 
			
		||||
            errorMessage: tr("Failed to load members list!")!,
 | 
			
		||||
            key: _key,
 | 
			
		||||
          ),
 | 
			
		||||
          Positioned(
 | 
			
		||||
@@ -88,13 +88,13 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
 | 
			
		||||
                  "Do you really want to cancel the invitation sent to %u%?",
 | 
			
		||||
                  args: {"u": user.fullName}))) return;
 | 
			
		||||
 | 
			
		||||
          await GroupsHelper.cancelInvitation(forezGroup.id, user.id);
 | 
			
		||||
          _key.currentState.refresh();
 | 
			
		||||
          await GroupsHelper.cancelInvitation(forezGroup!.id, user.id);
 | 
			
		||||
          _key.currentState!.refresh();
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case _PopupMenuActions.ACCEPT_REQUEST:
 | 
			
		||||
          await GroupsHelper.respondRequest(forezGroup.id, user.id, true);
 | 
			
		||||
          _key.currentState.refresh();
 | 
			
		||||
          await GroupsHelper.respondRequest(forezGroup!.id, user.id, true);
 | 
			
		||||
          _key.currentState!.refresh();
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
        case _PopupMenuActions.REJECT_REQUEST:
 | 
			
		||||
@@ -104,13 +104,13 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
 | 
			
		||||
                  "Do you really want to reject the request of %u% to join the Forez group?",
 | 
			
		||||
                  args: {"u": user.fullName}))) return;
 | 
			
		||||
 | 
			
		||||
          await GroupsHelper.respondRequest(forezGroup.id, user.id, false);
 | 
			
		||||
          _key.currentState.refresh();
 | 
			
		||||
          await GroupsHelper.respondRequest(forezGroup!.id, user.id, false);
 | 
			
		||||
          _key.currentState!.refresh();
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Error while processing action!"));
 | 
			
		||||
      snack(context, tr("Error while processing action!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -125,7 +125,7 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _openUserProfile(User user) =>
 | 
			
		||||
      MainController.of(context).openUserPage(user.id);
 | 
			
		||||
      MainController.of(context)!.openUserPage(user.id!);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ForezMemberTile extends StatelessWidget {
 | 
			
		||||
@@ -135,11 +135,11 @@ class _ForezMemberTile extends StatelessWidget {
 | 
			
		||||
  final Function(User) onTap;
 | 
			
		||||
 | 
			
		||||
  const _ForezMemberTile({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.user,
 | 
			
		||||
    @required this.member,
 | 
			
		||||
    @required this.selectedAction,
 | 
			
		||||
    @required this.onTap,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.user,
 | 
			
		||||
    required this.member,
 | 
			
		||||
    required this.selectedAction,
 | 
			
		||||
    required this.onTap,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -147,7 +147,7 @@ class _ForezMemberTile extends StatelessWidget {
 | 
			
		||||
        leading: AccountImageWidget(user: user),
 | 
			
		||||
        title: Text(user.fullName),
 | 
			
		||||
        subtitle: Text(member.membershipText),
 | 
			
		||||
        trailing: !member.isAtLeastMember && forezGroup.isAtLeastModerator
 | 
			
		||||
        trailing: !member.isAtLeastMember && forezGroup!.isAtLeastModerator
 | 
			
		||||
            ? (member.isInvited
 | 
			
		||||
                ? _buildInvitedButton()
 | 
			
		||||
                : _buildRequestedButton())
 | 
			
		||||
@@ -155,7 +155,7 @@ class _ForezMemberTile extends StatelessWidget {
 | 
			
		||||
        onTap: member.isAtLeastMember ? () => onTap(user) : null,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  Widget _buildConversationButton() => user.id == userID()
 | 
			
		||||
  Widget? _buildConversationButton() => user.id == userID()
 | 
			
		||||
      ? null
 | 
			
		||||
      : IconButton(
 | 
			
		||||
          icon: Icon(Icons.message),
 | 
			
		||||
@@ -201,12 +201,12 @@ class _MembershipButton extends StatelessWidget {
 | 
			
		||||
  final IconData icon;
 | 
			
		||||
 | 
			
		||||
  const _MembershipButton({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.user,
 | 
			
		||||
    @required this.action,
 | 
			
		||||
    @required this.onTap,
 | 
			
		||||
    @required this.color,
 | 
			
		||||
    @required this.icon,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.user,
 | 
			
		||||
    required this.action,
 | 
			
		||||
    required this.onTap,
 | 
			
		||||
    required this.color,
 | 
			
		||||
    required this.icon,
 | 
			
		||||
  })  : assert(user != null),
 | 
			
		||||
        assert(action != null),
 | 
			
		||||
        assert(onTap != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -26,14 +26,14 @@ enum CreateAccountResult {
 | 
			
		||||
 | 
			
		||||
class AccountHelper {
 | 
			
		||||
  // Current user ID
 | 
			
		||||
  static int _currentUserID = -1;
 | 
			
		||||
  static int? _currentUserID = -1;
 | 
			
		||||
 | 
			
		||||
  /// Checkout whether current user is signed in or not
 | 
			
		||||
  ///
 | 
			
		||||
  /// Warning : This method MUST BE CALLED AT LEAST ONCE AFTER APP START !!!
 | 
			
		||||
  Future<bool> signedIn() async {
 | 
			
		||||
    bool signedIn =
 | 
			
		||||
        (await PreferencesHelper.getInstance()).getLoginToken() != null;
 | 
			
		||||
        (await PreferencesHelper.getInstance())!.getLoginToken() != null;
 | 
			
		||||
 | 
			
		||||
    // Load current user ID for later use
 | 
			
		||||
    if (signedIn && _currentUserID == -1) await _loadCurrentUserID();
 | 
			
		||||
@@ -56,8 +56,8 @@ class AccountHelper {
 | 
			
		||||
    else if (response.code != 200) return AuthResult.NETWORK_ERROR;
 | 
			
		||||
 | 
			
		||||
    // Save login token
 | 
			
		||||
    await (await PreferencesHelper.getInstance())
 | 
			
		||||
        .setLoginToken(response.getObject()["token"]);
 | 
			
		||||
    await (await PreferencesHelper.getInstance())!
 | 
			
		||||
        .setLoginToken(response.getObject()!["token"]);
 | 
			
		||||
 | 
			
		||||
    // Get current user ID
 | 
			
		||||
    final userID = await _downloadCurrentUserID();
 | 
			
		||||
@@ -67,7 +67,7 @@ class AccountHelper {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Save current user ID
 | 
			
		||||
    final preferences = await PreferencesHelper.getInstance();
 | 
			
		||||
    final preferences = await (PreferencesHelper.getInstance());
 | 
			
		||||
    await preferences.setInt(PreferencesKeyList.USER_ID, userID);
 | 
			
		||||
    _currentUserID = userID;
 | 
			
		||||
 | 
			
		||||
@@ -130,27 +130,27 @@ class AccountHelper {
 | 
			
		||||
          .getObject()["exists"];
 | 
			
		||||
 | 
			
		||||
  /// Get current user email address
 | 
			
		||||
  static Future<String> getCurrentAccountEmailAddress() async =>
 | 
			
		||||
  static Future<String?> getCurrentAccountEmailAddress() async =>
 | 
			
		||||
      (await APIRequest.withLogin("account/mail")
 | 
			
		||||
          .execWithThrowGetObject())["mail"];
 | 
			
		||||
          .execWithThrowGetObject())!["mail"];
 | 
			
		||||
 | 
			
		||||
  /// Check out whether security questions have been set for an account or not
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<bool> hasSecurityQuestions(String email) async =>
 | 
			
		||||
  static Future<bool?> hasSecurityQuestions(String email) async =>
 | 
			
		||||
      (await APIRequest.withoutLogin("account/has_security_questions")
 | 
			
		||||
              .addString("email", email)
 | 
			
		||||
              .execWithThrow())
 | 
			
		||||
          .getObject()["defined"];
 | 
			
		||||
          .getObject()!["defined"];
 | 
			
		||||
 | 
			
		||||
  /// Get the security questions of the user
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<List<String>> getSecurityQuestions(String email) async =>
 | 
			
		||||
  static Future<List<String>?> getSecurityQuestions(String? email) async =>
 | 
			
		||||
      ((await APIRequest.withoutLogin("account/get_security_questions")
 | 
			
		||||
                  .addString("email", email)
 | 
			
		||||
                  .execWithThrow())
 | 
			
		||||
              .getObject()["questions"])
 | 
			
		||||
              .getObject()!["questions"])
 | 
			
		||||
          .cast<String>();
 | 
			
		||||
 | 
			
		||||
  /// Validate given security answers
 | 
			
		||||
@@ -158,14 +158,14 @@ class AccountHelper {
 | 
			
		||||
  /// Throws an [Exception] in case of failure
 | 
			
		||||
  ///
 | 
			
		||||
  /// Returns a password reset token in case of success
 | 
			
		||||
  static Future<String> checkAnswers(
 | 
			
		||||
          String email, List<String> answers) async =>
 | 
			
		||||
  static Future<String?> checkAnswers(
 | 
			
		||||
          String? email, List<String> answers) async =>
 | 
			
		||||
      (await APIRequest.withoutLogin("account/check_security_answers")
 | 
			
		||||
              .addString("email", email)
 | 
			
		||||
              .addString("answers",
 | 
			
		||||
                  answers.map((f) => Uri.encodeComponent(f)).join("&"))
 | 
			
		||||
              .execWithThrow())
 | 
			
		||||
          .getObject()["reset_token"];
 | 
			
		||||
          .getObject()!["reset_token"];
 | 
			
		||||
 | 
			
		||||
  /// Check a password reset token
 | 
			
		||||
  ///
 | 
			
		||||
@@ -195,25 +195,26 @@ class AccountHelper {
 | 
			
		||||
          .execWithThrow();
 | 
			
		||||
 | 
			
		||||
  /// Get current user ID from the server
 | 
			
		||||
  Future<int> _downloadCurrentUserID() async {
 | 
			
		||||
  Future<int?> _downloadCurrentUserID() async {
 | 
			
		||||
    final response = await APIRequest.withLogin("account/id").exec();
 | 
			
		||||
 | 
			
		||||
    if (response.code != 200) return null;
 | 
			
		||||
 | 
			
		||||
    return response.getObject()["userID"];
 | 
			
		||||
    return response.getObject()!["userID"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the ID of the currently signed in user
 | 
			
		||||
  Future<void> _loadCurrentUserID() async {
 | 
			
		||||
    final preferences = await PreferencesHelper.getInstance();
 | 
			
		||||
    final preferences =
 | 
			
		||||
        await PreferencesHelper.getInstance();
 | 
			
		||||
    _currentUserID = preferences.getInt(PreferencesKeyList.USER_ID);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Check if current user ID is loaded or not
 | 
			
		||||
  static bool get isUserIDLoaded => _currentUserID > 0;
 | 
			
		||||
  static bool get isUserIDLoaded => _currentUserID! > 0;
 | 
			
		||||
 | 
			
		||||
  /// Get the ID of the currently signed in user
 | 
			
		||||
  static int getCurrentUserID() {
 | 
			
		||||
  static int? getCurrentUserID() {
 | 
			
		||||
    if (_currentUserID == -1) throw "Current user ID has not been loaded yet!";
 | 
			
		||||
    return _currentUserID;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ class APIHelper {
 | 
			
		||||
 | 
			
		||||
      //Add user token (if required)
 | 
			
		||||
      if (request.needLogin) {
 | 
			
		||||
        final token = (await PreferencesHelper.getInstance()).getLoginToken();
 | 
			
		||||
        final token = (await PreferencesHelper.getInstance())!.getLoginToken();
 | 
			
		||||
 | 
			
		||||
        if (token == null) {
 | 
			
		||||
          EventsHelper.emit(InvalidLoginTokensEvent());
 | 
			
		||||
@@ -41,13 +41,13 @@ class APIHelper {
 | 
			
		||||
      else
 | 
			
		||||
        url = Uri.https(config().apiServerName, path);
 | 
			
		||||
 | 
			
		||||
      final data = FormData.fromMap(request.args);
 | 
			
		||||
      final data = FormData.fromMap(request.args!);
 | 
			
		||||
 | 
			
		||||
      // Process files (if required)
 | 
			
		||||
      if (multipart) {
 | 
			
		||||
        // Process filesystem files
 | 
			
		||||
        for (final key in request.files.keys) {
 | 
			
		||||
          var v = request.files[key];
 | 
			
		||||
          var v = request.files[key]!;
 | 
			
		||||
          data.files.add(MapEntry(
 | 
			
		||||
              key,
 | 
			
		||||
              await MultipartFile.fromFile(v.path,
 | 
			
		||||
@@ -56,11 +56,11 @@ class APIHelper {
 | 
			
		||||
 | 
			
		||||
        // Process in-memory files
 | 
			
		||||
        for (final key in request.bytesFiles.keys) {
 | 
			
		||||
          var v = request.bytesFiles[key];
 | 
			
		||||
          var v = request.bytesFiles[key]!;
 | 
			
		||||
          data.files.add(MapEntry(
 | 
			
		||||
              key,
 | 
			
		||||
              MultipartFile.fromBytes(
 | 
			
		||||
                v.bytes,
 | 
			
		||||
                v.bytes!,
 | 
			
		||||
                filename: v.filename.split("/").last,
 | 
			
		||||
                contentType: v.type,
 | 
			
		||||
              )));
 | 
			
		||||
@@ -85,9 +85,9 @@ class APIHelper {
 | 
			
		||||
        EventsHelper.emit(InvalidLoginTokensEvent());
 | 
			
		||||
 | 
			
		||||
      if (response.statusCode != HttpStatus.ok)
 | 
			
		||||
        return APIResponse(response.statusCode, response.data);
 | 
			
		||||
        return APIResponse(response.statusCode!, response.data);
 | 
			
		||||
 | 
			
		||||
      return APIResponse(response.statusCode, response.data);
 | 
			
		||||
      return APIResponse(response.statusCode!, response.data);
 | 
			
		||||
    } catch (e, stack) {
 | 
			
		||||
      print(e.toString());
 | 
			
		||||
      print("Could not execute a request!");
 | 
			
		||||
 
 | 
			
		||||
@@ -39,12 +39,12 @@ class CallsHelper {
 | 
			
		||||
            .cast<CallMember>());
 | 
			
		||||
 | 
			
		||||
  /// Request an offer to access another peer's stream
 | 
			
		||||
  static Future<void> requestOffer(int callID, int peerID) async =>
 | 
			
		||||
  static Future<void> requestOffer(int callID, int? peerID) async =>
 | 
			
		||||
      await ws("calls/request_offer", {"callID": callID, "peerID": peerID});
 | 
			
		||||
 | 
			
		||||
  /// Send a Session Description message to the server
 | 
			
		||||
  static Future<void> sendSessionDescription(
 | 
			
		||||
          int callID, int peerID, RTCSessionDescription sdp) async =>
 | 
			
		||||
          int callID, int? peerID, RTCSessionDescription sdp) async =>
 | 
			
		||||
      await ws("calls/signal", {
 | 
			
		||||
        "callID": callID,
 | 
			
		||||
        "peerID": peerID,
 | 
			
		||||
@@ -54,7 +54,7 @@ class CallsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Send an IceCandidate
 | 
			
		||||
  static Future<void> sendIceCandidate(
 | 
			
		||||
          int callID, int peerID, RTCIceCandidate candidate) async =>
 | 
			
		||||
          int callID, int? peerID, RTCIceCandidate candidate) async =>
 | 
			
		||||
      await ws("calls/signal", {
 | 
			
		||||
        "callID": callID,
 | 
			
		||||
        "peerID": peerID,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ class CommentsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get a single comment from the server, specified by its [id]
 | 
			
		||||
  Future<Comment> getSingle(int id) async {
 | 
			
		||||
  Future<Comment?> getSingle(int id) async {
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
        uri: "comments/get_single",
 | 
			
		||||
        needLogin: true,
 | 
			
		||||
@@ -39,7 +39,7 @@ class CommentsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Update comment content
 | 
			
		||||
  Future<bool> updateContent(int id, String newContent) async {
 | 
			
		||||
  Future<bool> updateContent(int id, String? newContent) async {
 | 
			
		||||
    return (await APIRequest(uri: "comments/edit", needLogin: true, args: {
 | 
			
		||||
      "commentID": id.toString(),
 | 
			
		||||
      "content": newContent,
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ import 'package:comunic/utils/account_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/color_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/dart_color.dart';
 | 
			
		||||
import 'package:dio/dio.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Conversation helper
 | 
			
		||||
///
 | 
			
		||||
@@ -47,13 +46,13 @@ class ConversationsHelper {
 | 
			
		||||
        .addBool("canEveryoneAddMembers", settings.canEveryoneAddMembers)
 | 
			
		||||
        .execWithThrow();
 | 
			
		||||
 | 
			
		||||
    return response.getObject()["conversationID"];
 | 
			
		||||
    return response.getObject()!["conversationID"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Add a member to a conversation.
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> addMember(int convID, int userID) async =>
 | 
			
		||||
  static Future<void> addMember(int? convID, int? userID) async =>
 | 
			
		||||
      await APIRequest.withLogin("conversations/addMember")
 | 
			
		||||
          .addInt("convID", convID)
 | 
			
		||||
          .addInt("userID", userID)
 | 
			
		||||
@@ -62,7 +61,7 @@ class ConversationsHelper {
 | 
			
		||||
  /// Remove a member from a conversation.
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> removeMember(int convID, int userID) async =>
 | 
			
		||||
  static Future<void> removeMember(int? convID, int? userID) async =>
 | 
			
		||||
      await APIRequest.withLogin("conversations/removeMember")
 | 
			
		||||
          .addInt("convID", convID)
 | 
			
		||||
          .addInt("userID", userID)
 | 
			
		||||
@@ -71,7 +70,7 @@ class ConversationsHelper {
 | 
			
		||||
  /// Update admin status of a user in a conversation
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> setAdmin(int/*!*/ convID, int/*!*/ userID, bool admin) async =>
 | 
			
		||||
  static Future<void> setAdmin(int convID, int userID, bool admin) async =>
 | 
			
		||||
      await APIRequest.withLogin("conversations/setAdmin")
 | 
			
		||||
          .addInt("convID", convID)
 | 
			
		||||
          .addInt("userID", userID)
 | 
			
		||||
@@ -91,7 +90,7 @@ class ConversationsHelper {
 | 
			
		||||
    if (settings.isComplete)
 | 
			
		||||
      request
 | 
			
		||||
          .addString("name", settings.name ?? "")
 | 
			
		||||
          .addBool("canEveryoneAddMembers", settings.canEveryoneAddMembers)
 | 
			
		||||
          .addBool("canEveryoneAddMembers", settings.canEveryoneAddMembers!)
 | 
			
		||||
          .addString("color", colorToHex(settings.color));
 | 
			
		||||
 | 
			
		||||
    await request.execWithThrow();
 | 
			
		||||
@@ -104,7 +103,7 @@ class ConversationsHelper {
 | 
			
		||||
  /// Set a new conversation logo
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> changeImage(int convID, BytesFile file) async =>
 | 
			
		||||
  static Future<void> changeImage(int? convID, BytesFile file) async =>
 | 
			
		||||
      await APIRequest.withLogin("conversations/change_image")
 | 
			
		||||
          .addInt("convID", convID)
 | 
			
		||||
          .addBytesFile("file", file)
 | 
			
		||||
@@ -113,13 +112,13 @@ class ConversationsHelper {
 | 
			
		||||
  /// Remove conversation logo
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> removeLogo(int convID) async =>
 | 
			
		||||
  static Future<void> removeLogo(int? convID) async =>
 | 
			
		||||
      await APIRequest.withLogin("conversations/delete_image")
 | 
			
		||||
          .addInt("convID", convID)
 | 
			
		||||
          .execWithThrow();
 | 
			
		||||
 | 
			
		||||
  /// Delete a conversation specified by its [id]
 | 
			
		||||
  Future<void> deleteConversation(int id) async =>
 | 
			
		||||
  Future<void> deleteConversation(int? id) async =>
 | 
			
		||||
      await APIRequest.withLogin("conversations/delete")
 | 
			
		||||
          .addInt("conversationID", id)
 | 
			
		||||
          .execWithThrow();
 | 
			
		||||
@@ -132,7 +131,7 @@ class ConversationsHelper {
 | 
			
		||||
        await APIRequest.withLogin("conversations/getList").execWithThrow();
 | 
			
		||||
 | 
			
		||||
    ConversationsList list = ConversationsList();
 | 
			
		||||
    response.getArray().forEach((f) => list.add(apiToConversation(f)));
 | 
			
		||||
    response.getArray()!.forEach((f) => list.add(apiToConversation(f)));
 | 
			
		||||
 | 
			
		||||
    // Update the database
 | 
			
		||||
    await ConversationsSerializationHelper().setList(list);
 | 
			
		||||
@@ -148,13 +147,13 @@ class ConversationsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get information about a single conversation specified by its [id]
 | 
			
		||||
  Future<Conversation> _downloadSingle(int id) async {
 | 
			
		||||
  Future<Conversation> _downloadSingle(int? id) async {
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
        uri: "conversations/get_single",
 | 
			
		||||
        needLogin: true,
 | 
			
		||||
        args: {"conversationID": id.toString()}).execWithThrow();
 | 
			
		||||
 | 
			
		||||
    final conversation = apiToConversation(response.getObject());
 | 
			
		||||
    final conversation = apiToConversation(response.getObject()!);
 | 
			
		||||
 | 
			
		||||
    await ConversationsSerializationHelper()
 | 
			
		||||
        .insertOrReplaceElement((c) => c.id == conversation.id, conversation);
 | 
			
		||||
@@ -167,7 +166,7 @@ class ConversationsHelper {
 | 
			
		||||
  /// case of failure
 | 
			
		||||
  ///
 | 
			
		||||
  /// Return value of this method is never null.
 | 
			
		||||
  Future<Conversation> getSingle(int id, {bool force = false}) async {
 | 
			
		||||
  Future<Conversation> getSingle(int? id, {bool force = false}) async {
 | 
			
		||||
    if (force ||
 | 
			
		||||
        !await ConversationsSerializationHelper().any((c) => c.id == id))
 | 
			
		||||
      return await _downloadSingle(id);
 | 
			
		||||
@@ -178,19 +177,19 @@ class ConversationsHelper {
 | 
			
		||||
  /// Get the name of a [conversation]. This requires information
 | 
			
		||||
  /// about the users of this conversation
 | 
			
		||||
  static String getConversationName(
 | 
			
		||||
      Conversation conversation, UsersList users) {
 | 
			
		||||
    if (conversation.hasName) return conversation.name;
 | 
			
		||||
      Conversation conversation, UsersList? users) {
 | 
			
		||||
    if (conversation.hasName) return conversation.name!;
 | 
			
		||||
 | 
			
		||||
    String name = "";
 | 
			
		||||
    int count = 0;
 | 
			
		||||
    for (int i = 0; i < 3 && i < conversation.members.length; i++)
 | 
			
		||||
      if (conversation.members[i].userID != userID()) {
 | 
			
		||||
    for (int i = 0; i < 3 && i < conversation.members!.length; i++)
 | 
			
		||||
      if (conversation.members![i].userID != userID()) {
 | 
			
		||||
        name += (count > 0 ? ", " : "") +
 | 
			
		||||
            users.getUser(conversation.members[i].userID).fullName;
 | 
			
		||||
            users!.getUser(conversation.members![i].userID).fullName;
 | 
			
		||||
        count++;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    if (conversation.members.length > 3) name += ", ...";
 | 
			
		||||
    if (conversation.members!.length > 3) name += ", ...";
 | 
			
		||||
 | 
			
		||||
    return name;
 | 
			
		||||
  }
 | 
			
		||||
@@ -200,7 +199,7 @@ class ConversationsHelper {
 | 
			
		||||
  /// true
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws an exception in case of failure
 | 
			
		||||
  Future<int> getPrivate(int userID, {bool allowCreate = true}) async {
 | 
			
		||||
  Future<int> getPrivate(int? userID, {bool allowCreate = true}) async {
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
      uri: "conversations/getPrivate",
 | 
			
		||||
      needLogin: true,
 | 
			
		||||
@@ -211,7 +210,7 @@ class ConversationsHelper {
 | 
			
		||||
    ).execWithThrow();
 | 
			
		||||
 | 
			
		||||
    // Get and return conversation ID
 | 
			
		||||
    return int.parse(response.getObject()["conversationsID"][0].toString());
 | 
			
		||||
    return int.parse(response.getObject()!["conversationsID"][0].toString());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Asynchronously get the name of the conversation
 | 
			
		||||
@@ -222,7 +221,7 @@ class ConversationsHelper {
 | 
			
		||||
  /// Throws an exception in case of failure
 | 
			
		||||
  static Future<String> getConversationNameAsync(
 | 
			
		||||
      Conversation conversation) async {
 | 
			
		||||
    if (conversation.hasName) return conversation.name;
 | 
			
		||||
    if (conversation.hasName) return conversation.name!;
 | 
			
		||||
 | 
			
		||||
    //Get information about the members of the conversation
 | 
			
		||||
    final members = await UsersHelper().getList(conversation.membersID);
 | 
			
		||||
@@ -273,7 +272,7 @@ class ConversationsHelper {
 | 
			
		||||
 | 
			
		||||
    // Parse the response of the server
 | 
			
		||||
    ConversationMessagesList list = ConversationMessagesList();
 | 
			
		||||
    response.getArray().forEach((f) {
 | 
			
		||||
    response.getArray()!.forEach((f) {
 | 
			
		||||
      list.add(
 | 
			
		||||
        apiToConversationMessage(f),
 | 
			
		||||
      );
 | 
			
		||||
@@ -294,7 +293,7 @@ class ConversationsHelper {
 | 
			
		||||
  /// Throws an exception in case of failure
 | 
			
		||||
  Future<ConversationMessagesList> _downloadNewMessagesSingle(
 | 
			
		||||
      int conversationID,
 | 
			
		||||
      {int lastMessageID = 0}) async {
 | 
			
		||||
      {int? lastMessageID = 0}) async {
 | 
			
		||||
    // Execute the request on the server
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
        uri: "conversations/refresh_single",
 | 
			
		||||
@@ -311,8 +310,8 @@ class ConversationsHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  Future<ConversationMessagesList> getOlderMessages({
 | 
			
		||||
    @required int conversationID,
 | 
			
		||||
    @required int oldestMessagesID,
 | 
			
		||||
    required int conversationID,
 | 
			
		||||
    required int? oldestMessagesID,
 | 
			
		||||
    int limit = 15,
 | 
			
		||||
  }) async {
 | 
			
		||||
    // Perform the request online
 | 
			
		||||
@@ -334,8 +333,8 @@ class ConversationsHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  Future<ConversationMessagesList> getNewMessages(
 | 
			
		||||
      {@required int conversationID,
 | 
			
		||||
      int lastMessageID = 0,
 | 
			
		||||
      {required int conversationID,
 | 
			
		||||
      int? lastMessageID = 0,
 | 
			
		||||
      bool online = true}) async {
 | 
			
		||||
    if (online)
 | 
			
		||||
      return await _downloadNewMessagesSingle(conversationID,
 | 
			
		||||
@@ -348,8 +347,8 @@ class ConversationsHelper {
 | 
			
		||||
  /// Send a new message to the server
 | 
			
		||||
  Future<SendMessageResult> sendMessage(
 | 
			
		||||
    NewConversationMessage message, {
 | 
			
		||||
    ProgressCallback sendProgress,
 | 
			
		||||
    CancelToken cancelToken,
 | 
			
		||||
    ProgressCallback? sendProgress,
 | 
			
		||||
    CancelToken? cancelToken,
 | 
			
		||||
  }) async {
 | 
			
		||||
    final request = APIRequest.withLogin("conversations/sendMessage")
 | 
			
		||||
        .addInt("conversationID", message.conversationID)
 | 
			
		||||
@@ -388,7 +387,7 @@ class ConversationsHelper {
 | 
			
		||||
      await ConversationsMessagesSerializationHelper(msg.convID).remove(msg);
 | 
			
		||||
 | 
			
		||||
  /// Update a message content
 | 
			
		||||
  Future<bool> updateMessage(int id, String newContent) async {
 | 
			
		||||
  Future<bool> updateMessage(int? id, String newContent) async {
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
        uri: "conversations/updateMessage",
 | 
			
		||||
        needLogin: true,
 | 
			
		||||
@@ -400,7 +399,7 @@ class ConversationsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Delete permanently a message specified by its [id]
 | 
			
		||||
  Future<bool> deleteMessage(int id) async {
 | 
			
		||||
  Future<bool> deleteMessage(int? id) async {
 | 
			
		||||
    // Delete the message online
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
        uri: "conversations/deleteMessage",
 | 
			
		||||
@@ -418,7 +417,7 @@ class ConversationsHelper {
 | 
			
		||||
  static Future<UnreadConversationsList> getListUnread() async {
 | 
			
		||||
    final list = (await APIRequest.withLogin("conversations/get_list_unread")
 | 
			
		||||
            .execWithThrow())
 | 
			
		||||
        .getArray();
 | 
			
		||||
        .getArray()!;
 | 
			
		||||
 | 
			
		||||
    return UnreadConversationsList()
 | 
			
		||||
      ..addAll(list.map((f) => UnreadConversation(
 | 
			
		||||
@@ -431,7 +430,7 @@ class ConversationsHelper {
 | 
			
		||||
  /// conversation through WebSocket
 | 
			
		||||
  Future<void> registerConversationEvents(int id) async {
 | 
			
		||||
    if (_registeredConversations.containsKey(id))
 | 
			
		||||
      _registeredConversations[id]++;
 | 
			
		||||
      _registeredConversations.update(id, (value) => value + 1);
 | 
			
		||||
    else {
 | 
			
		||||
      _registeredConversations[id] = 1;
 | 
			
		||||
      await ws("\$main/register_conv", {"convID": id});
 | 
			
		||||
@@ -442,16 +441,16 @@ class ConversationsHelper {
 | 
			
		||||
  Future<void> unregisterConversationEvents(int id) async {
 | 
			
		||||
    if (!_registeredConversations.containsKey(id)) return;
 | 
			
		||||
 | 
			
		||||
    _registeredConversations[id]--;
 | 
			
		||||
    _registeredConversations.update(id, (value) => value - 1);
 | 
			
		||||
 | 
			
		||||
    if (_registeredConversations[id] <= 0) {
 | 
			
		||||
    if (_registeredConversations[id]! <= 0) {
 | 
			
		||||
      _registeredConversations.remove(id);
 | 
			
		||||
      await ws("\$main/unregister_conv", {"convID": id});
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Send a notification to inform that the user is writing a message
 | 
			
		||||
  static Future<void> sendWritingEvent(int convID) async =>
 | 
			
		||||
  static Future<void> sendWritingEvent(int? convID) async =>
 | 
			
		||||
      await ws("conversations/is_writing", {"convID": convID});
 | 
			
		||||
 | 
			
		||||
  /// Turn an API response into a ConversationMessage object
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ import 'package:sqflite/sqflite.dart';
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
abstract class DatabaseHelper {
 | 
			
		||||
  static Database _db;
 | 
			
		||||
  static Database? _db;
 | 
			
		||||
 | 
			
		||||
  /// Open the database
 | 
			
		||||
  static Future<void> open() async {
 | 
			
		||||
    if (_db != null && _db.isOpen) return;
 | 
			
		||||
    if (_db != null && _db!.isOpen) return;
 | 
			
		||||
 | 
			
		||||
    var databasePath = await getDatabasesPath();
 | 
			
		||||
    _db = await openDatabase(
 | 
			
		||||
@@ -24,7 +24,7 @@ abstract class DatabaseHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get a database instance
 | 
			
		||||
  static Future<Database> get() async {
 | 
			
		||||
  static Future<Database?> get() async {
 | 
			
		||||
    await open();
 | 
			
		||||
    return _db;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -18,12 +18,12 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
 | 
			
		||||
 | 
			
		||||
  /// Insert an entry in the database
 | 
			
		||||
  Future<void> _insertDB(T el) async {
 | 
			
		||||
    await (await DatabaseHelper.get()).insert(tableName(), el.toMap());
 | 
			
		||||
    await (await DatabaseHelper.get())!.insert(tableName(), el.toMap());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Update an element in the database
 | 
			
		||||
  Future<void> _updateDB(T el) async {
 | 
			
		||||
    await (await DatabaseHelper.get()).update(
 | 
			
		||||
    await (await DatabaseHelper.get())!.update(
 | 
			
		||||
      tableName(),
 | 
			
		||||
      el.toMap(),
 | 
			
		||||
      where: "${BaseTableContract.C_ID} = ?",
 | 
			
		||||
@@ -34,14 +34,14 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
 | 
			
		||||
  /// Get an element from the database with a specified [id]
 | 
			
		||||
  ///
 | 
			
		||||
  /// Returns null if none found
 | 
			
		||||
  Future<T> get(int id) async {
 | 
			
		||||
    List<Map> maps = await (await DatabaseHelper.get()).query(
 | 
			
		||||
  Future<T?> get(int id) async {
 | 
			
		||||
    List<Map> maps = await (await DatabaseHelper.get())!.query(
 | 
			
		||||
      tableName(),
 | 
			
		||||
      where: '${BaseTableContract.C_ID} = ?',
 | 
			
		||||
      whereArgs: [id],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (maps.length > 0) return initializeFromMap(maps[0]);
 | 
			
		||||
    if (maps.length > 0) return initializeFromMap(maps[0] as Map<String, dynamic>);
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
@@ -50,7 +50,7 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Return true if at least one entry was deleted / false else
 | 
			
		||||
  Future<bool> delete(int id) async {
 | 
			
		||||
    return await (await DatabaseHelper.get()).delete(
 | 
			
		||||
    return await (await DatabaseHelper.get())!.delete(
 | 
			
		||||
      tableName(),
 | 
			
		||||
      where: '${BaseTableContract.C_ID} = ?',
 | 
			
		||||
      whereArgs: [id],
 | 
			
		||||
@@ -59,22 +59,22 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
 | 
			
		||||
 | 
			
		||||
  /// Get all the entries from the table
 | 
			
		||||
  Future<List<T>> getAll() async {
 | 
			
		||||
    List<Map> maps = await (await DatabaseHelper.get()).query(tableName());
 | 
			
		||||
    return maps.map((f) => initializeFromMap(f)).toList();
 | 
			
		||||
    List<Map> maps = await (await DatabaseHelper.get())!.query(tableName());
 | 
			
		||||
    return maps.map((f) => initializeFromMap(f as Map<String, dynamic>)).toList();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get some entries from the table based on some conditions
 | 
			
		||||
  Future<List<T>> getMultiple(
 | 
			
		||||
      {bool distinct,
 | 
			
		||||
      List<String> columns,
 | 
			
		||||
      String where,
 | 
			
		||||
      List<dynamic> whereArgs,
 | 
			
		||||
      String groupBy,
 | 
			
		||||
      String having,
 | 
			
		||||
      String orderBy,
 | 
			
		||||
      int limit,
 | 
			
		||||
      int offset}) async {
 | 
			
		||||
    List<Map> maps = await (await DatabaseHelper.get()).query(
 | 
			
		||||
      {bool? distinct,
 | 
			
		||||
      List<String>? columns,
 | 
			
		||||
      String? where,
 | 
			
		||||
      List<dynamic>? whereArgs,
 | 
			
		||||
      String? groupBy,
 | 
			
		||||
      String? having,
 | 
			
		||||
      String? orderBy,
 | 
			
		||||
      int? limit,
 | 
			
		||||
      int? offset}) async {
 | 
			
		||||
    List<Map> maps = await (await DatabaseHelper.get())!.query(
 | 
			
		||||
      tableName(),
 | 
			
		||||
      distinct: distinct,
 | 
			
		||||
      columns: columns,
 | 
			
		||||
@@ -86,12 +86,12 @@ abstract class ModelDatabaseHelper<T extends CacheModel> {
 | 
			
		||||
      limit: limit,
 | 
			
		||||
      offset: offset,
 | 
			
		||||
    );
 | 
			
		||||
    return maps.map((f) => initializeFromMap(f)).toList();
 | 
			
		||||
    return maps.map((f) => initializeFromMap(f as Map<String, dynamic>)).toList();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Empty the table
 | 
			
		||||
  Future<void> clearTable() async {
 | 
			
		||||
    await (await DatabaseHelper.get()).execute("DELETE FROM ${tableName()}");
 | 
			
		||||
    await (await DatabaseHelper.get())!.execute("DELETE FROM ${tableName()}");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Check out whether an element specified with its [id] is present
 | 
			
		||||
 
 | 
			
		||||
@@ -17,14 +17,14 @@ class WSClosedEvent {}
 | 
			
		||||
 | 
			
		||||
/// New number of notifications
 | 
			
		||||
class NewNumberNotifsEvent {
 | 
			
		||||
  final int newNum;
 | 
			
		||||
  final int? newNum;
 | 
			
		||||
 | 
			
		||||
  NewNumberNotifsEvent(this.newNum);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// New number of unread conversations
 | 
			
		||||
class NewNumberUnreadConversations {
 | 
			
		||||
  final int newNum;
 | 
			
		||||
  final int? newNum;
 | 
			
		||||
 | 
			
		||||
  NewNumberUnreadConversations(this.newNum);
 | 
			
		||||
}
 | 
			
		||||
@@ -45,15 +45,15 @@ class UpdatedCommentEvent {
 | 
			
		||||
 | 
			
		||||
/// Deleted comment
 | 
			
		||||
class DeletedCommentEvent {
 | 
			
		||||
  final int commentID;
 | 
			
		||||
  final int? commentID;
 | 
			
		||||
 | 
			
		||||
  DeletedCommentEvent(this.commentID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Writing message in conversation event
 | 
			
		||||
class WritingMessageInConversationEvent {
 | 
			
		||||
  final int convID;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int? convID;
 | 
			
		||||
  final int? userID;
 | 
			
		||||
 | 
			
		||||
  WritingMessageInConversationEvent(this.convID, this.userID);
 | 
			
		||||
}
 | 
			
		||||
@@ -81,31 +81,31 @@ class DeletedConversationMessageEvent {
 | 
			
		||||
 | 
			
		||||
/// Remove user from conversation
 | 
			
		||||
class RemovedUserFromConversationEvent {
 | 
			
		||||
  final int convID;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int? convID;
 | 
			
		||||
  final int? userID;
 | 
			
		||||
 | 
			
		||||
  RemovedUserFromConversationEvent(this.convID, this.userID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Deleted conversation
 | 
			
		||||
class DeletedConversationEvent {
 | 
			
		||||
  final int convID;
 | 
			
		||||
  final int? convID;
 | 
			
		||||
 | 
			
		||||
  DeletedConversationEvent(this.convID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// User joined call event
 | 
			
		||||
class UserJoinedCallEvent {
 | 
			
		||||
  final int callID;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int? callID;
 | 
			
		||||
  final int? userID;
 | 
			
		||||
 | 
			
		||||
  UserJoinedCallEvent(this.callID, this.userID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// User left call event
 | 
			
		||||
class UserLeftCallEvent {
 | 
			
		||||
  final int callID;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int? callID;
 | 
			
		||||
  final int? userID;
 | 
			
		||||
 | 
			
		||||
  UserLeftCallEvent(this.callID, this.userID);
 | 
			
		||||
}
 | 
			
		||||
@@ -114,12 +114,12 @@ class UserLeftCallEvent {
 | 
			
		||||
class NewCallSignalEvent {
 | 
			
		||||
  final int callID;
 | 
			
		||||
  final int peerID;
 | 
			
		||||
  final RTCSessionDescription sessionDescription;
 | 
			
		||||
  final RTCIceCandidate candidate;
 | 
			
		||||
  final RTCSessionDescription? sessionDescription;
 | 
			
		||||
  final RTCIceCandidate? candidate;
 | 
			
		||||
 | 
			
		||||
  const NewCallSignalEvent({
 | 
			
		||||
    this.callID,
 | 
			
		||||
    this.peerID,
 | 
			
		||||
    required this.callID,
 | 
			
		||||
    required this.peerID,
 | 
			
		||||
    this.sessionDescription,
 | 
			
		||||
    this.candidate,
 | 
			
		||||
  })  : assert(callID != null),
 | 
			
		||||
@@ -129,23 +129,23 @@ class NewCallSignalEvent {
 | 
			
		||||
 | 
			
		||||
/// Call peer ready event
 | 
			
		||||
class CallPeerReadyEvent {
 | 
			
		||||
  final int callID;
 | 
			
		||||
  final int peerID;
 | 
			
		||||
  final int? callID;
 | 
			
		||||
  final int? peerID;
 | 
			
		||||
 | 
			
		||||
  CallPeerReadyEvent(this.callID, this.peerID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Call peer interrupted streaming event
 | 
			
		||||
class CallPeerInterruptedStreamingEvent {
 | 
			
		||||
  final int callID;
 | 
			
		||||
  final int peerID;
 | 
			
		||||
  final int? callID;
 | 
			
		||||
  final int? peerID;
 | 
			
		||||
 | 
			
		||||
  CallPeerInterruptedStreamingEvent(this.callID, this.peerID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Call closed event
 | 
			
		||||
class CallClosedEvent {
 | 
			
		||||
  final int callID;
 | 
			
		||||
  final int? callID;
 | 
			
		||||
 | 
			
		||||
  CallClosedEvent(this.callID);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ class FirebaseMessagingHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get a Firebase access token
 | 
			
		||||
  static Future<String> getToken() async {
 | 
			
		||||
  static Future<String?> getToken() async {
 | 
			
		||||
    return await FirebaseMessaging.instance.getToken();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import 'package:comunic/models/group.dart';
 | 
			
		||||
class ForezGroupsHelper {
 | 
			
		||||
  static Future<List<Group>> getForezGroups() async {
 | 
			
		||||
    return (await APIRequest.withLogin("forez/get_groups").execWithThrow())
 | 
			
		||||
        .getArray()
 | 
			
		||||
        .getArray()!
 | 
			
		||||
        .cast<Map<String, dynamic>>()
 | 
			
		||||
        .map(GroupsHelper.getGroupFromAPI)
 | 
			
		||||
        .toList();
 | 
			
		||||
@@ -21,10 +21,10 @@ class ForezGroupsHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// This methods throws an exception in case of failure
 | 
			
		||||
  static Future<AdvancedUserInfo> getMemberInfo(int groupID, int userID) async {
 | 
			
		||||
    final response = await APIRequest.withLogin("forez/get_member_info")
 | 
			
		||||
    final response = await (APIRequest.withLogin("forez/get_member_info")
 | 
			
		||||
        .addInt("group", groupID)
 | 
			
		||||
        .addInt("user", userID)
 | 
			
		||||
        .execWithThrowGetObject();
 | 
			
		||||
        .execWithThrowGetObject());
 | 
			
		||||
 | 
			
		||||
    return UsersHelper.apiToAdvancedUserInfo(response);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,8 @@ import 'package:comunic/models/forez_presence.dart';
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
int _cachedGroup;
 | 
			
		||||
PresenceSet _cache;
 | 
			
		||||
int? _cachedGroup;
 | 
			
		||||
PresenceSet? _cache;
 | 
			
		||||
 | 
			
		||||
class ForezPresenceHelper {
 | 
			
		||||
  /// Refresh presence cache
 | 
			
		||||
@@ -40,16 +40,16 @@ class ForezPresenceHelper {
 | 
			
		||||
  /// Get the presences of a given user
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<PresenceSet> getForUser(int groupID, int userID) async {
 | 
			
		||||
  static Future<PresenceSet> getForUser(int groupID, int? userID) async {
 | 
			
		||||
    await _checkCache(groupID);
 | 
			
		||||
 | 
			
		||||
    return _cache.getForUser(userID);
 | 
			
		||||
    return _cache!.getForUser(userID);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get all the available presences
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<PresenceSet> getAll(int groupID) async {
 | 
			
		||||
  static Future<PresenceSet?> getAll(int groupID) async {
 | 
			
		||||
    await _checkCache(groupID);
 | 
			
		||||
    return _cache;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ import 'package:comunic/lists/friends_list.dart';
 | 
			
		||||
import 'package:comunic/models/api_request.dart';
 | 
			
		||||
import 'package:comunic/models/friend.dart';
 | 
			
		||||
import 'package:comunic/models/friend_status.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Friends helper
 | 
			
		||||
///
 | 
			
		||||
@@ -16,7 +15,7 @@ class FriendsHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Returns the list of friends in case of success, or null if an error
 | 
			
		||||
  /// occurred
 | 
			
		||||
  Future<FriendsList> _downloadList() async {
 | 
			
		||||
  Future<FriendsList?> _downloadList() async {
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
      uri: "friends/getList",
 | 
			
		||||
      needLogin: true,
 | 
			
		||||
@@ -30,7 +29,7 @@ class FriendsHelper {
 | 
			
		||||
    // Parse and return the list of friends
 | 
			
		||||
    FriendsList list = FriendsList()
 | 
			
		||||
      ..addAll(response
 | 
			
		||||
          .getArray()
 | 
			
		||||
          .getArray()!
 | 
			
		||||
          .cast<Map<String, dynamic>>()
 | 
			
		||||
          .map(apiToFriend)
 | 
			
		||||
          .toList());
 | 
			
		||||
@@ -54,7 +53,7 @@ class FriendsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the list, either from an online or an offline source
 | 
			
		||||
  Future<FriendsList> getList({@required bool online}) async {
 | 
			
		||||
  Future<FriendsList?> getList({required bool online}) async {
 | 
			
		||||
    if (online)
 | 
			
		||||
      return await _downloadList();
 | 
			
		||||
    else
 | 
			
		||||
@@ -108,7 +107,7 @@ class FriendsHelper {
 | 
			
		||||
    if (response.code != 200)
 | 
			
		||||
      throw Exception("Could not get friendship status!");
 | 
			
		||||
 | 
			
		||||
    final obj = response.getObject();
 | 
			
		||||
    final obj = response.getObject()!;
 | 
			
		||||
 | 
			
		||||
    return FriendStatus(
 | 
			
		||||
      userID: userID,
 | 
			
		||||
@@ -132,7 +131,7 @@ class FriendsHelper {
 | 
			
		||||
    if (response.code != 200)
 | 
			
		||||
      throw new Exception("Could not get the list of friends of this user!");
 | 
			
		||||
 | 
			
		||||
    return Set<int>.from(response.getArray());
 | 
			
		||||
    return Set<int>.from(response.getArray()!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Send a friendship request to a specified user
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ enum GetAdvancedInfoStatus { SUCCESS, ACCESS_DENIED }
 | 
			
		||||
 | 
			
		||||
class GetAdvancedInfoResult {
 | 
			
		||||
  final GetAdvancedInfoStatus status;
 | 
			
		||||
  final AdvancedGroupInfo info;
 | 
			
		||||
  final AdvancedGroupInfo? info;
 | 
			
		||||
 | 
			
		||||
  GetAdvancedInfoResult(this.status, this.info) : assert(status != null);
 | 
			
		||||
}
 | 
			
		||||
@@ -57,7 +57,7 @@ class GetAdvancedInfoResult {
 | 
			
		||||
/// Groups helper
 | 
			
		||||
class GroupsHelper {
 | 
			
		||||
  /// Download a list of groups information from the server
 | 
			
		||||
  Future<GroupsList> _downloadList(Set<int> groups) async {
 | 
			
		||||
  Future<GroupsList?> _downloadList(Set<int?> groups) async {
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
      uri: "groups/get_multiple_info",
 | 
			
		||||
      needLogin: true,
 | 
			
		||||
@@ -69,7 +69,7 @@ class GroupsHelper {
 | 
			
		||||
    final list = GroupsList();
 | 
			
		||||
 | 
			
		||||
    response
 | 
			
		||||
        .getObject()
 | 
			
		||||
        .getObject()!
 | 
			
		||||
        .forEach((k, d) => list[int.parse(k)] = getGroupFromAPI(d));
 | 
			
		||||
 | 
			
		||||
    return list;
 | 
			
		||||
@@ -77,7 +77,7 @@ class GroupsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Get a list of groups from the server. In case of error, this method throws
 | 
			
		||||
  /// an exception
 | 
			
		||||
  Future<GroupsList> getListOrThrow(Set<int> groups,
 | 
			
		||||
  Future<GroupsList> getListOrThrow(Set<int?> groups,
 | 
			
		||||
      {bool force = false}) async {
 | 
			
		||||
    final list = await getList(groups, force: force);
 | 
			
		||||
 | 
			
		||||
@@ -87,11 +87,11 @@ class GroupsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get a list of groups from the server
 | 
			
		||||
  Future<GroupsList> getList(Set<int> groups, {bool force = false}) async {
 | 
			
		||||
  Future<GroupsList?> getList(Set<int?> groups, {bool force = false}) async {
 | 
			
		||||
    final list = GroupsList();
 | 
			
		||||
 | 
			
		||||
    // Check which groups information to download
 | 
			
		||||
    final toDownload = Set<int>();
 | 
			
		||||
    final toDownload = Set<int?>();
 | 
			
		||||
    groups.forEach((groupID) {
 | 
			
		||||
      if (!force && _groupsListCache.containsKey(groupID))
 | 
			
		||||
        list[groupID] = _groupsListCache[groupID];
 | 
			
		||||
@@ -122,10 +122,10 @@ class GroupsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the list of groups of a user
 | 
			
		||||
  Future<Set<int>> getListUser() async =>
 | 
			
		||||
  Future<Set<int?>> getListUser() async =>
 | 
			
		||||
      (await APIRequest(uri: "groups/get_my_list", needLogin: true).exec())
 | 
			
		||||
          .assertOk()
 | 
			
		||||
          .getArray()
 | 
			
		||||
          .getArray()!
 | 
			
		||||
          .map((f) => cast<int>(f))
 | 
			
		||||
          .toSet();
 | 
			
		||||
 | 
			
		||||
@@ -142,7 +142,7 @@ class GroupsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Perform a simple membership request
 | 
			
		||||
  static Future<bool> _simpleMembershipRequest(int groupID, String uri,
 | 
			
		||||
          {Map<String, String> args}) async =>
 | 
			
		||||
          {Map<String, String>? args}) async =>
 | 
			
		||||
      (await (APIRequest.withLogin(uri)
 | 
			
		||||
                ..addInt("id", groupID)
 | 
			
		||||
                ..addArgs(args == null ? Map() : args))
 | 
			
		||||
@@ -176,7 +176,7 @@ class GroupsHelper {
 | 
			
		||||
          .isOK;
 | 
			
		||||
 | 
			
		||||
  /// Get advanced information about the user
 | 
			
		||||
  Future<GetAdvancedInfoResult> getAdvancedInfo(int groupID) async {
 | 
			
		||||
  Future<GetAdvancedInfoResult> getAdvancedInfo(int? groupID) async {
 | 
			
		||||
    // Get advanced information about the user
 | 
			
		||||
    final result =
 | 
			
		||||
        await (APIRequest(uri: "groups/get_advanced_info", needLogin: true)
 | 
			
		||||
@@ -189,7 +189,7 @@ class GroupsHelper {
 | 
			
		||||
 | 
			
		||||
      case 200:
 | 
			
		||||
        return GetAdvancedInfoResult(GetAdvancedInfoStatus.SUCCESS,
 | 
			
		||||
            _getAdvancedGroupInfoFromAPI(result.getObject()));
 | 
			
		||||
            _getAdvancedGroupInfoFromAPI(result.getObject()!));
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        throw Exception("Could not get advanced group information!");
 | 
			
		||||
@@ -202,7 +202,7 @@ class GroupsHelper {
 | 
			
		||||
  /// change in the future
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of error
 | 
			
		||||
  Future<AdvancedGroupInfo> getSettings(int groupID) async {
 | 
			
		||||
  Future<AdvancedGroupInfo?> getSettings(int groupID) async {
 | 
			
		||||
    final groupInfo = await getAdvancedInfo(groupID);
 | 
			
		||||
 | 
			
		||||
    if (groupInfo.status != GetAdvancedInfoStatus.SUCCESS)
 | 
			
		||||
@@ -239,7 +239,7 @@ class GroupsHelper {
 | 
			
		||||
            "posts_level",
 | 
			
		||||
            invertMap(
 | 
			
		||||
                _APIGroupsPostsCreationLevelsMap)[settings.postCreationLevel])
 | 
			
		||||
        .addBool("is_members_list_public", settings.isMembersListPublic)
 | 
			
		||||
        .addBool("is_members_list_public", settings.isMembersListPublic!)
 | 
			
		||||
        .addString("description", settings.description)
 | 
			
		||||
        .addString("url", settings.url)
 | 
			
		||||
        .execWithThrow();
 | 
			
		||||
@@ -248,7 +248,7 @@ class GroupsHelper {
 | 
			
		||||
  /// Upload a new logo
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> uploadNewLogo(int groupID, Uint8List bytes) async =>
 | 
			
		||||
  static Future<void> uploadNewLogo(int groupID, Uint8List? bytes) async =>
 | 
			
		||||
      await APIRequest(uri: "groups/upload_logo", needLogin: true)
 | 
			
		||||
          .addInt("id", groupID)
 | 
			
		||||
          .addBytesFile("logo", BytesFile("logo.png", bytes))
 | 
			
		||||
@@ -279,7 +279,7 @@ class GroupsHelper {
 | 
			
		||||
        ..addAll((await APIRequest(uri: "groups/get_members", needLogin: true)
 | 
			
		||||
                .addInt("id", groupID)
 | 
			
		||||
                .execWithThrow())
 | 
			
		||||
            .getArray()
 | 
			
		||||
            .getArray()!
 | 
			
		||||
            .map((f) => _apiToGroupMembership(f))
 | 
			
		||||
            .toList());
 | 
			
		||||
 | 
			
		||||
@@ -295,7 +295,7 @@ class GroupsHelper {
 | 
			
		||||
  /// Cancel a group membership invitation
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws an exception in case of failure
 | 
			
		||||
  static Future<void> cancelInvitation(int groupID, int userID) async =>
 | 
			
		||||
  static Future<void> cancelInvitation(int groupID, int? userID) async =>
 | 
			
		||||
      await APIRequest.withLogin("groups/cancel_invitation")
 | 
			
		||||
          .addInt("groupID", groupID)
 | 
			
		||||
          .addInt("userID", userID)
 | 
			
		||||
@@ -305,7 +305,7 @@ class GroupsHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws an exception in case of failure
 | 
			
		||||
  static Future<void> respondRequest(
 | 
			
		||||
          int groupID, int userID, bool accept) async =>
 | 
			
		||||
          int groupID, int? userID, bool accept) async =>
 | 
			
		||||
      await APIRequest.withLogin("groups/respond_request")
 | 
			
		||||
          .addInt("groupID", groupID)
 | 
			
		||||
          .addInt("userID", userID)
 | 
			
		||||
@@ -351,7 +351,7 @@ class GroupsHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> setConversationVisibility(
 | 
			
		||||
          int convID, GroupMembershipLevel newLevel) async =>
 | 
			
		||||
          int? convID, GroupMembershipLevel? newLevel) async =>
 | 
			
		||||
      await APIRequest.withLogin("groups/set_conversation_visibility")
 | 
			
		||||
          .addInt("conv_id", convID)
 | 
			
		||||
          .addString(
 | 
			
		||||
@@ -364,7 +364,7 @@ class GroupsHelper {
 | 
			
		||||
  /// Delete a group's conversation
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> deleteConversation(int convID) async =>
 | 
			
		||||
  static Future<void> deleteConversation(int? convID) async =>
 | 
			
		||||
      await APIRequest.withLogin("groups/delete_conversation")
 | 
			
		||||
          .addInt("conv_id", convID)
 | 
			
		||||
          .execWithThrow();
 | 
			
		||||
@@ -376,11 +376,11 @@ class GroupsHelper {
 | 
			
		||||
        name: map["name"],
 | 
			
		||||
        iconURL: map["icon_url"],
 | 
			
		||||
        numberMembers: map["number_members"],
 | 
			
		||||
        membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]],
 | 
			
		||||
        visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]],
 | 
			
		||||
        membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]]!,
 | 
			
		||||
        visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]]!,
 | 
			
		||||
        registrationLevel:
 | 
			
		||||
            _APIGroupsRegistrationLevelsMap[map["registration_level"]],
 | 
			
		||||
        postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]],
 | 
			
		||||
            _APIGroupsRegistrationLevelsMap[map["registration_level"]]!,
 | 
			
		||||
        postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]]!,
 | 
			
		||||
        virtualDirectory: nullToEmpty(map["virtual_directory"]),
 | 
			
		||||
        following: map["following"]);
 | 
			
		||||
  }
 | 
			
		||||
@@ -392,11 +392,11 @@ class GroupsHelper {
 | 
			
		||||
        name: map["name"],
 | 
			
		||||
        iconURL: map["icon_url"],
 | 
			
		||||
        numberMembers: map["number_members"],
 | 
			
		||||
        membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]],
 | 
			
		||||
        visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]],
 | 
			
		||||
        membershipLevel: APIGroupsMembershipLevelsMap[map["membership"]]!,
 | 
			
		||||
        visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]]!,
 | 
			
		||||
        registrationLevel:
 | 
			
		||||
            _APIGroupsRegistrationLevelsMap[map["registration_level"]],
 | 
			
		||||
        postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]],
 | 
			
		||||
            _APIGroupsRegistrationLevelsMap[map["registration_level"]]!,
 | 
			
		||||
        postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]]!,
 | 
			
		||||
        isMembersListPublic: map["is_members_list_public"],
 | 
			
		||||
        virtualDirectory: nullToEmpty(map["virtual_directory"]),
 | 
			
		||||
        following: map["following"],
 | 
			
		||||
@@ -418,6 +418,6 @@ class GroupsHelper {
 | 
			
		||||
        userID: row["user_id"],
 | 
			
		||||
        groupID: row["group_id"],
 | 
			
		||||
        timeCreate: row["time_create"],
 | 
			
		||||
        level: APIGroupsMembershipLevelsMap[row["level"]],
 | 
			
		||||
        level: APIGroupsMembershipLevelsMap[row["level"]]!,
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ class IndependentPushNotificationsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Configure independent push notification services with a pull URL
 | 
			
		||||
  static Future<void> configure(String wsURL) async {
 | 
			
		||||
  static Future<void> configure(String? wsURL) async {
 | 
			
		||||
    await platform.invokeMethod("configure", wsURL);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import 'package:comunic/enums/likes_type.dart';
 | 
			
		||||
import 'package:comunic/helpers/websocket_helper.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Likes helper
 | 
			
		||||
///
 | 
			
		||||
@@ -16,9 +15,9 @@ const LikesAPIMap = {
 | 
			
		||||
class LikesHelper {
 | 
			
		||||
  /// Update liking status of an element
 | 
			
		||||
  Future<void> setLiking({
 | 
			
		||||
    @required LikesType type,
 | 
			
		||||
    @required bool like,
 | 
			
		||||
    @required int id,
 | 
			
		||||
    required LikesType type,
 | 
			
		||||
    required bool like,
 | 
			
		||||
    required int id,
 | 
			
		||||
  }) async {
 | 
			
		||||
    return (await ws("likes/update", {
 | 
			
		||||
      "type": LikesAPIMap[type],
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ class NotificationsHelper {
 | 
			
		||||
        await APIRequest(uri: "notifications/count_all_news", needLogin: true)
 | 
			
		||||
            .exec();
 | 
			
		||||
 | 
			
		||||
    final content = response.assertOk().getObject();
 | 
			
		||||
    final content = response.assertOk().getObject()!;
 | 
			
		||||
 | 
			
		||||
    return CountUnreadNotifications(
 | 
			
		||||
      notifications: content["notifications"],
 | 
			
		||||
@@ -75,15 +75,15 @@ class NotificationsHelper {
 | 
			
		||||
    // Parse the list of notifications
 | 
			
		||||
    return NotificationsList()
 | 
			
		||||
      ..addAll(response
 | 
			
		||||
          .getArray()
 | 
			
		||||
          .getArray()!
 | 
			
		||||
          .map((f) => Notification(
 | 
			
		||||
              id: f["id"],
 | 
			
		||||
              timeCreate: f["time_create"],
 | 
			
		||||
              seen: f["seen"],
 | 
			
		||||
              fromUser: f["from_user_id"],
 | 
			
		||||
              onElemId: f["on_elem_id"],
 | 
			
		||||
              onElemType: _NotificationElementTypeAPImapping[f["on_elem_type"]],
 | 
			
		||||
              type: _NotificationsTypeAPImapping[f["type"]],
 | 
			
		||||
              onElemType: _NotificationElementTypeAPImapping[f["on_elem_type"]]!,
 | 
			
		||||
              type: _NotificationsTypeAPImapping[f["type"]]!,
 | 
			
		||||
              fromContainerId: f["from_container_id"],
 | 
			
		||||
              fromContainerType: f["from_container_type"] == ""
 | 
			
		||||
                  ? null
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ class PostsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Get the list of latest posts. Return the list of posts or null in case of
 | 
			
		||||
  /// failure
 | 
			
		||||
  Future<PostsList> getLatest({int from = 0}) async {
 | 
			
		||||
  Future<PostsList?> getLatest({int from = 0}) async {
 | 
			
		||||
    final response =
 | 
			
		||||
        await APIRequest(uri: "posts/get_latest", needLogin: true, args: {
 | 
			
		||||
      "include_groups": true.toString(),
 | 
			
		||||
@@ -66,7 +66,8 @@ class PostsHelper {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      // Parse & return the list of posts
 | 
			
		||||
      return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f)));
 | 
			
		||||
      return PostsList()
 | 
			
		||||
        ..addAll(response.getArray()!.map((f) => _apiToPost(f)));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print(e.toString());
 | 
			
		||||
      return null;
 | 
			
		||||
@@ -74,7 +75,7 @@ class PostsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the list of posts of a user
 | 
			
		||||
  Future<PostsList> getUserPosts(int userID, {int from = 0}) async {
 | 
			
		||||
  Future<PostsList?> getUserPosts(int? userID, {int from = 0}) async {
 | 
			
		||||
    final response = await (APIRequest(uri: "posts/get_user", needLogin: true)
 | 
			
		||||
          ..addInt("userID", userID)
 | 
			
		||||
          ..addInt("startFrom", from == 0 ? 0 : from - 1))
 | 
			
		||||
@@ -84,7 +85,8 @@ class PostsHelper {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      // Parse & return the list of posts
 | 
			
		||||
      return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f)));
 | 
			
		||||
      return PostsList()
 | 
			
		||||
        ..addAll(response.getArray()!.map((f) => _apiToPost(f)));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print(e.toString());
 | 
			
		||||
      return null;
 | 
			
		||||
@@ -92,7 +94,7 @@ class PostsHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the list of posts of a group
 | 
			
		||||
  Future<PostsList> getGroupPosts(int groupID, {int from = 0}) async {
 | 
			
		||||
  Future<PostsList?> getGroupPosts(int groupID, {int from = 0}) async {
 | 
			
		||||
    final response = await (APIRequest(uri: "posts/get_group", needLogin: true)
 | 
			
		||||
          ..addInt("groupID", groupID)
 | 
			
		||||
          ..addInt("startFrom", from == 0 ? 0 : from - 1))
 | 
			
		||||
@@ -102,7 +104,8 @@ class PostsHelper {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      // Parse & return the list of posts
 | 
			
		||||
      return PostsList()..addAll(response.getArray().map((f) => _apiToPost(f)));
 | 
			
		||||
      return PostsList()
 | 
			
		||||
        ..addAll(response.getArray()!.map((f) => _apiToPost(f)));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print(e.toString());
 | 
			
		||||
      return null;
 | 
			
		||||
@@ -120,7 +123,7 @@ class PostsHelper {
 | 
			
		||||
    if (!response.isOK)
 | 
			
		||||
      throw Exception("Could not get information about the post!");
 | 
			
		||||
 | 
			
		||||
    return _apiToPost(response.getObject());
 | 
			
		||||
    return _apiToPost(response.getObject()!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Create a new post
 | 
			
		||||
@@ -158,13 +161,14 @@ class PostsHelper {
 | 
			
		||||
 | 
			
		||||
      case PostKind.COUNTDOWN:
 | 
			
		||||
        request.addInt(
 | 
			
		||||
            "time-end", (post.timeEnd.millisecondsSinceEpoch / 1000).floor());
 | 
			
		||||
            "time-end", (post.timeEnd!.millisecondsSinceEpoch / 1000).floor());
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case PostKind.SURVEY:
 | 
			
		||||
        request.addString("question", post.survey.question);
 | 
			
		||||
        request.addString("answers", post.survey.answers.join("<>"));
 | 
			
		||||
        request.addBool("allowNewAnswers", post.survey.allowNewChoicesCreation);
 | 
			
		||||
        request.addString("question", post.survey!.question);
 | 
			
		||||
        request.addString("answers", post.survey!.answers.join("<>"));
 | 
			
		||||
        request.addBool(
 | 
			
		||||
            "allowNewAnswers", post.survey!.allowNewChoicesCreation);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case PostKind.YOUTUBE:
 | 
			
		||||
@@ -221,7 +225,7 @@ class PostsHelper {
 | 
			
		||||
  /// Register to a post events
 | 
			
		||||
  Future<void> registerPostEvents(int id) async {
 | 
			
		||||
    if (_registeredPosts.containsKey(id))
 | 
			
		||||
      _registeredPosts[id]++;
 | 
			
		||||
      _registeredPosts.update(id, (v) => v + 1);
 | 
			
		||||
    else {
 | 
			
		||||
      _registeredPosts[id] = 1;
 | 
			
		||||
      await ws("\$main/register_post", {"postID": id});
 | 
			
		||||
@@ -232,9 +236,9 @@ class PostsHelper {
 | 
			
		||||
  Future<void> unregisterPostEvents(int id) async {
 | 
			
		||||
    if (!_registeredPosts.containsKey(id)) return;
 | 
			
		||||
 | 
			
		||||
    _registeredPosts[id]--;
 | 
			
		||||
    _registeredPosts.update(id, (v) => v - 1);
 | 
			
		||||
 | 
			
		||||
    if (_registeredPosts[id] <= 0) {
 | 
			
		||||
    if (_registeredPosts[id]! <= 0) {
 | 
			
		||||
      _registeredPosts.remove(id);
 | 
			
		||||
      await ws("\$main/unregister_post", {"postID": id});
 | 
			
		||||
    }
 | 
			
		||||
@@ -242,14 +246,14 @@ class PostsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Turn an API entry into a [Post] object
 | 
			
		||||
  Post _apiToPost(Map<String, dynamic> map) {
 | 
			
		||||
    final postKind = _APIPostsKindsMap[map["kind"]];
 | 
			
		||||
    final postKind = _APIPostsKindsMap[map["kind"]]!;
 | 
			
		||||
 | 
			
		||||
    // Parse comments
 | 
			
		||||
    CommentsList comments;
 | 
			
		||||
    CommentsList? comments;
 | 
			
		||||
    if (map["comments"] != null) {
 | 
			
		||||
      comments = CommentsList();
 | 
			
		||||
      map["comments"]
 | 
			
		||||
          .forEach((v) => comments.add(CommentsHelper.apiToComment(v)));
 | 
			
		||||
          .forEach((v) => comments!.add(CommentsHelper.apiToComment(v)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final survey = postKind == PostKind.SURVEY
 | 
			
		||||
@@ -263,7 +267,7 @@ class PostsHelper {
 | 
			
		||||
        groupID: map["group_id"],
 | 
			
		||||
        timeSent: map["post_time"],
 | 
			
		||||
        content: DisplayedString(map["content"]),
 | 
			
		||||
        visibilityLevel: _APIPostsVisibilityLevelMap[map["visibility_level"]],
 | 
			
		||||
        visibilityLevel: _APIPostsVisibilityLevelMap[map["visibility_level"]]!,
 | 
			
		||||
        kind: postKind,
 | 
			
		||||
        fileSize: map["file_size"],
 | 
			
		||||
        fileType: map["file_type"],
 | 
			
		||||
@@ -276,7 +280,7 @@ class PostsHelper {
 | 
			
		||||
        linkImage: map["link_image"],
 | 
			
		||||
        likes: map["likes"],
 | 
			
		||||
        userLike: map["userlike"],
 | 
			
		||||
        access: _APIUserAccessMap[map["user_access"]],
 | 
			
		||||
        access: _APIUserAccessMap[map["user_access"]]!,
 | 
			
		||||
        comments: comments,
 | 
			
		||||
        survey: survey);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ const _PreferenceKeysName = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PreferencesHelper {
 | 
			
		||||
  static PreferencesHelper _instance;
 | 
			
		||||
  static PreferencesHelper? _instance;
 | 
			
		||||
 | 
			
		||||
  static Future<PreferencesHelper> getInstance() async {
 | 
			
		||||
    if (_instance == null) {
 | 
			
		||||
@@ -38,10 +38,10 @@ class PreferencesHelper {
 | 
			
		||||
      await _init();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return _instance;
 | 
			
		||||
    return _instance!;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static SharedPreferences _sharedPreferences;
 | 
			
		||||
  static late SharedPreferences _sharedPreferences;
 | 
			
		||||
 | 
			
		||||
  PreferencesHelper._();
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +50,7 @@ class PreferencesHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Set new login tokens
 | 
			
		||||
  Future<void> setLoginToken(String token) async {
 | 
			
		||||
  Future<void> setLoginToken(String? token) async {
 | 
			
		||||
    if (token != null)
 | 
			
		||||
      await setString(PreferencesKeyList.LOGIN_TOKEN, token);
 | 
			
		||||
    else
 | 
			
		||||
@@ -58,7 +58,7 @@ class PreferencesHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get current [LoginTokens]. Returns null if none or in case of failure
 | 
			
		||||
  String getLoginToken() {
 | 
			
		||||
  String? getLoginToken() {
 | 
			
		||||
    try {
 | 
			
		||||
      final string = getString(PreferencesKeyList.LOGIN_TOKEN);
 | 
			
		||||
      return string;
 | 
			
		||||
@@ -69,35 +69,35 @@ class PreferencesHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool containsKey(PreferencesKeyList key) {
 | 
			
		||||
    return _sharedPreferences.containsKey(_PreferenceKeysName[key]);
 | 
			
		||||
    return _sharedPreferences.containsKey(_PreferenceKeysName[key]!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<bool> removeKey(PreferencesKeyList key) async {
 | 
			
		||||
    return await _sharedPreferences.remove(_PreferenceKeysName[key]);
 | 
			
		||||
    return await _sharedPreferences.remove(_PreferenceKeysName[key]!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<bool> setString(PreferencesKeyList key, String value) async {
 | 
			
		||||
    return await _sharedPreferences.setString(_PreferenceKeysName[key], value);
 | 
			
		||||
    return await _sharedPreferences.setString(_PreferenceKeysName[key]!, value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String getString(PreferencesKeyList key) {
 | 
			
		||||
    return _sharedPreferences.getString(_PreferenceKeysName[key]);
 | 
			
		||||
  String? getString(PreferencesKeyList key) {
 | 
			
		||||
    return _sharedPreferences.getString(_PreferenceKeysName[key]!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<bool> setBool(PreferencesKeyList key, bool value) async {
 | 
			
		||||
    return await _sharedPreferences.setBool(_PreferenceKeysName[key], value);
 | 
			
		||||
    return await _sharedPreferences.setBool(_PreferenceKeysName[key]!, value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<bool> setInt(PreferencesKeyList key, int value) async {
 | 
			
		||||
    return await _sharedPreferences.setInt(_PreferenceKeysName[key], value);
 | 
			
		||||
    return await _sharedPreferences.setInt(_PreferenceKeysName[key]!, value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int getInt(PreferencesKeyList key) {
 | 
			
		||||
    return _sharedPreferences.getInt(_PreferenceKeysName[key]);
 | 
			
		||||
  int? getInt(PreferencesKeyList key) {
 | 
			
		||||
    return _sharedPreferences.getInt(_PreferenceKeysName[key]!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool getBool(PreferencesKeyList key, {bool alternative = false}) {
 | 
			
		||||
    final v = _sharedPreferences.getBool(_PreferenceKeysName[key]);
 | 
			
		||||
    final v = _sharedPreferences.getBool(_PreferenceKeysName[key]!);
 | 
			
		||||
    return v == null ? alternative : v;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -115,7 +115,7 @@ class PreferencesHelper {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PreferencesHelper preferences() {
 | 
			
		||||
PreferencesHelper? preferences() {
 | 
			
		||||
  if (PreferencesHelper._instance == null)
 | 
			
		||||
    throw Exception("Try to get preference before their initialization!");
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,14 +21,14 @@ const _PushNotificationsAPIMap = {
 | 
			
		||||
 | 
			
		||||
class PushNotificationsHelper {
 | 
			
		||||
  /// Get cached status of push notifications
 | 
			
		||||
  static Future<PushNotificationsStatus> getLocalStatus() async {
 | 
			
		||||
  static Future<PushNotificationsStatus?> getLocalStatus() async {
 | 
			
		||||
    final pref = await PreferencesHelper.getInstance();
 | 
			
		||||
 | 
			
		||||
    if (!pref.containsKey(PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS))
 | 
			
		||||
      return PushNotificationsStatus.UNDEFINED;
 | 
			
		||||
 | 
			
		||||
    return _PushNotificationsAPIMap[
 | 
			
		||||
        pref.getString(PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS)];
 | 
			
		||||
        pref.getString(PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS)!];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Refresh local status with information from server
 | 
			
		||||
@@ -47,13 +47,13 @@ class PushNotificationsHelper {
 | 
			
		||||
          response["independent_push_url"]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await (await PreferencesHelper.getInstance()).setString(
 | 
			
		||||
    await (await PreferencesHelper.getInstance())!.setString(
 | 
			
		||||
        PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS, response["status"]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Clear local push notifications status
 | 
			
		||||
  static Future<void> clearLocalStatus() async {
 | 
			
		||||
    await (await PreferencesHelper.getInstance())
 | 
			
		||||
    await (await PreferencesHelper.getInstance())!
 | 
			
		||||
        .removeKey(PreferencesKeyList.PUSH_NOTIFICATIONS_STATUS);
 | 
			
		||||
 | 
			
		||||
    // Stop local refresh notification refresh
 | 
			
		||||
@@ -62,8 +62,8 @@ class PushNotificationsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Configure push notifications
 | 
			
		||||
  static Future<void> configure(
 | 
			
		||||
      BuildContext context, PushNotificationsStatus newStatus) async {
 | 
			
		||||
    String firebaseToken = "";
 | 
			
		||||
      BuildContext context, PushNotificationsStatus? newStatus) async {
 | 
			
		||||
    String? firebaseToken = "";
 | 
			
		||||
    switch (newStatus) {
 | 
			
		||||
      case PushNotificationsStatus.DISABLED:
 | 
			
		||||
        break;
 | 
			
		||||
@@ -90,8 +90,8 @@ class PushNotificationsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Set new push notification status on the server
 | 
			
		||||
  static Future<void> setNewStatus(
 | 
			
		||||
    PushNotificationsStatus newStatus, {
 | 
			
		||||
    String firebaseToken = "",
 | 
			
		||||
    PushNotificationsStatus? newStatus, {
 | 
			
		||||
    String? firebaseToken = "",
 | 
			
		||||
  }) async =>
 | 
			
		||||
      await APIRequest.withLogin("push_notifications/configure")
 | 
			
		||||
          .addString(
 | 
			
		||||
@@ -104,6 +104,6 @@ class PushNotificationsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Is true if possible if push notifications are configurable
 | 
			
		||||
  static bool get arePushNotificationsAvailable =>
 | 
			
		||||
      srvConfig.notificationsPolicy.hasFirebase ||
 | 
			
		||||
      (isAndroid && srvConfig.notificationsPolicy.hasIndependent);
 | 
			
		||||
      srvConfig!.notificationsPolicy.hasFirebase ||
 | 
			
		||||
      (isAndroid && srvConfig!.notificationsPolicy.hasIndependent);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ class SearchHelper {
 | 
			
		||||
  /// Search for user. This method returns information about the target users
 | 
			
		||||
  ///
 | 
			
		||||
  /// Returns information about the target users or null if an error occurred
 | 
			
		||||
  Future<UsersList> searchUser(String query) async {
 | 
			
		||||
  Future<UsersList?> searchUser(String query) async {
 | 
			
		||||
    // Execute the query on the server
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
        uri: "user/search", needLogin: true, args: {"query": query}).exec();
 | 
			
		||||
@@ -21,7 +21,7 @@ class SearchHelper {
 | 
			
		||||
    if (response.code != 200) return null;
 | 
			
		||||
 | 
			
		||||
    return await UsersHelper()
 | 
			
		||||
        .getUsersInfo(response.getArray().map((f) => cast<int>(f)).toList());
 | 
			
		||||
        .getUsersInfo(response.getArray()!.map((f) => cast<int>(f)).toList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Perform a global search
 | 
			
		||||
@@ -31,7 +31,7 @@ class SearchHelper {
 | 
			
		||||
 | 
			
		||||
    result.assertOk();
 | 
			
		||||
 | 
			
		||||
    return SearchResultsList()..addAll(result.getArray().map((f) {
 | 
			
		||||
    return SearchResultsList()..addAll(result.getArray()!.map((f) {
 | 
			
		||||
      switch (f["kind"]) {
 | 
			
		||||
        case "user":
 | 
			
		||||
          return SearchResult(id: f["id"], kind: SearchResultKind.USER);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ abstract class SerializableElement<T> extends Comparable<T> {
 | 
			
		||||
 | 
			
		||||
abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
  /// List cache
 | 
			
		||||
  List<T> _cache;
 | 
			
		||||
  List<T>? _cache;
 | 
			
		||||
 | 
			
		||||
  /// The name of the type of data to serialise
 | 
			
		||||
  String get type;
 | 
			
		||||
@@ -48,12 +48,15 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
    try {
 | 
			
		||||
      final file = await _getFilePath();
 | 
			
		||||
 | 
			
		||||
      if (!await file.exists()) return _cache = [];
 | 
			
		||||
      if (!await file.exists()) {
 | 
			
		||||
        _cache = [];
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      final List<dynamic> json = jsonDecode(await file.readAsString());
 | 
			
		||||
      _cache = json.cast<Map<String, dynamic>>().map(parse).toList();
 | 
			
		||||
 | 
			
		||||
      _cache.sort();
 | 
			
		||||
      _cache!.sort();
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      print("Failed to read serialized data!");
 | 
			
		||||
@@ -67,8 +70,10 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      final file = await _getFilePath();
 | 
			
		||||
      await file.writeAsString(jsonEncode(
 | 
			
		||||
          _cache.map((e) => e.toJson()).toList().cast<Map<String, dynamic>>()));
 | 
			
		||||
      await file.writeAsString(jsonEncode(_cache!
 | 
			
		||||
          .map((e) => e.toJson())
 | 
			
		||||
          .toList()
 | 
			
		||||
          .cast<Map<String, dynamic>>()));
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Failed to write file!");
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
@@ -78,7 +83,7 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
  /// Get the current list of elements
 | 
			
		||||
  Future<List<T>> getList() async {
 | 
			
		||||
    await _loadCache();
 | 
			
		||||
    return List.from(_cache);
 | 
			
		||||
    return List.from(_cache!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Set a new list of conversations
 | 
			
		||||
@@ -90,23 +95,23 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
  /// Insert new element
 | 
			
		||||
  Future<void> insert(T el) async {
 | 
			
		||||
    await _loadCache();
 | 
			
		||||
    _cache.add(el);
 | 
			
		||||
    _cache.sort();
 | 
			
		||||
    _cache!.add(el);
 | 
			
		||||
    _cache!.sort();
 | 
			
		||||
    await _saveCache();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Insert new element
 | 
			
		||||
  Future<void> insertMany(List<T> els) async {
 | 
			
		||||
    await _loadCache();
 | 
			
		||||
    _cache.addAll(els);
 | 
			
		||||
    _cache.sort();
 | 
			
		||||
    _cache!.addAll(els);
 | 
			
		||||
    _cache!.sort();
 | 
			
		||||
    await _saveCache();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Check if any entry in the last match the predicate
 | 
			
		||||
  Future<bool> any(bool isContained(T t)) async {
 | 
			
		||||
    await _loadCache();
 | 
			
		||||
    return _cache.any((element) => isContained(element));
 | 
			
		||||
    return _cache!.any((element) => isContained(element));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<bool> has(T el) => any((t) => t == el);
 | 
			
		||||
@@ -114,7 +119,7 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
  /// Check if any entry in the last match the predicate
 | 
			
		||||
  Future<T> first(bool filter(T t)) async {
 | 
			
		||||
    await _loadCache();
 | 
			
		||||
    return _cache.firstWhere((element) => filter(element));
 | 
			
		||||
    return _cache!.firstWhere((element) => filter(element));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Replace an element with another one
 | 
			
		||||
@@ -122,10 +127,10 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
    await _loadCache();
 | 
			
		||||
 | 
			
		||||
    // Insert or replace the element
 | 
			
		||||
    _cache = _cache.where((element) => !isToReplace(element)).toList();
 | 
			
		||||
    _cache.add(newEl);
 | 
			
		||||
    _cache = _cache!.where((element) => !isToReplace(element)).toList();
 | 
			
		||||
    _cache!.add(newEl);
 | 
			
		||||
 | 
			
		||||
    _cache.sort();
 | 
			
		||||
    _cache!.sort();
 | 
			
		||||
    await _saveCache();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -133,8 +138,8 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
  Future<void> insertOrReplaceElements(List<T> list) async {
 | 
			
		||||
    await _loadCache();
 | 
			
		||||
 | 
			
		||||
    _cache.removeWhere((element) => list.any((newEl) => element == newEl));
 | 
			
		||||
    _cache.addAll(list);
 | 
			
		||||
    _cache!.removeWhere((element) => list.any((newEl) => element == newEl));
 | 
			
		||||
    _cache!.addAll(list);
 | 
			
		||||
 | 
			
		||||
    await _saveCache();
 | 
			
		||||
  }
 | 
			
		||||
@@ -142,7 +147,7 @@ abstract class BaseSerializationHelper<T extends SerializableElement> {
 | 
			
		||||
  /// Remove elements
 | 
			
		||||
  Future<void> removeElement(bool isToRemove(T t)) async {
 | 
			
		||||
    await _loadCache();
 | 
			
		||||
    _cache.removeWhere((element) => isToRemove(element));
 | 
			
		||||
    _cache!.removeWhere((element) => isToRemove(element));
 | 
			
		||||
    await _saveCache();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import 'package:comunic/models/conversation_message.dart';
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
HashMap<int, ConversationsMessagesSerializationHelper> _instances;
 | 
			
		||||
HashMap<int?, ConversationsMessagesSerializationHelper>? _instances;
 | 
			
		||||
 | 
			
		||||
class ConversationsMessagesSerializationHelper
 | 
			
		||||
    extends BaseSerializationHelper<ConversationMessage> {
 | 
			
		||||
@@ -18,13 +18,13 @@ class ConversationsMessagesSerializationHelper
 | 
			
		||||
      : convID = convID,
 | 
			
		||||
        assert(convID != null);
 | 
			
		||||
 | 
			
		||||
  factory ConversationsMessagesSerializationHelper(int convID) {
 | 
			
		||||
  factory ConversationsMessagesSerializationHelper(int? convID) {
 | 
			
		||||
    if (_instances == null) _instances = HashMap();
 | 
			
		||||
 | 
			
		||||
    if (!_instances.containsKey(convID))
 | 
			
		||||
      _instances[convID] = ConversationsMessagesSerializationHelper._(convID);
 | 
			
		||||
    if (!_instances!.containsKey(convID))
 | 
			
		||||
      _instances![convID] = ConversationsMessagesSerializationHelper._(convID!);
 | 
			
		||||
 | 
			
		||||
    return _instances[convID];
 | 
			
		||||
    return _instances![convID]!;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
 
 | 
			
		||||
@@ -28,5 +28,5 @@ class ConversationsSerializationHelper
 | 
			
		||||
      ConversationsList()..addAll(await super.getList());
 | 
			
		||||
 | 
			
		||||
  /// Get a conversation
 | 
			
		||||
  Future<Conversation> get(int id) => first((t) => t.id == id);
 | 
			
		||||
  Future<Conversation> get(int? id) => first((t) => t.id == id);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import 'package:comunic/models/user.dart';
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
UsersListSerialisationHelper _singleton;
 | 
			
		||||
UsersListSerialisationHelper? _singleton;
 | 
			
		||||
 | 
			
		||||
class UsersListSerialisationHelper extends BaseSerializationHelper<User> {
 | 
			
		||||
  UsersListSerialisationHelper._();
 | 
			
		||||
@@ -13,7 +13,7 @@ class UsersListSerialisationHelper extends BaseSerializationHelper<User> {
 | 
			
		||||
  factory UsersListSerialisationHelper() {
 | 
			
		||||
    if (_singleton == null) _singleton = UsersListSerialisationHelper._();
 | 
			
		||||
 | 
			
		||||
    return _singleton;
 | 
			
		||||
    return _singleton!;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -23,6 +23,6 @@ class UsersListSerialisationHelper extends BaseSerializationHelper<User> {
 | 
			
		||||
  User parse(Map<String, dynamic> m) => User.fromJson(m);
 | 
			
		||||
 | 
			
		||||
  /// Remove a user by its ID
 | 
			
		||||
  Future<void> removeUserByID(int userID) =>
 | 
			
		||||
  Future<void> removeUserByID(int? userID) =>
 | 
			
		||||
      removeElement((t) => t.id == userID);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import 'package:version/version.dart';
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class ServerConfigurationHelper {
 | 
			
		||||
  static ServerConfig _config;
 | 
			
		||||
  static ServerConfig? _config;
 | 
			
		||||
 | 
			
		||||
  /// Make sure the configuration has been correctly loaded
 | 
			
		||||
  static Future<void> ensureLoaded() async {
 | 
			
		||||
@@ -15,7 +15,7 @@ class ServerConfigurationHelper {
 | 
			
		||||
 | 
			
		||||
    final response =
 | 
			
		||||
        (await APIRequest.withoutLogin("server/config").execWithThrow())
 | 
			
		||||
            .getObject();
 | 
			
		||||
            .getObject()!;
 | 
			
		||||
 | 
			
		||||
    final banner = response["banner"];
 | 
			
		||||
    final pushNotificationsPolicy = response["push_notifications"];
 | 
			
		||||
@@ -97,7 +97,7 @@ class ServerConfigurationHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get current server configuration, throwing if it is not loaded yet
 | 
			
		||||
  static ServerConfig get config {
 | 
			
		||||
  static ServerConfig? get config {
 | 
			
		||||
    if (_config == null)
 | 
			
		||||
      throw Exception(
 | 
			
		||||
          "Trying to access server configuration but it is not loaded yet!");
 | 
			
		||||
@@ -107,6 +107,6 @@ class ServerConfigurationHelper {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Shortcut for server configuration
 | 
			
		||||
ServerConfig get srvConfig => ServerConfigurationHelper.config;
 | 
			
		||||
ServerConfig? get srvConfig => ServerConfigurationHelper.config;
 | 
			
		||||
 | 
			
		||||
bool get showBanner => srvConfig.banner != null && srvConfig.banner.visible;
 | 
			
		||||
bool get showBanner => srvConfig!.banner != null && srvConfig!.banner!.visible;
 | 
			
		||||
@@ -25,7 +25,7 @@ class SettingsHelper {
 | 
			
		||||
    final response =
 | 
			
		||||
        (await APIRequest(uri: "settings/get_general", needLogin: true).exec())
 | 
			
		||||
            .assertOk()
 | 
			
		||||
            .getObject();
 | 
			
		||||
            .getObject()!;
 | 
			
		||||
 | 
			
		||||
    return GeneralSettings(
 | 
			
		||||
      email: response["email"],
 | 
			
		||||
@@ -88,13 +88,13 @@ class SettingsHelper {
 | 
			
		||||
        (await APIRequest(uri: "settings/get_account_image", needLogin: true)
 | 
			
		||||
                .exec())
 | 
			
		||||
            .assertOk()
 | 
			
		||||
            .getObject();
 | 
			
		||||
            .getObject()!;
 | 
			
		||||
 | 
			
		||||
    return AccountImageSettings(
 | 
			
		||||
        hasImage: response["has_image"],
 | 
			
		||||
        imageURL: response["image_url"],
 | 
			
		||||
        visibility:
 | 
			
		||||
            _APIAccountImageVisibilityAPILevels[response["visibility"]]);
 | 
			
		||||
            _APIAccountImageVisibilityAPILevels[response["visibility"]]!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Upload a new account image
 | 
			
		||||
@@ -143,7 +143,7 @@ class SettingsHelper {
 | 
			
		||||
  /// Delete a custom emoji
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> deleteCustomEmoji(int emojiID) async =>
 | 
			
		||||
  static Future<void> deleteCustomEmoji(int? emojiID) async =>
 | 
			
		||||
      (await APIRequest(uri: "settings/delete_custom_emoji", needLogin: true)
 | 
			
		||||
              .addInt("emojiID", emojiID)
 | 
			
		||||
              .exec())
 | 
			
		||||
@@ -175,7 +175,7 @@ class SettingsHelper {
 | 
			
		||||
        (await APIRequest(uri: "settings/get_security", needLogin: true)
 | 
			
		||||
                .addString("password", password)
 | 
			
		||||
                .execWithThrow())
 | 
			
		||||
            .getObject();
 | 
			
		||||
            .getObject()!;
 | 
			
		||||
 | 
			
		||||
    return SecuritySettings(
 | 
			
		||||
      securityQuestion1: response["security_question_1"],
 | 
			
		||||
@@ -207,7 +207,7 @@ class SettingsHelper {
 | 
			
		||||
    final response =
 | 
			
		||||
        (await APIRequest.withLogin("settings/get_data_conservation_policy")
 | 
			
		||||
                .execWithThrow())
 | 
			
		||||
            .getObject();
 | 
			
		||||
            .getObject()!;
 | 
			
		||||
 | 
			
		||||
    return DataConservationPolicySettings(
 | 
			
		||||
        inactiveAccountLifeTime: response["inactive_account_lifetime"],
 | 
			
		||||
@@ -223,7 +223,7 @@ class SettingsHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  static Future<void> setDataConservationPolicy(
 | 
			
		||||
      String password, DataConservationPolicySettings newSettings) async {
 | 
			
		||||
      String? password, DataConservationPolicySettings newSettings) async {
 | 
			
		||||
    await APIRequest(
 | 
			
		||||
            uri: "settings/set_data_conservation_policy", needLogin: true)
 | 
			
		||||
        .addString("password", password)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import 'package:comunic/models/api_request.dart';
 | 
			
		||||
import 'package:comunic/models/survey.dart';
 | 
			
		||||
import 'package:comunic/models/survey_choice.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Survey helper
 | 
			
		||||
///
 | 
			
		||||
@@ -13,7 +12,7 @@ class SurveyHelper {
 | 
			
		||||
      apiToSurvey((await APIRequest.withLogin("surveys/get_info")
 | 
			
		||||
              .addInt("postID", postID)
 | 
			
		||||
              .execWithThrow())
 | 
			
		||||
          .getObject());
 | 
			
		||||
          .getObject()!);
 | 
			
		||||
 | 
			
		||||
  /// Cancel the response of a user to a survey
 | 
			
		||||
  Future<bool> cancelResponse(Survey survey) async {
 | 
			
		||||
@@ -26,7 +25,7 @@ class SurveyHelper {
 | 
			
		||||
 | 
			
		||||
  /// Send the response of a user to a survey
 | 
			
		||||
  Future<bool> respondToSurvey(
 | 
			
		||||
      {@required Survey survey, @required SurveyChoice choice}) async {
 | 
			
		||||
      {required Survey survey, required SurveyChoice choice}) async {
 | 
			
		||||
    assert(survey != null);
 | 
			
		||||
    assert(choice != null);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ class UsersHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// Return the list of users information in case of success, null in case of
 | 
			
		||||
  /// failure
 | 
			
		||||
  Future<UsersList> _downloadInfo(List<int> users) async {
 | 
			
		||||
  Future<UsersList?> _downloadInfo(List<int?> users) async {
 | 
			
		||||
    // Execute the request
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
        uri: "user/getInfoMultiple",
 | 
			
		||||
@@ -42,7 +42,7 @@ class UsersHelper {
 | 
			
		||||
    if (response.code != 200) return null;
 | 
			
		||||
 | 
			
		||||
    final list = UsersList();
 | 
			
		||||
    response.getObject().forEach(
 | 
			
		||||
    response.getObject()!.forEach(
 | 
			
		||||
          (k, v) => list.add(
 | 
			
		||||
            User(
 | 
			
		||||
              id: v["userID"],
 | 
			
		||||
@@ -69,7 +69,7 @@ class UsersHelper {
 | 
			
		||||
 | 
			
		||||
  /// Get users information from a given [Set]. Throws an exception in case
 | 
			
		||||
  /// of failure
 | 
			
		||||
  Future<UsersList> getListWithThrow(Set<int> users,
 | 
			
		||||
  Future<UsersList> getListWithThrow(Set<int?> users,
 | 
			
		||||
      {bool forceDownload = false}) async {
 | 
			
		||||
    final list =
 | 
			
		||||
        await getUsersInfo(users.toList(), forceDownload: forceDownload);
 | 
			
		||||
@@ -82,16 +82,16 @@ class UsersHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get information about a single user. Throws in case of failure
 | 
			
		||||
  Future<User> getSingleWithThrow(int user,
 | 
			
		||||
  Future<User> getSingleWithThrow(int? user,
 | 
			
		||||
      {bool forceDownload = false}) async {
 | 
			
		||||
    return (await getListWithThrow(Set<int>()..add(user),
 | 
			
		||||
    return (await getListWithThrow(Set<int?>()..add(user),
 | 
			
		||||
        forceDownload: forceDownload))[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get users information from a given [Set]
 | 
			
		||||
  ///
 | 
			
		||||
  /// Throws in case of failure
 | 
			
		||||
  Future<UsersList> getList(Set<int> users,
 | 
			
		||||
  Future<UsersList> getList(Set<int?> users,
 | 
			
		||||
      {bool forceDownload = false}) async {
 | 
			
		||||
    final list = await getUsersInfo(users.toList());
 | 
			
		||||
 | 
			
		||||
@@ -104,13 +104,13 @@ class UsersHelper {
 | 
			
		||||
  ///
 | 
			
		||||
  /// If [forceDownload] is set to true, the data will always be retrieved from
 | 
			
		||||
  /// the server, otherwise cached data will be used if available
 | 
			
		||||
  Future<UsersList> getUsersInfo(List<int> users,
 | 
			
		||||
  Future<UsersList?> getUsersInfo(List<int?> users,
 | 
			
		||||
      {bool forceDownload = false}) async {
 | 
			
		||||
    List<int> toDownload = [];
 | 
			
		||||
    List<int?> toDownload = [];
 | 
			
		||||
    UsersList list = UsersList();
 | 
			
		||||
 | 
			
		||||
    // Check cache
 | 
			
		||||
    for (int userID in users) {
 | 
			
		||||
    for (int? userID in users) {
 | 
			
		||||
      if (!forceDownload &&
 | 
			
		||||
          await UsersListSerialisationHelper().any((u) => u.id == userID))
 | 
			
		||||
        list.add(
 | 
			
		||||
@@ -151,7 +151,7 @@ class UsersHelper {
 | 
			
		||||
      throw new GetUserAdvancedUserError(cause);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return apiToAdvancedUserInfo(response.getObject());
 | 
			
		||||
    return apiToAdvancedUserInfo(response.getObject()!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Parse the list of custom emojies
 | 
			
		||||
 
 | 
			
		||||
@@ -7,15 +7,15 @@ import 'package:version/version.dart';
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class VersionHelper {
 | 
			
		||||
  static PackageInfo _info;
 | 
			
		||||
  static PackageInfo? _info;
 | 
			
		||||
 | 
			
		||||
  static Future<void> ensureLoaded() async {
 | 
			
		||||
    if (!isWeb) _info = await PackageInfo.fromPlatform();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get current version information
 | 
			
		||||
  static PackageInfo get info => _info;
 | 
			
		||||
  static PackageInfo? get info => _info;
 | 
			
		||||
 | 
			
		||||
  /// Get current application version, in parsed format
 | 
			
		||||
  static Version get version => Version.parse(info.version);
 | 
			
		||||
  static Version get version => Version.parse(info!.version);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import 'package:comunic/models/api_request.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Virtual directory helper
 | 
			
		||||
///
 | 
			
		||||
@@ -9,10 +8,10 @@ enum VirtualDirectoryType { USER, GROUP, NONE }
 | 
			
		||||
 | 
			
		||||
class VirtualDirectoryResult {
 | 
			
		||||
  final VirtualDirectoryType type;
 | 
			
		||||
  final int id;
 | 
			
		||||
  final int? id;
 | 
			
		||||
 | 
			
		||||
  const VirtualDirectoryResult({
 | 
			
		||||
    @required this.type,
 | 
			
		||||
    required this.type,
 | 
			
		||||
    this.id,
 | 
			
		||||
  }) : assert(type != null);
 | 
			
		||||
}
 | 
			
		||||
@@ -30,8 +29,8 @@ class VirtualDirectoryHelper {
 | 
			
		||||
        return VirtualDirectoryResult(type: VirtualDirectoryType.NONE);
 | 
			
		||||
 | 
			
		||||
      case 200:
 | 
			
		||||
        final id = response.getObject()["id"];
 | 
			
		||||
        final kind = response.getObject()["kind"];
 | 
			
		||||
        final id = response.getObject()!["id"];
 | 
			
		||||
        final kind = response.getObject()!["kind"];
 | 
			
		||||
        switch (kind) {
 | 
			
		||||
          case "user":
 | 
			
		||||
            return VirtualDirectoryResult(
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ class WebAppHelper {
 | 
			
		||||
  static Future<MembershipList> getMemberships() async {
 | 
			
		||||
    final response =
 | 
			
		||||
        (await APIRequest.withLogin("webApp/getMemberships").execWithThrow())
 | 
			
		||||
            .getArray();
 | 
			
		||||
            .getArray()!;
 | 
			
		||||
 | 
			
		||||
    return MembershipList()
 | 
			
		||||
      ..addAll(response
 | 
			
		||||
@@ -26,7 +26,7 @@ class WebAppHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Turn an API entry into a membership entry
 | 
			
		||||
  static Membership _apiToMembership(Map<String, dynamic> entry) {
 | 
			
		||||
  static Membership? _apiToMembership(Map<String, dynamic> entry) {
 | 
			
		||||
    switch (entry["type"]) {
 | 
			
		||||
      case "conversation":
 | 
			
		||||
        return Membership.conversation(
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ import 'package:web_socket_channel/web_socket_channel.dart';
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class WebSocketHelper {
 | 
			
		||||
  static WebSocketChannel _ws;
 | 
			
		||||
  static WebSocketChannel? _ws;
 | 
			
		||||
 | 
			
		||||
  static int _counter = 0;
 | 
			
		||||
 | 
			
		||||
@@ -23,14 +23,14 @@ class WebSocketHelper {
 | 
			
		||||
 | 
			
		||||
  /// Check out whether we are currently connected to WebSocket or not
 | 
			
		||||
  static bool isConnected() {
 | 
			
		||||
    return _ws != null && _ws.closeCode == null;
 | 
			
		||||
    return _ws != null && _ws!.closeCode == null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get WebSocket access token
 | 
			
		||||
  static Future<String> _getWsToken() async =>
 | 
			
		||||
  static Future<String?> _getWsToken() async =>
 | 
			
		||||
      (await APIRequest(uri: "ws/token", needLogin: true).exec())
 | 
			
		||||
          .assertOk()
 | 
			
		||||
          .getObject()["token"];
 | 
			
		||||
          .getObject()!["token"];
 | 
			
		||||
 | 
			
		||||
  /// Connect to WebSocket
 | 
			
		||||
  static connect() async {
 | 
			
		||||
@@ -47,7 +47,7 @@ class WebSocketHelper {
 | 
			
		||||
    // Connect
 | 
			
		||||
    _ws = WebSocketChannel.connect(wsURI);
 | 
			
		||||
 | 
			
		||||
    _ws.stream.listen(
 | 
			
		||||
    _ws!.stream.listen(
 | 
			
		||||
      // When we got data
 | 
			
		||||
      (data) {
 | 
			
		||||
        print("WS New data: $data");
 | 
			
		||||
@@ -75,7 +75,7 @@ class WebSocketHelper {
 | 
			
		||||
 | 
			
		||||
  /// Close current WebSocket (if any)
 | 
			
		||||
  static close() {
 | 
			
		||||
    if (isConnected()) _ws.sink.close();
 | 
			
		||||
    if (isConnected()) _ws!.sink.close();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Send a new message
 | 
			
		||||
@@ -93,7 +93,7 @@ class WebSocketHelper {
 | 
			
		||||
 | 
			
		||||
    print("WS Send message: $msg");
 | 
			
		||||
 | 
			
		||||
    _ws.sink.add(msg);
 | 
			
		||||
    _ws!.sink.add(msg);
 | 
			
		||||
 | 
			
		||||
    _requests[id] = completer;
 | 
			
		||||
    return completer.future;
 | 
			
		||||
@@ -240,11 +240,11 @@ class WebSocketHelper {
 | 
			
		||||
 | 
			
		||||
    // Handles errors
 | 
			
		||||
    if (msg.title != "success") {
 | 
			
		||||
      completer.completeError(Exception("Could not process request!"));
 | 
			
		||||
      completer!.completeError(Exception("Could not process request!"));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    completer.complete(msg.data);
 | 
			
		||||
    completer!.complete(msg.data);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,4 +16,7 @@ class AbstractList<E> extends ListBase<E> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void operator []=(int index, E value) => _list[index] = value;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void add(E element) => _list.add(element);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ class BaseSet<T> extends SetBase<T> {
 | 
			
		||||
  bool add(T value) => _set.add(value);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool contains(Object element) => _set.contains(element);
 | 
			
		||||
  bool contains(Object? element) => _set.contains(element);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Iterator<T> get iterator => _set.iterator;
 | 
			
		||||
@@ -20,10 +20,10 @@ class BaseSet<T> extends SetBase<T> {
 | 
			
		||||
  int get length => _set.length;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  T lookup(Object element) => _set.lookup(element);
 | 
			
		||||
  T? lookup(Object? element) => _set.lookup(element);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool remove(Object value) => _set.remove(value);
 | 
			
		||||
  bool remove(Object? value) => _set.remove(value);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Set<T> toSet() => _set.toSet();
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,10 @@ class CallMembersList extends AbstractList<CallMember> {
 | 
			
		||||
  Set<int> get usersID => this.map((f) => f.userID).toSet();
 | 
			
		||||
 | 
			
		||||
  /// Remove a specific member from this list
 | 
			
		||||
  void removeUser(int userID) => this.removeWhere((f) => f.userID == userID);
 | 
			
		||||
  void removeUser(int? userID) => this.removeWhere((f) => f.userID == userID);
 | 
			
		||||
 | 
			
		||||
  /// Get the connection of a specific user
 | 
			
		||||
  CallMember getUser(int userID) => this.firstWhere((f) => f.userID == userID);
 | 
			
		||||
  CallMember getUser(int? userID) => this.firstWhere((f) => f.userID == userID);
 | 
			
		||||
 | 
			
		||||
  /// Extract ready peers from this list
 | 
			
		||||
  CallMembersList get readyPeers =>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,14 @@
 | 
			
		||||
import 'dart:collection';
 | 
			
		||||
 | 
			
		||||
import 'package:comunic/lists/abstract_list.dart';
 | 
			
		||||
import 'package:comunic/models/conversation_message.dart';
 | 
			
		||||
 | 
			
		||||
/// Conversations messages list
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class ConversationMessagesList extends ListBase<ConversationMessage> {
 | 
			
		||||
  final List<ConversationMessage> _list = [];
 | 
			
		||||
 | 
			
		||||
  set length(int v) => _list.length = v;
 | 
			
		||||
 | 
			
		||||
  int get length => _list.length;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ConversationMessage operator [](int index) {
 | 
			
		||||
    return _list[index];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void operator []=(int index, ConversationMessage value) {
 | 
			
		||||
    _list[index] = value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
class ConversationMessagesList extends AbstractList<ConversationMessage> {
 | 
			
		||||
  /// Get the list of the users ID who own a message in this list
 | 
			
		||||
  Set<int> getUsersID() {
 | 
			
		||||
    final Set<int> users = Set();
 | 
			
		||||
  Set<int?> getUsersID() {
 | 
			
		||||
    final Set<int?> users = Set();
 | 
			
		||||
 | 
			
		||||
    for (ConversationMessage message in this) users.addAll(message.usersID);
 | 
			
		||||
 | 
			
		||||
@@ -33,18 +16,18 @@ class ConversationMessagesList extends ListBase<ConversationMessage> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the ID of the last message present in this list
 | 
			
		||||
  int get lastMessageID {
 | 
			
		||||
    int lastMessageID = 0;
 | 
			
		||||
  int? get lastMessageID {
 | 
			
		||||
    int? lastMessageID = 0;
 | 
			
		||||
    for (ConversationMessage message in this)
 | 
			
		||||
      if (message.id > lastMessageID) lastMessageID = message.id;
 | 
			
		||||
      if (message.id! > lastMessageID!) lastMessageID = message.id;
 | 
			
		||||
    return lastMessageID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the ID of the first message present in this list
 | 
			
		||||
  int get firstMessageID {
 | 
			
		||||
    int firstMessageID = this[0].id;
 | 
			
		||||
  int? get firstMessageID {
 | 
			
		||||
    int? firstMessageID = this[0].id;
 | 
			
		||||
    for (ConversationMessage message in this)
 | 
			
		||||
      if (message.id < firstMessageID) firstMessageID = message.id;
 | 
			
		||||
      if (message.id! < firstMessageID!) firstMessageID = message.id;
 | 
			
		||||
    return firstMessageID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -55,5 +38,5 @@ class ConversationMessagesList extends ListBase<ConversationMessage> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Remove a message from this list
 | 
			
		||||
  void removeMsg(int id) => removeWhere((f) => f.id == id);
 | 
			
		||||
  void removeMsg(int? id) => removeWhere((f) => f.id == id);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,12 +22,15 @@ class ConversationsList extends ListBase<Conversation> {
 | 
			
		||||
  /// Get the entire lists of users ID in this list
 | 
			
		||||
  Set<int> get allUsersID {
 | 
			
		||||
    final Set<int> list = Set();
 | 
			
		||||
    forEach((c) => c.members.forEach((member) => list.add(member.userID)));
 | 
			
		||||
    forEach((c) => c.members!.forEach((member) => list.add(member.userID)));
 | 
			
		||||
    return list;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the entire lists of groups ID in this list
 | 
			
		||||
  Set<int> get allGroupsID => where((element) => element.isGroupConversation)
 | 
			
		||||
  Set<int?> get allGroupsID => where((element) => element.isGroupConversation)
 | 
			
		||||
      .map((e) => e.groupID)
 | 
			
		||||
      .toSet();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void add(Conversation element) => _list.add(element);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,7 @@ import 'package:comunic/models/custom_emoji.dart';
 | 
			
		||||
 | 
			
		||||
class CustomEmojiesList extends AbstractList<CustomEmoji> {
 | 
			
		||||
  /// Check if an emoji, identified by its shortcut, is present in this list
 | 
			
		||||
  bool hasShortcut(String shortcut) =>
 | 
			
		||||
      firstWhere((f) => f.shortcut == shortcut, orElse: () => null) != null;
 | 
			
		||||
  bool hasShortcut(String shortcut) => any((f) => f.shortcut == shortcut);
 | 
			
		||||
 | 
			
		||||
  /// Serialize this list
 | 
			
		||||
  List<Map<String, dynamic>> toSerializableList() =>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import 'package:comunic/models/forez_presence.dart';
 | 
			
		||||
 | 
			
		||||
class PresenceSet extends BaseSet<Presence> {
 | 
			
		||||
  /// Get the presence of a specific user
 | 
			
		||||
  PresenceSet getForUser(int userID) =>
 | 
			
		||||
  PresenceSet getForUser(int? userID) =>
 | 
			
		||||
      PresenceSet()..addAll(where((element) => element.userID == userID));
 | 
			
		||||
 | 
			
		||||
  bool containsDate(DateTime dt) => any(
 | 
			
		||||
@@ -24,11 +24,11 @@ class PresenceSet extends BaseSet<Presence> {
 | 
			
		||||
            element.day == dt.day,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void toggleDate(DateTime dt, int userID) {
 | 
			
		||||
  void toggleDate(DateTime dt, int? userID) {
 | 
			
		||||
    if (containsDate(dt))
 | 
			
		||||
      removeDate(dt);
 | 
			
		||||
    else
 | 
			
		||||
      add(Presence.fromDateTime(dt, userID));
 | 
			
		||||
      add(Presence.fromDateTime(dt, userID!));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int countAtDate(DateTime dt) => where(
 | 
			
		||||
 
 | 
			
		||||
@@ -6,23 +6,23 @@ import 'package:comunic/models/group.dart';
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class GroupsList extends MapBase<int, Group> {
 | 
			
		||||
  final Map<int, Group> _groups = Map();
 | 
			
		||||
class GroupsList extends MapBase<int?, Group> {
 | 
			
		||||
  final Map<int?, Group?> _groups = Map();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Group operator [](Object key) => _groups[key];
 | 
			
		||||
  Group? operator [](Object? key) => _groups[key];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void operator []=(int key, Group value) => _groups[key] = value;
 | 
			
		||||
  void operator []=(int? key, Group? value) => _groups[key] = value;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void clear() => _groups.clear();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Iterable<int> get keys => _groups.keys;
 | 
			
		||||
  Iterable<int?> get keys => _groups.keys;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Group remove(Object key) => _groups.remove(key);
 | 
			
		||||
  Group? remove(Object? key) => _groups.remove(key);
 | 
			
		||||
 | 
			
		||||
  Group getGroup(int id) => this[id];
 | 
			
		||||
  Group? getGroup(int? id) => this[id];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,20 +5,20 @@ import 'package:comunic/models/membership.dart';
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class MembershipList extends AbstractList<Membership> {
 | 
			
		||||
class MembershipList extends AbstractList<Membership?> {
 | 
			
		||||
  /// Get the IDs of all the users included in some way in this list
 | 
			
		||||
  Set<int> get usersId {
 | 
			
		||||
    final s = Set<int>();
 | 
			
		||||
  Set<int?> get usersId {
 | 
			
		||||
    final s = Set<int?>();
 | 
			
		||||
 | 
			
		||||
    forEach((m) {
 | 
			
		||||
      switch (m.type) {
 | 
			
		||||
      switch (m!.type) {
 | 
			
		||||
        case MembershipType.FRIEND:
 | 
			
		||||
          s.add(m.friend.id);
 | 
			
		||||
          s.add(m.friend!.id);
 | 
			
		||||
          break;
 | 
			
		||||
        case MembershipType.GROUP:
 | 
			
		||||
          break;
 | 
			
		||||
        case MembershipType.CONVERSATION:
 | 
			
		||||
          s.addAll(m.conversation.membersID);
 | 
			
		||||
          s.addAll(m.conversation!.membersID);
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
@@ -27,16 +27,16 @@ class MembershipList extends AbstractList<Membership> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the ID of the groups included in this list
 | 
			
		||||
  Set<int> get groupsId => where((f) => f.type == MembershipType.GROUP)
 | 
			
		||||
      .map((f) => f.groupID)
 | 
			
		||||
  Set<int?> get groupsId => where((f) => f!.type == MembershipType.GROUP)
 | 
			
		||||
      .map((f) => f!.groupID)
 | 
			
		||||
      .toSet();
 | 
			
		||||
 | 
			
		||||
  /// Remove a friend membership from the list
 | 
			
		||||
  void removeFriend(int friendID) => remove(firstWhere(
 | 
			
		||||
      (f) => f.type == MembershipType.FRIEND && f.friend.id == friendID));
 | 
			
		||||
      (f) => f!.type == MembershipType.FRIEND && f.friend!.id == friendID));
 | 
			
		||||
 | 
			
		||||
  /// Get the list of conversations of a group
 | 
			
		||||
  Set<Membership> getGroupConversations(int groupID) => where((element) =>
 | 
			
		||||
      element.type == MembershipType.CONVERSATION &&
 | 
			
		||||
      element.conversation.groupID == groupID).toSet();
 | 
			
		||||
  Set<Membership?> getGroupConversations(int groupID) => where((element) =>
 | 
			
		||||
      element!.type == MembershipType.CONVERSATION &&
 | 
			
		||||
      element.conversation!.groupID == groupID).toSet();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@ import 'package:comunic/models/notification.dart';
 | 
			
		||||
class NotificationsList extends AbstractList<Notification> {
 | 
			
		||||
  /// Get the ID of all the users related to the notifications
 | 
			
		||||
  /// included in the list
 | 
			
		||||
  Set<int> get usersIds {
 | 
			
		||||
    final list = Set<int>();
 | 
			
		||||
  Set<int?> get usersIds {
 | 
			
		||||
    final list = Set<int?>();
 | 
			
		||||
 | 
			
		||||
    forEach((n) {
 | 
			
		||||
      list.add(n.fromUser);
 | 
			
		||||
@@ -28,8 +28,8 @@ class NotificationsList extends AbstractList<Notification> {
 | 
			
		||||
 | 
			
		||||
  /// Get the ID of all the groups related to the notifications
 | 
			
		||||
  /// included in the list
 | 
			
		||||
  Set<int> get groupsIds {
 | 
			
		||||
    final list = Set<int>();
 | 
			
		||||
  Set<int?> get groupsIds {
 | 
			
		||||
    final list = Set<int?>();
 | 
			
		||||
 | 
			
		||||
    forEach((n) {
 | 
			
		||||
      if (n.onElemType == NotificationElementType.GROUP_PAGE)
 | 
			
		||||
 
 | 
			
		||||
@@ -22,23 +22,23 @@ class PostsList extends ListBase<Post> {
 | 
			
		||||
  void operator []=(int index, Post value) => _list[index] = value;
 | 
			
		||||
 | 
			
		||||
  // Get the list of users ID in this set
 | 
			
		||||
  Set<int> get usersID {
 | 
			
		||||
    Set<int> set = Set();
 | 
			
		||||
  Set<int?> get usersID {
 | 
			
		||||
    Set<int?> set = Set();
 | 
			
		||||
 | 
			
		||||
    forEach((p) {
 | 
			
		||||
      set.add(p.userID);
 | 
			
		||||
 | 
			
		||||
      if (p.userPageID != null && p.userPageID > 0) set.add(p.userPageID);
 | 
			
		||||
      if (p.userPageID != null && p.userPageID! > 0) set.add(p.userPageID);
 | 
			
		||||
 | 
			
		||||
      if (p.comments != null) set.addAll(p.comments.usersID);
 | 
			
		||||
      if (p.comments != null) set.addAll(p.comments!.usersID);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return set;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the list of groups in this list of posts
 | 
			
		||||
  Set<int> get groupsID {
 | 
			
		||||
    Set<int> set = Set();
 | 
			
		||||
  Set<int?> get groupsID {
 | 
			
		||||
    Set<int?> set = Set();
 | 
			
		||||
 | 
			
		||||
    forEach((p) {
 | 
			
		||||
      if (p.isGroupPost) set.add(p.groupID);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,8 @@ import 'package:comunic/models/unread_conversation.dart';
 | 
			
		||||
 | 
			
		||||
class UnreadConversationsList extends AbstractList<UnreadConversation> {
 | 
			
		||||
  /// Get the ID of the users included in this list
 | 
			
		||||
  Set<int> get usersID {
 | 
			
		||||
    final set = Set<int>();
 | 
			
		||||
  Set<int?> get usersID {
 | 
			
		||||
    final set = Set<int?>();
 | 
			
		||||
    forEach((element) {
 | 
			
		||||
      set.addAll(element.conv.membersID);
 | 
			
		||||
      set.addAll(element.message.usersID);
 | 
			
		||||
@@ -17,8 +17,8 @@ class UnreadConversationsList extends AbstractList<UnreadConversation> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the ID of the groups references ind this list
 | 
			
		||||
  Set<int> get groupsID {
 | 
			
		||||
    final set = Set<int>();
 | 
			
		||||
  Set<int?> get groupsID {
 | 
			
		||||
    final set = Set<int?>();
 | 
			
		||||
    forEach((element) {
 | 
			
		||||
      if (element.conv.isGroupConversation) set.add(element.conv.groupID);
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ class UsersList extends ListBase<User> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Find a user with a specific ID
 | 
			
		||||
  User getUser(int userID) {
 | 
			
		||||
  User getUser(int? userID) {
 | 
			
		||||
    for (int i = 0; i < this.length; i++)
 | 
			
		||||
      if (this[i].id == userID) return this[i];
 | 
			
		||||
 | 
			
		||||
@@ -32,8 +32,11 @@ class UsersList extends ListBase<User> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Check if the user is included in this list or not
 | 
			
		||||
  bool hasUser(int userID) => any((f) => f.id == userID);
 | 
			
		||||
  bool hasUser(int? userID) => any((f) => f.id == userID);
 | 
			
		||||
 | 
			
		||||
  /// Get the list of users ID present in this list
 | 
			
		||||
  List<int> get usersID => List.generate(length, (i) => this[i].id);
 | 
			
		||||
  List<int?> get usersID => List.generate(length, (i) => this[i].id);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void add(User element) => _list.add(element);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,8 +44,8 @@ class ComunicApplication extends StatefulWidget {
 | 
			
		||||
  final PreferencesHelper preferences;
 | 
			
		||||
 | 
			
		||||
  const ComunicApplication({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.preferences,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.preferences,
 | 
			
		||||
  })  : assert(preferences != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import 'package:comunic/utils/flutter_utils.dart';
 | 
			
		||||
/// Fix HTTPS issue
 | 
			
		||||
class MyHttpOverride extends HttpOverrides {
 | 
			
		||||
  @override
 | 
			
		||||
  HttpClient createHttpClient(SecurityContext context) {
 | 
			
		||||
  HttpClient createHttpClient(SecurityContext? context) {
 | 
			
		||||
    return super.createHttpClient(context)
 | 
			
		||||
      ..badCertificateCallback = (cert, host, port) {
 | 
			
		||||
        return host == "devweb.local"; // Forcefully trust local website
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:flutter/widgets.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Account image settings
 | 
			
		||||
///
 | 
			
		||||
@@ -7,14 +7,14 @@ import 'package:flutter/widgets.dart';
 | 
			
		||||
enum AccountImageVisibilityLevels { EVERYONE, COMUNIC_USERS, FRIENDS_ONLY }
 | 
			
		||||
 | 
			
		||||
class AccountImageSettings {
 | 
			
		||||
  final bool/*!*/ hasImage;
 | 
			
		||||
  final String/*!*/ imageURL;
 | 
			
		||||
  final AccountImageVisibilityLevels/*!*/ visibility;
 | 
			
		||||
  final bool hasImage;
 | 
			
		||||
  final String imageURL;
 | 
			
		||||
  final AccountImageVisibilityLevels visibility;
 | 
			
		||||
 | 
			
		||||
  const AccountImageSettings({
 | 
			
		||||
    @required this.hasImage,
 | 
			
		||||
    @required this.imageURL,
 | 
			
		||||
    @required this.visibility,
 | 
			
		||||
    required this.hasImage,
 | 
			
		||||
    required this.imageURL,
 | 
			
		||||
    required this.visibility,
 | 
			
		||||
  })  : assert(hasImage != null),
 | 
			
		||||
        assert(imageURL != null),
 | 
			
		||||
        assert(visibility != null);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import 'package:comunic/enums/likes_type.dart';
 | 
			
		||||
import 'package:comunic/models/conversation.dart';
 | 
			
		||||
import 'package:comunic/models/like_element.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'group.dart';
 | 
			
		||||
 | 
			
		||||
@@ -10,34 +9,34 @@ import 'group.dart';
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class AdvancedGroupInfo extends Group implements LikeElement {
 | 
			
		||||
  bool isMembersListPublic;
 | 
			
		||||
  final int timeCreate;
 | 
			
		||||
  bool? isMembersListPublic;
 | 
			
		||||
  final int? timeCreate;
 | 
			
		||||
  String description;
 | 
			
		||||
  String url;
 | 
			
		||||
  int likes;
 | 
			
		||||
  bool userLike;
 | 
			
		||||
  List<Conversation> conversations;
 | 
			
		||||
  bool/*!*/ isForezGroup;
 | 
			
		||||
  List<Conversation>? conversations;
 | 
			
		||||
  bool isForezGroup;
 | 
			
		||||
 | 
			
		||||
  AdvancedGroupInfo({
 | 
			
		||||
    @required int id,
 | 
			
		||||
    @required String name,
 | 
			
		||||
    @required String iconURL,
 | 
			
		||||
    @required int numberMembers,
 | 
			
		||||
    @required GroupMembershipLevel membershipLevel,
 | 
			
		||||
    @required GroupVisibilityLevel visibilityLevel,
 | 
			
		||||
    @required GroupRegistrationLevel registrationLevel,
 | 
			
		||||
    @required GroupPostCreationLevel postCreationLevel,
 | 
			
		||||
    @required String virtualDirectory,
 | 
			
		||||
    @required bool following,
 | 
			
		||||
    @required this.isMembersListPublic,
 | 
			
		||||
    @required this.timeCreate,
 | 
			
		||||
    @required this.description,
 | 
			
		||||
    @required this.url,
 | 
			
		||||
    @required this.likes,
 | 
			
		||||
    @required this.userLike,
 | 
			
		||||
    @required this.conversations,
 | 
			
		||||
    @required this.isForezGroup,
 | 
			
		||||
    required int id,
 | 
			
		||||
    required String name,
 | 
			
		||||
    required String iconURL,
 | 
			
		||||
    required int numberMembers,
 | 
			
		||||
    required GroupMembershipLevel membershipLevel,
 | 
			
		||||
    required GroupVisibilityLevel visibilityLevel,
 | 
			
		||||
    required GroupRegistrationLevel registrationLevel,
 | 
			
		||||
    required GroupPostCreationLevel postCreationLevel,
 | 
			
		||||
    required String virtualDirectory,
 | 
			
		||||
    required bool following,
 | 
			
		||||
    required this.isMembersListPublic,
 | 
			
		||||
    required this.timeCreate,
 | 
			
		||||
    required this.description,
 | 
			
		||||
    required this.url,
 | 
			
		||||
    required this.likes,
 | 
			
		||||
    required this.userLike,
 | 
			
		||||
    required this.conversations,
 | 
			
		||||
    required this.isForezGroup,
 | 
			
		||||
  })  : assert(isForezGroup != null),
 | 
			
		||||
        super(
 | 
			
		||||
            id: id,
 | 
			
		||||
 
 | 
			
		||||
@@ -3,42 +3,41 @@ import 'package:comunic/enums/user_page_visibility.dart';
 | 
			
		||||
import 'package:comunic/lists/custom_emojies_list.dart';
 | 
			
		||||
import 'package:comunic/models/like_element.dart';
 | 
			
		||||
import 'package:comunic/models/user.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Advanced user information
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class AdvancedUserInfo extends User implements LikeElement {
 | 
			
		||||
  final String emailAddress;
 | 
			
		||||
  final String/*!*/ publicNote;
 | 
			
		||||
  final bool/*!*/ canPostTexts;
 | 
			
		||||
  final bool/*!*/ isFriendsListPublic;
 | 
			
		||||
  final int/*!*/ numberFriends;
 | 
			
		||||
  final int/*!*/ accountCreationTime;
 | 
			
		||||
  final String/*!*/ personalWebsite;
 | 
			
		||||
  final String location;
 | 
			
		||||
  bool/*!*/ userLike;
 | 
			
		||||
  int/*!*/ likes;
 | 
			
		||||
  final String? emailAddress;
 | 
			
		||||
  final String publicNote;
 | 
			
		||||
  final bool canPostTexts;
 | 
			
		||||
  final bool isFriendsListPublic;
 | 
			
		||||
  final int numberFriends;
 | 
			
		||||
  final int accountCreationTime;
 | 
			
		||||
  final String personalWebsite;
 | 
			
		||||
  final String? location;
 | 
			
		||||
  bool userLike;
 | 
			
		||||
  int likes;
 | 
			
		||||
 | 
			
		||||
  AdvancedUserInfo({
 | 
			
		||||
    @required int id,
 | 
			
		||||
    @required String firstName,
 | 
			
		||||
    @required String lastName,
 | 
			
		||||
    @required UserPageVisibility pageVisibility,
 | 
			
		||||
    @required String virtualDirectory,
 | 
			
		||||
    @required String accountImageURL,
 | 
			
		||||
    @required CustomEmojiesList customEmojies,
 | 
			
		||||
    @required this.emailAddress,
 | 
			
		||||
    @required this.publicNote,
 | 
			
		||||
    @required this.canPostTexts,
 | 
			
		||||
    @required this.isFriendsListPublic,
 | 
			
		||||
    @required this.numberFriends,
 | 
			
		||||
    @required this.accountCreationTime,
 | 
			
		||||
    @required this.personalWebsite,
 | 
			
		||||
    @required this.location,
 | 
			
		||||
    @required this.userLike,
 | 
			
		||||
    @required this.likes,
 | 
			
		||||
    required int id,
 | 
			
		||||
    required String firstName,
 | 
			
		||||
    required String lastName,
 | 
			
		||||
    required UserPageVisibility pageVisibility,
 | 
			
		||||
    required String? virtualDirectory,
 | 
			
		||||
    required String accountImageURL,
 | 
			
		||||
    required CustomEmojiesList customEmojies,
 | 
			
		||||
    required this.emailAddress,
 | 
			
		||||
    required this.publicNote,
 | 
			
		||||
    required this.canPostTexts,
 | 
			
		||||
    required this.isFriendsListPublic,
 | 
			
		||||
    required this.numberFriends,
 | 
			
		||||
    required this.accountCreationTime,
 | 
			
		||||
    required this.personalWebsite,
 | 
			
		||||
    required this.location,
 | 
			
		||||
    required this.userLike,
 | 
			
		||||
    required this.likes,
 | 
			
		||||
  })  : assert(publicNote != null),
 | 
			
		||||
        assert(canPostTexts != null),
 | 
			
		||||
        assert(isFriendsListPublic != null),
 | 
			
		||||
@@ -60,9 +59,9 @@ class AdvancedUserInfo extends User implements LikeElement {
 | 
			
		||||
 | 
			
		||||
  bool get hasPersonalWebsite => personalWebsite.isNotEmpty;
 | 
			
		||||
 | 
			
		||||
  bool get hasEmailAddress => emailAddress != null && emailAddress.isNotEmpty;
 | 
			
		||||
  bool get hasEmailAddress => emailAddress != null && emailAddress!.isNotEmpty;
 | 
			
		||||
 | 
			
		||||
  bool get hasLocation => location != null && location.isNotEmpty;
 | 
			
		||||
  bool get hasLocation => location != null && location!.isNotEmpty;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  LikesType get likeType => LikesType.USER;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import 'package:comunic/helpers/api_helper.dart';
 | 
			
		||||
import 'package:comunic/models/api_response.dart';
 | 
			
		||||
import 'package:dio/dio.dart';
 | 
			
		||||
import 'package:http_parser/http_parser.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// API Request model
 | 
			
		||||
///
 | 
			
		||||
@@ -14,8 +13,8 @@ import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
class BytesFile {
 | 
			
		||||
  final String filename;
 | 
			
		||||
  final List<int> bytes;
 | 
			
		||||
  final MediaType type;
 | 
			
		||||
  final List<int>? bytes;
 | 
			
		||||
  final MediaType? type;
 | 
			
		||||
 | 
			
		||||
  const BytesFile(
 | 
			
		||||
    this.filename,
 | 
			
		||||
@@ -25,15 +24,15 @@ class BytesFile {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class APIRequest {
 | 
			
		||||
  final String/*!*/ uri;
 | 
			
		||||
  final bool/*!*/ needLogin;
 | 
			
		||||
  ProgressCallback progressCallback;
 | 
			
		||||
  CancelToken cancelToken;
 | 
			
		||||
  Map<String, String> args;
 | 
			
		||||
  final String uri;
 | 
			
		||||
  final bool needLogin;
 | 
			
		||||
  ProgressCallback? progressCallback;
 | 
			
		||||
  CancelToken? cancelToken;
 | 
			
		||||
  Map<String, String?>? args;
 | 
			
		||||
  Map<String, File> files = Map();
 | 
			
		||||
  Map<String, BytesFile> bytesFiles = Map();
 | 
			
		||||
  Map<String, BytesFile?> bytesFiles = Map();
 | 
			
		||||
 | 
			
		||||
  APIRequest({@required this.uri, this.needLogin = false, this.args})
 | 
			
		||||
  APIRequest({required this.uri, this.needLogin = false, this.args})
 | 
			
		||||
      : assert(uri != null),
 | 
			
		||||
        assert(needLogin != null) {
 | 
			
		||||
    if (this.args == null) this.args = Map();
 | 
			
		||||
@@ -51,18 +50,18 @@ class APIRequest {
 | 
			
		||||
    if (args == null) this.args = Map();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  APIRequest addString(String name, String value) {
 | 
			
		||||
    args[name] = value;
 | 
			
		||||
  APIRequest addString(String name, String? value) {
 | 
			
		||||
    args![name] = value;
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  APIRequest addInt(String name, int value) {
 | 
			
		||||
    args[name] = value.toString();
 | 
			
		||||
  APIRequest addInt(String name, int? value) {
 | 
			
		||||
    args![name] = value.toString();
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  APIRequest addBool(String name, bool value) {
 | 
			
		||||
    args[name] = value ? "true" : "false";
 | 
			
		||||
    args![name] = value ? "true" : "false";
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -71,12 +70,12 @@ class APIRequest {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  APIRequest addBytesFile(String name, BytesFile file) {
 | 
			
		||||
  APIRequest addBytesFile(String name, BytesFile? file) {
 | 
			
		||||
    this.bytesFiles[name] = file;
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void addArgs(Map<String, String> newArgs) => args.addAll(newArgs);
 | 
			
		||||
  void addArgs(Map<String, String> newArgs) => args!.addAll(newArgs);
 | 
			
		||||
 | 
			
		||||
  /// Execute the request
 | 
			
		||||
  Future<APIResponse> exec() async => APIHelper().exec(this);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,14 @@ import 'dart:convert';
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class APIResponse {
 | 
			
		||||
  final int/*!*/ code;
 | 
			
		||||
  final String content;
 | 
			
		||||
  final int code;
 | 
			
		||||
  final String? content;
 | 
			
		||||
 | 
			
		||||
  const APIResponse(this.code, this.content) : assert(code != null);
 | 
			
		||||
  const APIResponse(this.code, this.content);
 | 
			
		||||
 | 
			
		||||
  List<dynamic> getArray() => jsonDecode(this.content);
 | 
			
		||||
  List<dynamic>? getArray() => jsonDecode(this.content!);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> getObject() => jsonDecode(this.content);
 | 
			
		||||
  Map<String, dynamic> getObject() => jsonDecode(this.content!);
 | 
			
		||||
 | 
			
		||||
  /// Check if the request is successful or not
 | 
			
		||||
  bool get isOK => code == 200;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Application settings
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class ApplicationPreferences {
 | 
			
		||||
  bool/*!*/ enableDarkMode;
 | 
			
		||||
  bool/*!*/ forceMobileMode;
 | 
			
		||||
  bool/*!*/ showPerformancesOverlay;
 | 
			
		||||
  bool enableDarkMode;
 | 
			
		||||
  bool forceMobileMode;
 | 
			
		||||
  bool showPerformancesOverlay;
 | 
			
		||||
 | 
			
		||||
  ApplicationPreferences({
 | 
			
		||||
    @required this.enableDarkMode,
 | 
			
		||||
    @required this.forceMobileMode,
 | 
			
		||||
    @required this.showPerformancesOverlay,
 | 
			
		||||
    required this.enableDarkMode,
 | 
			
		||||
    required this.forceMobileMode,
 | 
			
		||||
    required this.showPerformancesOverlay,
 | 
			
		||||
  })  : assert(enableDarkMode != null),
 | 
			
		||||
        assert(forceMobileMode != null),
 | 
			
		||||
        assert(showPerformancesOverlay != null);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Authentication details
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class AuthenticationDetails {
 | 
			
		||||
  final String/*!*/ email;
 | 
			
		||||
  final String/*!*/ password;
 | 
			
		||||
  final String email;
 | 
			
		||||
  final String password;
 | 
			
		||||
 | 
			
		||||
  const AuthenticationDetails({@required this.email, @required this.password})
 | 
			
		||||
  const AuthenticationDetails({required this.email, required this.password})
 | 
			
		||||
      : assert(email != null),
 | 
			
		||||
        assert(password != null);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,13 @@
 | 
			
		||||
import 'package:comunic/helpers/database/database_contract.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Cache base model
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
abstract class CacheModel {
 | 
			
		||||
  final int/*!*/ id;
 | 
			
		||||
  final int id;
 | 
			
		||||
 | 
			
		||||
  const CacheModel({@required this.id}) : assert(id != null);
 | 
			
		||||
  const CacheModel({required this.id}) : assert(id != null);
 | 
			
		||||
 | 
			
		||||
  /// Initialize a CacheModel from a map
 | 
			
		||||
  CacheModel.fromMap(Map<String, dynamic> map)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Call configuration
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class CallConfig {
 | 
			
		||||
  final List<String>/*!*/ iceServers;
 | 
			
		||||
  final List<String> iceServers;
 | 
			
		||||
 | 
			
		||||
  const CallConfig({
 | 
			
		||||
    @required this.iceServers,
 | 
			
		||||
    required this.iceServers,
 | 
			
		||||
  }) : assert(iceServers != null);
 | 
			
		||||
 | 
			
		||||
  /// Turn this call configuration into the right for the WebRTC plugin
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
 | 
			
		||||
 | 
			
		||||
/// Single call member information
 | 
			
		||||
@@ -8,16 +7,16 @@ import 'package:flutter_webrtc/flutter_webrtc.dart';
 | 
			
		||||
enum MemberStatus { JOINED, READY }
 | 
			
		||||
 | 
			
		||||
class CallMember {
 | 
			
		||||
  final int/*!*/ userID;
 | 
			
		||||
  MemberStatus/*!*/ status;
 | 
			
		||||
  MediaStream stream;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  MemberStatus status;
 | 
			
		||||
  MediaStream? stream;
 | 
			
		||||
 | 
			
		||||
  CallMember({
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    this.status = MemberStatus.JOINED,
 | 
			
		||||
  })  : assert(userID != null),
 | 
			
		||||
        assert(status != null);
 | 
			
		||||
 | 
			
		||||
  bool get hasVideoStream =>
 | 
			
		||||
      stream != null && stream.getVideoTracks().length > 0;
 | 
			
		||||
      stream != null && stream!.getVideoTracks().length > 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import 'package:comunic/enums/likes_type.dart';
 | 
			
		||||
import 'package:comunic/models/displayed_content.dart';
 | 
			
		||||
import 'package:comunic/models/like_element.dart';
 | 
			
		||||
import 'package:comunic/utils/account_utils.dart' as account;
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Comments
 | 
			
		||||
///
 | 
			
		||||
@@ -11,24 +10,24 @@ import 'package:meta/meta.dart';
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class Comment implements LikeElement {
 | 
			
		||||
  final int/*!*/ id;
 | 
			
		||||
  final int/*!*/ userID;
 | 
			
		||||
  final int/*!*/ postID;
 | 
			
		||||
  final int/*!*/ timeSent;
 | 
			
		||||
  DisplayedString/*!*/ content;
 | 
			
		||||
  final String imageURL;
 | 
			
		||||
  int/*!*/ likes;
 | 
			
		||||
  bool/*!*/ userLike;
 | 
			
		||||
  final int id;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int postID;
 | 
			
		||||
  final int timeSent;
 | 
			
		||||
  DisplayedString content;
 | 
			
		||||
  final String? imageURL;
 | 
			
		||||
  int likes;
 | 
			
		||||
  bool userLike;
 | 
			
		||||
 | 
			
		||||
  Comment({
 | 
			
		||||
    @required this.id,
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    @required this.postID,
 | 
			
		||||
    @required this.timeSent,
 | 
			
		||||
    @required this.content,
 | 
			
		||||
    @required this.imageURL,
 | 
			
		||||
    @required this.likes,
 | 
			
		||||
    @required this.userLike,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    required this.postID,
 | 
			
		||||
    required this.timeSent,
 | 
			
		||||
    required this.content,
 | 
			
		||||
    required this.imageURL,
 | 
			
		||||
    required this.likes,
 | 
			
		||||
    required this.userLike,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(userID != null),
 | 
			
		||||
        assert(postID != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -16,27 +16,27 @@ class Config {
 | 
			
		||||
 | 
			
		||||
  // Theme customization
 | 
			
		||||
  final Color splashBackgroundColor;
 | 
			
		||||
  final Color primaryColor;
 | 
			
		||||
  final Color primaryColorDark;
 | 
			
		||||
  final Color? primaryColor;
 | 
			
		||||
  final Color? primaryColorDark;
 | 
			
		||||
  final String appName;
 | 
			
		||||
  final String appQuickDescription;
 | 
			
		||||
  final Color unreadConversationColor;
 | 
			
		||||
  final Color defaultConversationColor;
 | 
			
		||||
  final String? appQuickDescription;
 | 
			
		||||
  final Color? unreadConversationColor;
 | 
			
		||||
  final Color? defaultConversationColor;
 | 
			
		||||
 | 
			
		||||
  // Entries for the welcome tour
 | 
			
		||||
  final TourEntriesBuilder toursEntriesBuilder;
 | 
			
		||||
  final TourEntriesBuilder? toursEntriesBuilder;
 | 
			
		||||
 | 
			
		||||
  // Custom initialization
 | 
			
		||||
  final Future<void> Function() additionalLoading;
 | 
			
		||||
  final Future<void> Function()? additionalLoading;
 | 
			
		||||
 | 
			
		||||
  // Custom main application route
 | 
			
		||||
  final Widget Function(BuildContext, GlobalKey) mainRouteBuilder;
 | 
			
		||||
  final Widget Function(BuildContext, GlobalKey)? mainRouteBuilder;
 | 
			
		||||
 | 
			
		||||
  const Config({
 | 
			
		||||
    @required this.apiServerName,
 | 
			
		||||
    @required this.apiServerUri,
 | 
			
		||||
    @required this.apiServerSecure,
 | 
			
		||||
    @required this.clientName,
 | 
			
		||||
    required this.apiServerName,
 | 
			
		||||
    required this.apiServerUri,
 | 
			
		||||
    required this.apiServerSecure,
 | 
			
		||||
    required this.clientName,
 | 
			
		||||
    this.splashBackgroundColor = defaultColor,
 | 
			
		||||
    this.primaryColor,
 | 
			
		||||
    this.primaryColorDark,
 | 
			
		||||
@@ -55,9 +55,9 @@ class Config {
 | 
			
		||||
        assert(appName != null);
 | 
			
		||||
 | 
			
		||||
  /// Get and set static configuration
 | 
			
		||||
  static Config/*?*/ _config;
 | 
			
		||||
  static Config? _config;
 | 
			
		||||
 | 
			
		||||
  static Config get() {
 | 
			
		||||
  static Config? get() {
 | 
			
		||||
    return _config;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -67,6 +67,6 @@ class Config {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the current configuration of the application
 | 
			
		||||
Config/*!*/ config() {
 | 
			
		||||
  return Config.get();
 | 
			
		||||
Config config() {
 | 
			
		||||
  return Config.get()!;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import 'package:collection/collection.dart' show IterableExtension;
 | 
			
		||||
import 'package:comunic/helpers/serialization/base_serialization_helper.dart';
 | 
			
		||||
import 'package:comunic/models/conversation_member.dart';
 | 
			
		||||
import 'package:comunic/utils/account_utils.dart';
 | 
			
		||||
@@ -12,28 +13,28 @@ import 'group.dart';
 | 
			
		||||
enum CallCapabilities { NONE, AUDIO, VIDEO }
 | 
			
		||||
 | 
			
		||||
class Conversation extends SerializableElement<Conversation> {
 | 
			
		||||
  final int id;
 | 
			
		||||
  final int lastActivity;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final Color color;
 | 
			
		||||
  final String logoURL;
 | 
			
		||||
  final int groupID;
 | 
			
		||||
  final GroupMembershipLevel groupMinMembershipLevel;
 | 
			
		||||
  final List<ConversationMember> members;
 | 
			
		||||
  final bool canEveryoneAddMembers;
 | 
			
		||||
  final int? id;
 | 
			
		||||
  final int? lastActivity;
 | 
			
		||||
  final String? name;
 | 
			
		||||
  final Color? color;
 | 
			
		||||
  final String? logoURL;
 | 
			
		||||
  final int? groupID;
 | 
			
		||||
  final GroupMembershipLevel? groupMinMembershipLevel;
 | 
			
		||||
  final List<ConversationMember>? members;
 | 
			
		||||
  final bool? canEveryoneAddMembers;
 | 
			
		||||
  final CallCapabilities callCapabilities;
 | 
			
		||||
  final bool isHavingCall;
 | 
			
		||||
 | 
			
		||||
  Conversation({
 | 
			
		||||
    /*required*/ @required this.id,
 | 
			
		||||
    /*required*/ @required this.lastActivity,
 | 
			
		||||
    @required this.name,
 | 
			
		||||
    @required this.color,
 | 
			
		||||
    @required this.logoURL,
 | 
			
		||||
    @required this.groupID,
 | 
			
		||||
    @required this.groupMinMembershipLevel,
 | 
			
		||||
    /*required*/ @required this.members,
 | 
			
		||||
    /*required*/ @required this.canEveryoneAddMembers,
 | 
			
		||||
    /*required*/ required int this.id,
 | 
			
		||||
    /*required*/ required int this.lastActivity,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.color,
 | 
			
		||||
    required this.logoURL,
 | 
			
		||||
    required this.groupID,
 | 
			
		||||
    required this.groupMinMembershipLevel,
 | 
			
		||||
    /*required*/ required List<ConversationMember> this.members,
 | 
			
		||||
    /*required*/ required bool this.canEveryoneAddMembers,
 | 
			
		||||
    this.callCapabilities = CallCapabilities.NONE,
 | 
			
		||||
    this.isHavingCall = false,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
@@ -49,7 +50,7 @@ class Conversation extends SerializableElement<Conversation> {
 | 
			
		||||
 | 
			
		||||
  /// Get current user membership
 | 
			
		||||
  ConversationMember get membership =>
 | 
			
		||||
      members.firstWhere((m) => m.userID == userID());
 | 
			
		||||
      members!.firstWhere((m) => m.userID == userID());
 | 
			
		||||
 | 
			
		||||
  /// Check out whether current user of the application is an admin
 | 
			
		||||
  bool get isAdmin => membership.isAdmin;
 | 
			
		||||
@@ -61,17 +62,17 @@ class Conversation extends SerializableElement<Conversation> {
 | 
			
		||||
  bool get following => membership.following;
 | 
			
		||||
 | 
			
		||||
  /// Get the list of members in the conversation
 | 
			
		||||
  Set<int> get membersID => members.map((e) => e.userID).toSet();
 | 
			
		||||
  Set<int?> get membersID => members!.map((e) => e.userID).toSet();
 | 
			
		||||
 | 
			
		||||
  /// Get the list of admins in the conversation
 | 
			
		||||
  Set<int> get adminsID =>
 | 
			
		||||
      members.where((e) => e.isAdmin).map((e) => e.userID).toSet();
 | 
			
		||||
  Set<int?> get adminsID =>
 | 
			
		||||
      members!.where((e) => e.isAdmin).map((e) => e.userID).toSet();
 | 
			
		||||
 | 
			
		||||
  /// Get the list of the OTHER members of the conversation (all except current user)
 | 
			
		||||
  Set<int> get otherMembersID => membersID..remove(userID());
 | 
			
		||||
  Set<int?> get otherMembersID => membersID..remove(userID());
 | 
			
		||||
 | 
			
		||||
  /// Check if the last message has been seen or not
 | 
			
		||||
  bool get sawLastMessage => lastActivity <= membership.lastAccessTime;
 | 
			
		||||
  bool get sawLastMessage => lastActivity! <= membership.lastAccessTime;
 | 
			
		||||
 | 
			
		||||
  /// Check out whether a conversation is managed or not
 | 
			
		||||
  bool get isManaged => isGroupConversation;
 | 
			
		||||
@@ -86,9 +87,8 @@ class Conversation extends SerializableElement<Conversation> {
 | 
			
		||||
        color = map["color"] == null ? null : Color(map["color"]),
 | 
			
		||||
        logoURL = map["logoURL"],
 | 
			
		||||
        groupID = map["groupID"],
 | 
			
		||||
        groupMinMembershipLevel = GroupMembershipLevel.values.firstWhere(
 | 
			
		||||
            (element) => element.toString() == map["groupMinMembershipLevel"],
 | 
			
		||||
            orElse: () => null),
 | 
			
		||||
        groupMinMembershipLevel = GroupMembershipLevel.values.firstWhereOrNull(
 | 
			
		||||
            (element) => element.toString() == map["groupMinMembershipLevel"]),
 | 
			
		||||
        lastActivity = map["lastActivity"],
 | 
			
		||||
        members = map["members"]
 | 
			
		||||
            .map((el) => ConversationMember.fromJSON(el))
 | 
			
		||||
@@ -109,13 +109,13 @@ class Conversation extends SerializableElement<Conversation> {
 | 
			
		||||
      "groupID": groupID,
 | 
			
		||||
      "groupMinMembershipLevel": groupMinMembershipLevel?.toString(),
 | 
			
		||||
      "lastActivity": lastActivity,
 | 
			
		||||
      "members": members.map((e) => e.toJson()).toList(),
 | 
			
		||||
      "members": members!.map((e) => e.toJson()).toList(),
 | 
			
		||||
      "canEveryoneAddMembers": canEveryoneAddMembers,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int compareTo(Conversation other) {
 | 
			
		||||
    return other.lastActivity.compareTo(this.lastActivity);
 | 
			
		||||
    return other.lastActivity!.compareTo(this.lastActivity!);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,22 @@
 | 
			
		||||
import 'package:flutter/widgets.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Conversation member
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class ConversationMember {
 | 
			
		||||
  final int/*!*/ userID;
 | 
			
		||||
  final int/*!*/ lastMessageSeen;
 | 
			
		||||
  final int/*!*/ lastAccessTime;
 | 
			
		||||
  final bool/*!*/ following;
 | 
			
		||||
  final bool/*!*/ isAdmin;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int lastMessageSeen;
 | 
			
		||||
  final int lastAccessTime;
 | 
			
		||||
  final bool following;
 | 
			
		||||
  final bool isAdmin;
 | 
			
		||||
 | 
			
		||||
  const ConversationMember({
 | 
			
		||||
    /*required*/ @required this.userID,
 | 
			
		||||
    /*required*/ @required this.lastMessageSeen,
 | 
			
		||||
    /*required*/ @required this.lastAccessTime,
 | 
			
		||||
    /*required*/ @required this.following,
 | 
			
		||||
    /*required*/ @required this.isAdmin,
 | 
			
		||||
    /*required*/ required this.userID,
 | 
			
		||||
    /*required*/ required this.lastMessageSeen,
 | 
			
		||||
    /*required*/ required this.lastAccessTime,
 | 
			
		||||
    /*required*/ required this.following,
 | 
			
		||||
    /*required*/ required this.isAdmin,
 | 
			
		||||
  })  : assert(userID != null),
 | 
			
		||||
        assert(lastMessageSeen != null),
 | 
			
		||||
        assert(lastAccessTime != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -30,27 +30,27 @@ const _ConversationFileMimeTypeMapping = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ConversationMessageFile {
 | 
			
		||||
  final String url;
 | 
			
		||||
  final int size;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final String thumbnail;
 | 
			
		||||
  final String type;
 | 
			
		||||
  final String? url;
 | 
			
		||||
  final int? size;
 | 
			
		||||
  final String? name;
 | 
			
		||||
  final String? thumbnail;
 | 
			
		||||
  final String? type;
 | 
			
		||||
 | 
			
		||||
  const ConversationMessageFile({
 | 
			
		||||
    @required this.url,
 | 
			
		||||
    @required this.size,
 | 
			
		||||
    @required this.name,
 | 
			
		||||
    @required this.thumbnail,
 | 
			
		||||
    @required this.type,
 | 
			
		||||
    required String this.url,
 | 
			
		||||
    required int this.size,
 | 
			
		||||
    required String this.name,
 | 
			
		||||
    required this.thumbnail,
 | 
			
		||||
    required String this.type,
 | 
			
		||||
  })  : assert(url != null),
 | 
			
		||||
        assert(size != null),
 | 
			
		||||
        assert(name != null),
 | 
			
		||||
        assert(type != null);
 | 
			
		||||
 | 
			
		||||
  /// Get the type of file
 | 
			
		||||
  ConversationMessageFileType get fileType {
 | 
			
		||||
  ConversationMessageFileType? get fileType {
 | 
			
		||||
    if (type != null && _ConversationFileMimeTypeMapping.containsKey(type))
 | 
			
		||||
      return _ConversationFileMimeTypeMapping[type];
 | 
			
		||||
      return _ConversationFileMimeTypeMapping[type!];
 | 
			
		||||
    else
 | 
			
		||||
      return ConversationMessageFileType.OTHER;
 | 
			
		||||
  }
 | 
			
		||||
@@ -102,19 +102,19 @@ enum ConversationServerMessageType {
 | 
			
		||||
 | 
			
		||||
class ConversationServerMessage {
 | 
			
		||||
  final ConversationServerMessageType type;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int userWhoAdded;
 | 
			
		||||
  final int userAdded;
 | 
			
		||||
  final int userWhoRemoved;
 | 
			
		||||
  final int userRemoved;
 | 
			
		||||
  final int? userID;
 | 
			
		||||
  final int? userWhoAdded;
 | 
			
		||||
  final int? userAdded;
 | 
			
		||||
  final int? userWhoRemoved;
 | 
			
		||||
  final int? userRemoved;
 | 
			
		||||
 | 
			
		||||
  const ConversationServerMessage({
 | 
			
		||||
    @required this.type,
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    @required this.userWhoAdded,
 | 
			
		||||
    @required this.userAdded,
 | 
			
		||||
    @required this.userWhoRemoved,
 | 
			
		||||
    @required this.userRemoved,
 | 
			
		||||
    required this.type,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    required this.userWhoAdded,
 | 
			
		||||
    required this.userAdded,
 | 
			
		||||
    required this.userWhoRemoved,
 | 
			
		||||
    required this.userRemoved,
 | 
			
		||||
  })  : assert(type != null),
 | 
			
		||||
        assert(userID != null ||
 | 
			
		||||
            (type != ConversationServerMessageType.USER_CREATED_CONVERSATION &&
 | 
			
		||||
@@ -124,7 +124,7 @@ class ConversationServerMessage {
 | 
			
		||||
        assert((userWhoRemoved != null && userRemoved != null) ||
 | 
			
		||||
            type != ConversationServerMessageType.USER_REMOVED_ANOTHER_USER);
 | 
			
		||||
 | 
			
		||||
  Set<int> get usersID {
 | 
			
		||||
  Set<int?> get usersID {
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case ConversationServerMessageType.USER_CREATED_CONVERSATION:
 | 
			
		||||
      case ConversationServerMessageType.USER_LEFT_CONV:
 | 
			
		||||
@@ -144,26 +144,26 @@ class ConversationServerMessage {
 | 
			
		||||
    throw Exception("Unsupported server message type!");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String getText(UsersList list) {
 | 
			
		||||
  String? getText(UsersList? list) {
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case ConversationServerMessageType.USER_CREATED_CONVERSATION:
 | 
			
		||||
        return tr("%1% created the conversation",
 | 
			
		||||
            args: {"1": list.getUser(userID).fullName});
 | 
			
		||||
            args: {"1": list!.getUser(userID).fullName});
 | 
			
		||||
 | 
			
		||||
      case ConversationServerMessageType.USER_ADDED_ANOTHER_USER:
 | 
			
		||||
        return tr("%1% added %2% to the conversation", args: {
 | 
			
		||||
          "1": list.getUser(userWhoAdded).fullName,
 | 
			
		||||
          "1": list!.getUser(userWhoAdded).fullName,
 | 
			
		||||
          "2": list.getUser(userAdded).fullName,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      case ConversationServerMessageType.USER_LEFT_CONV:
 | 
			
		||||
        return tr("%1% left the conversation", args: {
 | 
			
		||||
          "1": list.getUser(userID).fullName,
 | 
			
		||||
          "1": list!.getUser(userID).fullName,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      case ConversationServerMessageType.USER_REMOVED_ANOTHER_USER:
 | 
			
		||||
        return tr("%1% removed %2% from the conversation", args: {
 | 
			
		||||
          "1": list.getUser(userWhoRemoved).fullName,
 | 
			
		||||
          "1": list!.getUser(userWhoRemoved).fullName,
 | 
			
		||||
          "2": list.getUser(userRemoved).fullName,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
@@ -191,29 +191,29 @@ class ConversationServerMessage {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ConversationMessage extends SerializableElement<ConversationMessage> {
 | 
			
		||||
  final int id;
 | 
			
		||||
  final int convID;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int timeSent;
 | 
			
		||||
  final int? id;
 | 
			
		||||
  final int? convID;
 | 
			
		||||
  final int? userID;
 | 
			
		||||
  final int? timeSent;
 | 
			
		||||
  final DisplayedString message;
 | 
			
		||||
  final ConversationMessageFile file;
 | 
			
		||||
  final ConversationServerMessage serverMessage;
 | 
			
		||||
  final ConversationMessageFile? file;
 | 
			
		||||
  final ConversationServerMessage? serverMessage;
 | 
			
		||||
 | 
			
		||||
  ConversationMessage({
 | 
			
		||||
    @required this.id,
 | 
			
		||||
    @required this.convID,
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    @required this.timeSent,
 | 
			
		||||
    @required this.message,
 | 
			
		||||
    @required this.file,
 | 
			
		||||
    @required this.serverMessage,
 | 
			
		||||
    required int this.id,
 | 
			
		||||
    required int this.convID,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    required int this.timeSent,
 | 
			
		||||
    required this.message,
 | 
			
		||||
    required this.file,
 | 
			
		||||
    required this.serverMessage,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(convID != null),
 | 
			
		||||
        assert(userID != null || serverMessage != null),
 | 
			
		||||
        assert(timeSent != null),
 | 
			
		||||
        assert(message != null || file != null || serverMessage != null);
 | 
			
		||||
 | 
			
		||||
  DateTime get date => DateTime.fromMillisecondsSinceEpoch(timeSent * 1000);
 | 
			
		||||
  DateTime get date => DateTime.fromMillisecondsSinceEpoch(timeSent! * 1000);
 | 
			
		||||
 | 
			
		||||
  bool get hasMessage => !message.isNull && message.length > 0;
 | 
			
		||||
 | 
			
		||||
@@ -224,16 +224,16 @@ class ConversationMessage extends SerializableElement<ConversationMessage> {
 | 
			
		||||
  bool get isServerMessage => serverMessage != null;
 | 
			
		||||
 | 
			
		||||
  /// Get the list of the ID of the users implied in this message
 | 
			
		||||
  Set<int> get usersID {
 | 
			
		||||
  Set<int?> get usersID {
 | 
			
		||||
    if (userID != null) return Set()..add(userID);
 | 
			
		||||
 | 
			
		||||
    if (serverMessage != null) return serverMessage.usersID;
 | 
			
		||||
    if (serverMessage != null) return serverMessage!.usersID;
 | 
			
		||||
    return Set();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int compareTo(ConversationMessage other) {
 | 
			
		||||
    return id.compareTo(other.id);
 | 
			
		||||
    return id!.compareTo(other.id!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class CountUnreadNotifications {
 | 
			
		||||
  int notifications;
 | 
			
		||||
  int conversations;
 | 
			
		||||
  int? notifications;
 | 
			
		||||
  int? conversations;
 | 
			
		||||
 | 
			
		||||
  CountUnreadNotifications({
 | 
			
		||||
    this.notifications,
 | 
			
		||||
    this.conversations,
 | 
			
		||||
    required int this.notifications,
 | 
			
		||||
    required int this.conversations,
 | 
			
		||||
  })  : assert(notifications != null),
 | 
			
		||||
        assert(conversations != null);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,20 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Single custom emoji information
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class CustomEmoji {
 | 
			
		||||
  final int id;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final String shortcut;
 | 
			
		||||
  final String url;
 | 
			
		||||
  final int? id;
 | 
			
		||||
  final int? userID;
 | 
			
		||||
  final String? shortcut;
 | 
			
		||||
  final String? url;
 | 
			
		||||
 | 
			
		||||
  const CustomEmoji({
 | 
			
		||||
    @required this.id,
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    @required this.shortcut,
 | 
			
		||||
    @required this.url,
 | 
			
		||||
    required int this.id,
 | 
			
		||||
    required int this.userID,
 | 
			
		||||
    required String this.shortcut,
 | 
			
		||||
    required String this.url,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(userID != null),
 | 
			
		||||
        assert(shortcut != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class DataConservationPolicySettings {
 | 
			
		||||
  int inactiveAccountLifeTime;
 | 
			
		||||
  int notificationLifetime;
 | 
			
		||||
  int commentsLifetime;
 | 
			
		||||
  int postsLifetime;
 | 
			
		||||
  int conversationMessagesLifetime;
 | 
			
		||||
  int likesLifetime;
 | 
			
		||||
  int? inactiveAccountLifeTime;
 | 
			
		||||
  int? notificationLifetime;
 | 
			
		||||
  int? commentsLifetime;
 | 
			
		||||
  int? postsLifetime;
 | 
			
		||||
  int? conversationMessagesLifetime;
 | 
			
		||||
  int? likesLifetime;
 | 
			
		||||
 | 
			
		||||
  DataConservationPolicySettings({
 | 
			
		||||
    this.inactiveAccountLifeTime,
 | 
			
		||||
 
 | 
			
		||||
@@ -5,32 +5,32 @@ import 'package:comunic/utils/ui_utils.dart';
 | 
			
		||||
/// @author Pierre Hubert
 | 
			
		||||
 | 
			
		||||
class DisplayedString {
 | 
			
		||||
  String _string;
 | 
			
		||||
  String _parseCache;
 | 
			
		||||
  String? _string;
 | 
			
		||||
  String? _parseCache;
 | 
			
		||||
 | 
			
		||||
  DisplayedString(this._string);
 | 
			
		||||
 | 
			
		||||
  int get length => _string.length;
 | 
			
		||||
  int get length => _string!.length;
 | 
			
		||||
 | 
			
		||||
  bool get isEmpty => _string.isEmpty;
 | 
			
		||||
  bool get isEmpty => _string!.isEmpty;
 | 
			
		||||
 | 
			
		||||
  bool get isNull => _string == null;
 | 
			
		||||
 | 
			
		||||
  String get content => _string;
 | 
			
		||||
  String? get content => _string;
 | 
			
		||||
 | 
			
		||||
  set content(String content) {
 | 
			
		||||
  set content(String? content) {
 | 
			
		||||
    _string = content;
 | 
			
		||||
    _parseCache = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() {
 | 
			
		||||
    return _string;
 | 
			
		||||
    return _string!;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String get parsedString {
 | 
			
		||||
  String? get parsedString {
 | 
			
		||||
    if (_parseCache == null) {
 | 
			
		||||
      _parseCache = parseEmojies(this._string);
 | 
			
		||||
      _parseCache = parseEmojies(this._string!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return _parseCache;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Single presence information
 | 
			
		||||
///
 | 
			
		||||
@@ -11,10 +11,10 @@ class Presence {
 | 
			
		||||
  final int day;
 | 
			
		||||
 | 
			
		||||
  const Presence({
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    @required this.year,
 | 
			
		||||
    @required this.month,
 | 
			
		||||
    @required this.day,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    required this.year,
 | 
			
		||||
    required this.month,
 | 
			
		||||
    required this.day,
 | 
			
		||||
  })  : assert(userID != null),
 | 
			
		||||
        assert(year != null),
 | 
			
		||||
        assert(month != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import 'package:comunic/helpers/database/database_contract.dart';
 | 
			
		||||
import 'package:comunic/models/cache_model.dart';
 | 
			
		||||
import 'package:comunic/utils/date_utils.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Single user Friend information
 | 
			
		||||
///
 | 
			
		||||
@@ -9,16 +8,16 @@ import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
class Friend extends CacheModel implements Comparable<Friend> {
 | 
			
		||||
  bool accepted;
 | 
			
		||||
  final int lastActive;
 | 
			
		||||
  final int? lastActive;
 | 
			
		||||
  final bool following;
 | 
			
		||||
  final bool canPostTexts;
 | 
			
		||||
 | 
			
		||||
  Friend({
 | 
			
		||||
    @required int id,
 | 
			
		||||
    @required this.accepted,
 | 
			
		||||
    @required this.lastActive,
 | 
			
		||||
    @required this.following,
 | 
			
		||||
    @required this.canPostTexts,
 | 
			
		||||
    required int id,
 | 
			
		||||
    required this.accepted,
 | 
			
		||||
    required int this.lastActive,
 | 
			
		||||
    required this.following,
 | 
			
		||||
    required this.canPostTexts,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(accepted != null),
 | 
			
		||||
        assert(lastActive != null),
 | 
			
		||||
@@ -27,10 +26,10 @@ class Friend extends CacheModel implements Comparable<Friend> {
 | 
			
		||||
        super(id: id);
 | 
			
		||||
 | 
			
		||||
  /// Check out whether friend is connected or not
 | 
			
		||||
  bool get isConnected => time() - 30 < lastActive;
 | 
			
		||||
  bool get isConnected => time() - 30 < lastActive!;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int compareTo(Friend other) => other.lastActive.compareTo(lastActive);
 | 
			
		||||
  int compareTo(Friend other) => other.lastActive!.compareTo(lastActive!);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Map<String, dynamic> toMap() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Simple friendship status
 | 
			
		||||
///
 | 
			
		||||
@@ -12,11 +12,11 @@ class FriendStatus {
 | 
			
		||||
  final bool following;
 | 
			
		||||
 | 
			
		||||
  const FriendStatus({
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    @required this.areFriend,
 | 
			
		||||
    @required this.sentRequest,
 | 
			
		||||
    @required this.receivedRequest,
 | 
			
		||||
    @required this.following,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    required this.areFriend,
 | 
			
		||||
    required this.sentRequest,
 | 
			
		||||
    required this.receivedRequest,
 | 
			
		||||
    required this.following,
 | 
			
		||||
  })  : assert(userID != null),
 | 
			
		||||
        assert(areFriend != null),
 | 
			
		||||
        assert(sentRequest != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import 'package:comunic/enums/user_page_visibility.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// General settings
 | 
			
		||||
///
 | 
			
		||||
@@ -18,22 +17,22 @@ class GeneralSettings {
 | 
			
		||||
  String virtualDirectory;
 | 
			
		||||
  String personalWebsite;
 | 
			
		||||
  String publicNote;
 | 
			
		||||
  String location;
 | 
			
		||||
  String? location;
 | 
			
		||||
 | 
			
		||||
  GeneralSettings({
 | 
			
		||||
    @required this.email,
 | 
			
		||||
    @required this.firstName,
 | 
			
		||||
    @required this.lastName,
 | 
			
		||||
    @required this.pageVisibility,
 | 
			
		||||
    @required this.allowComments,
 | 
			
		||||
    @required this.allowPostsFromFriends,
 | 
			
		||||
    @required this.allowComunicEmails,
 | 
			
		||||
    @required this.publicFriendsList,
 | 
			
		||||
    @required this.publicEmail,
 | 
			
		||||
    @required this.virtualDirectory,
 | 
			
		||||
    @required this.personalWebsite,
 | 
			
		||||
    @required this.publicNote,
 | 
			
		||||
    @required this.location,
 | 
			
		||||
    required this.email,
 | 
			
		||||
    required this.firstName,
 | 
			
		||||
    required this.lastName,
 | 
			
		||||
    required this.pageVisibility,
 | 
			
		||||
    required this.allowComments,
 | 
			
		||||
    required this.allowPostsFromFriends,
 | 
			
		||||
    required this.allowComunicEmails,
 | 
			
		||||
    required this.publicFriendsList,
 | 
			
		||||
    required this.publicEmail,
 | 
			
		||||
    required this.virtualDirectory,
 | 
			
		||||
    required this.personalWebsite,
 | 
			
		||||
    required this.publicNote,
 | 
			
		||||
    required this.location,
 | 
			
		||||
  })  : assert(email != null),
 | 
			
		||||
        assert(firstName != null),
 | 
			
		||||
        assert(lastName != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import 'package:comunic/utils/intl_utils.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Group information
 | 
			
		||||
///
 | 
			
		||||
@@ -14,20 +13,20 @@ enum GroupMembershipLevel {
 | 
			
		||||
  VISITOR
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String/*!*/ membershipToText(GroupMembershipLevel level) {
 | 
			
		||||
String membershipToText(GroupMembershipLevel level) {
 | 
			
		||||
  switch (level) {
 | 
			
		||||
    case GroupMembershipLevel.ADMINISTRATOR:
 | 
			
		||||
      return tr("Administrator");
 | 
			
		||||
      return tr("Administrator")!;
 | 
			
		||||
    case GroupMembershipLevel.MODERATOR:
 | 
			
		||||
      return tr("Moderator");
 | 
			
		||||
      return tr("Moderator")!;
 | 
			
		||||
    case GroupMembershipLevel.MEMBER:
 | 
			
		||||
      return tr("Member");
 | 
			
		||||
      return tr("Member")!;
 | 
			
		||||
    case GroupMembershipLevel.INVITED:
 | 
			
		||||
      return tr("Invited");
 | 
			
		||||
      return tr("Invited")!;
 | 
			
		||||
    case GroupMembershipLevel.PENDING:
 | 
			
		||||
      return tr("Requested");
 | 
			
		||||
      return tr("Requested")!;
 | 
			
		||||
    case GroupMembershipLevel.VISITOR:
 | 
			
		||||
      return tr("Visitor");
 | 
			
		||||
      return tr("Visitor")!;
 | 
			
		||||
  }
 | 
			
		||||
  throw new Exception("Unreachable statement!");
 | 
			
		||||
}
 | 
			
		||||
@@ -51,16 +50,16 @@ class Group implements Comparable<Group> {
 | 
			
		||||
  bool following;
 | 
			
		||||
 | 
			
		||||
  Group({
 | 
			
		||||
    @required this.id,
 | 
			
		||||
    @required this.name,
 | 
			
		||||
    @required this.iconURL,
 | 
			
		||||
    @required this.numberMembers,
 | 
			
		||||
    @required this.membershipLevel,
 | 
			
		||||
    @required this.visibilityLevel,
 | 
			
		||||
    @required this.registrationLevel,
 | 
			
		||||
    @required this.postCreationLevel,
 | 
			
		||||
    @required this.virtualDirectory,
 | 
			
		||||
    @required this.following,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.iconURL,
 | 
			
		||||
    required this.numberMembers,
 | 
			
		||||
    required this.membershipLevel,
 | 
			
		||||
    required this.visibilityLevel,
 | 
			
		||||
    required this.registrationLevel,
 | 
			
		||||
    required this.postCreationLevel,
 | 
			
		||||
    required this.virtualDirectory,
 | 
			
		||||
    required this.following,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(name != null),
 | 
			
		||||
        assert(iconURL != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import 'package:comunic/models/group.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Group membership information
 | 
			
		||||
///
 | 
			
		||||
@@ -12,10 +11,10 @@ class GroupMembership {
 | 
			
		||||
  final GroupMembershipLevel level;
 | 
			
		||||
 | 
			
		||||
  const GroupMembership({
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    @required this.groupID,
 | 
			
		||||
    @required this.timeCreate,
 | 
			
		||||
    @required this.level,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    required this.groupID,
 | 
			
		||||
    required this.timeCreate,
 | 
			
		||||
    required this.level,
 | 
			
		||||
  })  : assert(userID != null),
 | 
			
		||||
        assert(groupID != null),
 | 
			
		||||
        assert(timeCreate != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,6 @@ abstract class LikeElement {
 | 
			
		||||
 | 
			
		||||
  int get id;
 | 
			
		||||
 | 
			
		||||
  bool userLike;
 | 
			
		||||
  int likes;
 | 
			
		||||
  late bool userLike;
 | 
			
		||||
  late int likes;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import 'package:comunic/models/conversation.dart';
 | 
			
		||||
import 'package:comunic/models/friend.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Membership information
 | 
			
		||||
///
 | 
			
		||||
@@ -10,40 +9,40 @@ enum MembershipType { FRIEND, GROUP, CONVERSATION }
 | 
			
		||||
 | 
			
		||||
class Membership {
 | 
			
		||||
  final MembershipType type;
 | 
			
		||||
  final Conversation conversation;
 | 
			
		||||
  final Friend friend;
 | 
			
		||||
  final int groupID;
 | 
			
		||||
  final int groupLastActive;
 | 
			
		||||
  final Conversation? conversation;
 | 
			
		||||
  final Friend? friend;
 | 
			
		||||
  final int? groupID;
 | 
			
		||||
  final int? groupLastActive;
 | 
			
		||||
 | 
			
		||||
  Membership.conversation(this.conversation)
 | 
			
		||||
  Membership.conversation(Conversation this.conversation)
 | 
			
		||||
      : type = MembershipType.CONVERSATION,
 | 
			
		||||
        friend = null,
 | 
			
		||||
        groupID = null,
 | 
			
		||||
        groupLastActive = null,
 | 
			
		||||
        assert(conversation != null);
 | 
			
		||||
 | 
			
		||||
  Membership.friend(this.friend)
 | 
			
		||||
  Membership.friend(Friend this.friend)
 | 
			
		||||
      : type = MembershipType.FRIEND,
 | 
			
		||||
        conversation = null,
 | 
			
		||||
        groupID = null,
 | 
			
		||||
        groupLastActive = null,
 | 
			
		||||
        assert(friend != null);
 | 
			
		||||
 | 
			
		||||
  Membership.group({@required this.groupID, @required this.groupLastActive})
 | 
			
		||||
  Membership.group({required int this.groupID, required int this.groupLastActive})
 | 
			
		||||
      : type = MembershipType.GROUP,
 | 
			
		||||
        conversation = null,
 | 
			
		||||
        friend = null,
 | 
			
		||||
        assert(groupID != null),
 | 
			
		||||
        assert(groupLastActive != null);
 | 
			
		||||
 | 
			
		||||
  int get lastActive {
 | 
			
		||||
  int? get lastActive {
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case MembershipType.FRIEND:
 | 
			
		||||
        return friend.lastActive;
 | 
			
		||||
        return friend!.lastActive;
 | 
			
		||||
      case MembershipType.GROUP:
 | 
			
		||||
        return groupLastActive;
 | 
			
		||||
      case MembershipType.CONVERSATION:
 | 
			
		||||
        return conversation.lastActivity;
 | 
			
		||||
        return conversation!.lastActivity;
 | 
			
		||||
      default:
 | 
			
		||||
        throw Exception("Unreachable statment!");
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:flutter/widgets.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// New account information container
 | 
			
		||||
///
 | 
			
		||||
@@ -11,10 +11,10 @@ class NewAccount {
 | 
			
		||||
  final String password;
 | 
			
		||||
 | 
			
		||||
  NewAccount({
 | 
			
		||||
    @required this.firstName,
 | 
			
		||||
    @required this.lastName,
 | 
			
		||||
    @required this.email,
 | 
			
		||||
    @required this.password,
 | 
			
		||||
    required this.firstName,
 | 
			
		||||
    required this.lastName,
 | 
			
		||||
    required this.email,
 | 
			
		||||
    required this.password,
 | 
			
		||||
  })  : assert(firstName != null),
 | 
			
		||||
        assert(lastName != null),
 | 
			
		||||
        assert(email != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
import 'api_request.dart';
 | 
			
		||||
 | 
			
		||||
/// New comment information
 | 
			
		||||
@@ -9,12 +7,12 @@ import 'api_request.dart';
 | 
			
		||||
class NewComment {
 | 
			
		||||
  final int postID;
 | 
			
		||||
  final String content;
 | 
			
		||||
  final BytesFile image;
 | 
			
		||||
  final BytesFile? image;
 | 
			
		||||
 | 
			
		||||
  const NewComment({
 | 
			
		||||
    @required this.postID,
 | 
			
		||||
    @required this.content,
 | 
			
		||||
    @required this.image,
 | 
			
		||||
    required this.postID,
 | 
			
		||||
    required this.content,
 | 
			
		||||
    required this.image,
 | 
			
		||||
  }) : assert(postID != null);
 | 
			
		||||
 | 
			
		||||
  bool get hasContent => content != null && content.length > 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,17 +6,17 @@ import 'package:flutter/cupertino.dart';
 | 
			
		||||
 | 
			
		||||
class NewConversation {
 | 
			
		||||
  final String name;
 | 
			
		||||
  final List<int> members;
 | 
			
		||||
  final List<int?> members;
 | 
			
		||||
  final bool follow;
 | 
			
		||||
  final bool canEveryoneAddMembers;
 | 
			
		||||
  final Color color;
 | 
			
		||||
  final Color? color;
 | 
			
		||||
 | 
			
		||||
  const NewConversation({
 | 
			
		||||
    @required this.name,
 | 
			
		||||
    @required this.members,
 | 
			
		||||
    @required this.follow,
 | 
			
		||||
    @required this.canEveryoneAddMembers,
 | 
			
		||||
    @required this.color,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.members,
 | 
			
		||||
    required this.follow,
 | 
			
		||||
    required this.canEveryoneAddMembers,
 | 
			
		||||
    required this.color,
 | 
			
		||||
  })  : assert(members != null),
 | 
			
		||||
        assert(members.length > 0),
 | 
			
		||||
        assert(follow != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import 'package:comunic/models/api_request.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// New conversation message model
 | 
			
		||||
///
 | 
			
		||||
@@ -9,13 +8,13 @@ import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
class NewConversationMessage {
 | 
			
		||||
  final int conversationID;
 | 
			
		||||
  final String message;
 | 
			
		||||
  final BytesFile file;
 | 
			
		||||
  final BytesFile thumbnail;
 | 
			
		||||
  final String? message;
 | 
			
		||||
  final BytesFile? file;
 | 
			
		||||
  final BytesFile? thumbnail;
 | 
			
		||||
 | 
			
		||||
  NewConversationMessage({
 | 
			
		||||
    @required this.conversationID,
 | 
			
		||||
    @required this.message,
 | 
			
		||||
    required this.conversationID,
 | 
			
		||||
    required this.message,
 | 
			
		||||
    this.file,
 | 
			
		||||
    this.thumbnail,
 | 
			
		||||
  })  : assert(conversationID != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -9,16 +9,16 @@ class NewConversationsSettings {
 | 
			
		||||
  final bool following;
 | 
			
		||||
  final bool isComplete;
 | 
			
		||||
  final String name;
 | 
			
		||||
  final bool canEveryoneAddMembers;
 | 
			
		||||
  final Color color;
 | 
			
		||||
  final bool? canEveryoneAddMembers;
 | 
			
		||||
  final Color? color;
 | 
			
		||||
 | 
			
		||||
  const NewConversationsSettings({
 | 
			
		||||
    @required this.convID,
 | 
			
		||||
    @required this.following,
 | 
			
		||||
    @required this.isComplete,
 | 
			
		||||
    @required this.name,
 | 
			
		||||
    @required this.canEveryoneAddMembers,
 | 
			
		||||
    @required this.color,
 | 
			
		||||
    required this.convID,
 | 
			
		||||
    required this.following,
 | 
			
		||||
    required this.isComplete,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.canEveryoneAddMembers,
 | 
			
		||||
    required this.color,
 | 
			
		||||
  })  : assert(convID != null),
 | 
			
		||||
        assert(convID > 0),
 | 
			
		||||
        assert(following != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'api_request.dart';
 | 
			
		||||
 | 
			
		||||
/// New emoji information
 | 
			
		||||
@@ -11,8 +9,8 @@ class NewEmoji {
 | 
			
		||||
  final BytesFile image;
 | 
			
		||||
 | 
			
		||||
  const NewEmoji({
 | 
			
		||||
    @required this.shortcut,
 | 
			
		||||
    @required this.image,
 | 
			
		||||
    required this.shortcut,
 | 
			
		||||
    required this.image,
 | 
			
		||||
  })  : assert(shortcut != null),
 | 
			
		||||
        assert(image != null);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import 'package:comunic/models/group.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
 | 
			
		||||
/// This class contains information about a conversation linked to a group
 | 
			
		||||
/// to create
 | 
			
		||||
@@ -12,9 +11,9 @@ class NewGroupConversation {
 | 
			
		||||
  final GroupMembershipLevel minMembershipLevel;
 | 
			
		||||
 | 
			
		||||
  const NewGroupConversation({
 | 
			
		||||
    @required this.groupID,
 | 
			
		||||
    @required this.name,
 | 
			
		||||
    @required this.minMembershipLevel,
 | 
			
		||||
    required this.groupID,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.minMembershipLevel,
 | 
			
		||||
  })  : assert(groupID != null),
 | 
			
		||||
        assert(name != null),
 | 
			
		||||
        assert(minMembershipLevel != null);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import 'package:comunic/enums/post_kind.dart';
 | 
			
		||||
import 'package:comunic/enums/post_target.dart';
 | 
			
		||||
import 'package:comunic/enums/post_visibility_level.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
import 'api_request.dart';
 | 
			
		||||
 | 
			
		||||
@@ -15,9 +14,9 @@ class NewSurvey {
 | 
			
		||||
  final bool allowNewChoicesCreation;
 | 
			
		||||
 | 
			
		||||
  const NewSurvey({
 | 
			
		||||
    @required this.question,
 | 
			
		||||
    @required this.answers,
 | 
			
		||||
    @required this.allowNewChoicesCreation,
 | 
			
		||||
    required this.question,
 | 
			
		||||
    required this.answers,
 | 
			
		||||
    required this.allowNewChoicesCreation,
 | 
			
		||||
  })  : assert(question != null),
 | 
			
		||||
        assert(answers.length > 1),
 | 
			
		||||
        assert(allowNewChoicesCreation != null);
 | 
			
		||||
@@ -28,26 +27,26 @@ class NewPost {
 | 
			
		||||
  final int targetID;
 | 
			
		||||
  final PostVisibilityLevel visibility;
 | 
			
		||||
  final String content;
 | 
			
		||||
  final BytesFile image;
 | 
			
		||||
  final String url;
 | 
			
		||||
  final List<int> pdf;
 | 
			
		||||
  final BytesFile? image;
 | 
			
		||||
  final String? url;
 | 
			
		||||
  final List<int>? pdf;
 | 
			
		||||
  final PostKind kind;
 | 
			
		||||
  final DateTime timeEnd;
 | 
			
		||||
  final NewSurvey survey;
 | 
			
		||||
  final String youtubeId;
 | 
			
		||||
  final DateTime? timeEnd;
 | 
			
		||||
  final NewSurvey? survey;
 | 
			
		||||
  final String? youtubeId;
 | 
			
		||||
 | 
			
		||||
  const NewPost({
 | 
			
		||||
    @required this.target,
 | 
			
		||||
    @required this.targetID,
 | 
			
		||||
    @required this.visibility,
 | 
			
		||||
    @required this.content,
 | 
			
		||||
    @required this.kind,
 | 
			
		||||
    @required this.image,
 | 
			
		||||
    @required this.url,
 | 
			
		||||
    @required this.pdf,
 | 
			
		||||
    @required this.timeEnd,
 | 
			
		||||
    @required this.survey,
 | 
			
		||||
    @required this.youtubeId,
 | 
			
		||||
    required this.target,
 | 
			
		||||
    required this.targetID,
 | 
			
		||||
    required this.visibility,
 | 
			
		||||
    required this.content,
 | 
			
		||||
    required this.kind,
 | 
			
		||||
    required this.image,
 | 
			
		||||
    required this.url,
 | 
			
		||||
    required this.pdf,
 | 
			
		||||
    required this.timeEnd,
 | 
			
		||||
    required this.survey,
 | 
			
		||||
    required this.youtubeId,
 | 
			
		||||
  })  : assert(target != null),
 | 
			
		||||
        assert(targetID != null),
 | 
			
		||||
        assert(visibility != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:flutter/widgets.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Notification model
 | 
			
		||||
///
 | 
			
		||||
@@ -45,19 +45,19 @@ class Notification {
 | 
			
		||||
  final int onElemId;
 | 
			
		||||
  final NotificationElementType onElemType;
 | 
			
		||||
  final NotificationType type;
 | 
			
		||||
  final int fromContainerId;
 | 
			
		||||
  final NotificationElementType fromContainerType;
 | 
			
		||||
  final int? fromContainerId;
 | 
			
		||||
  final NotificationElementType? fromContainerType;
 | 
			
		||||
 | 
			
		||||
  const Notification({
 | 
			
		||||
    @required this.id,
 | 
			
		||||
    @required this.timeCreate,
 | 
			
		||||
    @required this.seen,
 | 
			
		||||
    @required this.fromUser,
 | 
			
		||||
    @required this.onElemId,
 | 
			
		||||
    @required this.onElemType,
 | 
			
		||||
    @required this.type,
 | 
			
		||||
    @required this.fromContainerId,
 | 
			
		||||
    @required this.fromContainerType,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.timeCreate,
 | 
			
		||||
    required this.seen,
 | 
			
		||||
    required this.fromUser,
 | 
			
		||||
    required this.onElemId,
 | 
			
		||||
    required this.onElemType,
 | 
			
		||||
    required this.type,
 | 
			
		||||
    required this.fromContainerId,
 | 
			
		||||
    required this.fromContainerType,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(timeCreate != null),
 | 
			
		||||
        assert(seen != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Notifications settings
 | 
			
		||||
///
 | 
			
		||||
@@ -9,8 +9,8 @@ class NotificationsSettings {
 | 
			
		||||
  bool allowNotificationsSound;
 | 
			
		||||
 | 
			
		||||
  NotificationsSettings({
 | 
			
		||||
    @required this.allowConversations,
 | 
			
		||||
    @required this.allowNotificationsSound,
 | 
			
		||||
    required this.allowConversations,
 | 
			
		||||
    required this.allowNotificationsSound,
 | 
			
		||||
  })  : assert(allowConversations != null),
 | 
			
		||||
        assert(allowNotificationsSound != null);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ import 'package:comunic/lists/comments_list.dart';
 | 
			
		||||
import 'package:comunic/models/displayed_content.dart';
 | 
			
		||||
import 'package:comunic/models/like_element.dart';
 | 
			
		||||
import 'package:comunic/models/survey.dart';
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Single post information
 | 
			
		||||
///
 | 
			
		||||
@@ -15,50 +14,50 @@ import 'package:meta/meta.dart';
 | 
			
		||||
class Post implements LikeElement {
 | 
			
		||||
  final int id;
 | 
			
		||||
  final int userID;
 | 
			
		||||
  final int userPageID;
 | 
			
		||||
  final int groupID;
 | 
			
		||||
  final int? userPageID;
 | 
			
		||||
  final int? groupID;
 | 
			
		||||
  final int timeSent;
 | 
			
		||||
  DisplayedString content;
 | 
			
		||||
  PostVisibilityLevel visibilityLevel;
 | 
			
		||||
  final PostKind kind;
 | 
			
		||||
  final int fileSize;
 | 
			
		||||
  final String fileType;
 | 
			
		||||
  final String filePath;
 | 
			
		||||
  final String fileURL;
 | 
			
		||||
  final int timeEnd;
 | 
			
		||||
  final String linkURL;
 | 
			
		||||
  final String linkTitle;
 | 
			
		||||
  final String linkDescription;
 | 
			
		||||
  final String linkImage;
 | 
			
		||||
  final int? fileSize;
 | 
			
		||||
  final String? fileType;
 | 
			
		||||
  final String? filePath;
 | 
			
		||||
  final String? fileURL;
 | 
			
		||||
  final int? timeEnd;
 | 
			
		||||
  final String? linkURL;
 | 
			
		||||
  final String? linkTitle;
 | 
			
		||||
  final String? linkDescription;
 | 
			
		||||
  final String? linkImage;
 | 
			
		||||
  int likes;
 | 
			
		||||
  bool userLike;
 | 
			
		||||
  final UserAccessLevels access;
 | 
			
		||||
  final CommentsList comments;
 | 
			
		||||
  Survey survey;
 | 
			
		||||
  final CommentsList? comments;
 | 
			
		||||
  Survey? survey;
 | 
			
		||||
 | 
			
		||||
  Post(
 | 
			
		||||
      {@required this.id,
 | 
			
		||||
      @required this.userID,
 | 
			
		||||
      @required this.userPageID,
 | 
			
		||||
      @required this.groupID,
 | 
			
		||||
      @required this.timeSent,
 | 
			
		||||
      @required this.content,
 | 
			
		||||
      @required this.visibilityLevel,
 | 
			
		||||
      @required this.kind,
 | 
			
		||||
      @required this.fileSize,
 | 
			
		||||
      @required this.fileType,
 | 
			
		||||
      @required this.filePath,
 | 
			
		||||
      @required this.fileURL,
 | 
			
		||||
      @required this.timeEnd,
 | 
			
		||||
      @required this.linkURL,
 | 
			
		||||
      @required this.linkTitle,
 | 
			
		||||
      @required this.linkDescription,
 | 
			
		||||
      @required this.linkImage,
 | 
			
		||||
      @required this.likes,
 | 
			
		||||
      @required this.userLike,
 | 
			
		||||
      @required this.access,
 | 
			
		||||
      @required this.comments,
 | 
			
		||||
      @required this.survey})
 | 
			
		||||
      {required this.id,
 | 
			
		||||
      required this.userID,
 | 
			
		||||
      required this.userPageID,
 | 
			
		||||
      required this.groupID,
 | 
			
		||||
      required this.timeSent,
 | 
			
		||||
      required this.content,
 | 
			
		||||
      required this.visibilityLevel,
 | 
			
		||||
      required this.kind,
 | 
			
		||||
      required this.fileSize,
 | 
			
		||||
      required this.fileType,
 | 
			
		||||
      required this.filePath,
 | 
			
		||||
      required this.fileURL,
 | 
			
		||||
      required this.timeEnd,
 | 
			
		||||
      required this.linkURL,
 | 
			
		||||
      required this.linkTitle,
 | 
			
		||||
      required this.linkDescription,
 | 
			
		||||
      required this.linkImage,
 | 
			
		||||
      required this.likes,
 | 
			
		||||
      required this.userLike,
 | 
			
		||||
      required this.access,
 | 
			
		||||
      required this.comments,
 | 
			
		||||
      required this.survey})
 | 
			
		||||
      : assert(id != null),
 | 
			
		||||
        assert(userID != null),
 | 
			
		||||
        assert(userPageID != 0 || groupID != 0),
 | 
			
		||||
@@ -72,7 +71,7 @@ class Post implements LikeElement {
 | 
			
		||||
        assert(userLike != null),
 | 
			
		||||
        assert(access != null);
 | 
			
		||||
 | 
			
		||||
  bool get isGroupPost => groupID != null && groupID > 0;
 | 
			
		||||
  bool get isGroupPost => groupID != null && groupID! > 0;
 | 
			
		||||
 | 
			
		||||
  bool get hasContent => content != null && !content.isNull;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Check password reset token result
 | 
			
		||||
///
 | 
			
		||||
@@ -10,9 +10,9 @@ class ResCheckPasswordToken {
 | 
			
		||||
  final String email;
 | 
			
		||||
 | 
			
		||||
  const ResCheckPasswordToken({
 | 
			
		||||
    @required this.firstName,
 | 
			
		||||
    @required this.lastName,
 | 
			
		||||
    @required this.email,
 | 
			
		||||
    required this.firstName,
 | 
			
		||||
    required this.lastName,
 | 
			
		||||
    required this.email,
 | 
			
		||||
  })  : assert(firstName != null),
 | 
			
		||||
        assert(lastName != null),
 | 
			
		||||
        assert(email != null);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Single search result
 | 
			
		||||
///
 | 
			
		||||
@@ -11,8 +11,8 @@ class SearchResult {
 | 
			
		||||
  final SearchResultKind kind;
 | 
			
		||||
 | 
			
		||||
  SearchResult({
 | 
			
		||||
    @required this.id,
 | 
			
		||||
    @required this.kind,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.kind,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(kind != null);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Security settings of the user
 | 
			
		||||
///
 | 
			
		||||
@@ -11,10 +11,10 @@ class SecuritySettings {
 | 
			
		||||
  final String securityAnswer2;
 | 
			
		||||
 | 
			
		||||
  const SecuritySettings({
 | 
			
		||||
    @required this.securityQuestion1,
 | 
			
		||||
    @required this.securityAnswer1,
 | 
			
		||||
    @required this.securityQuestion2,
 | 
			
		||||
    @required this.securityAnswer2,
 | 
			
		||||
    required this.securityQuestion1,
 | 
			
		||||
    required this.securityAnswer1,
 | 
			
		||||
    required this.securityQuestion2,
 | 
			
		||||
    required this.securityAnswer2,
 | 
			
		||||
  })  : assert(securityQuestion1 != null),
 | 
			
		||||
        assert(securityAnswer1 != null),
 | 
			
		||||
        assert(securityQuestion2 != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import 'package:comunic/utils/date_utils.dart';
 | 
			
		||||
import 'package:flutter/widgets.dart';
 | 
			
		||||
import 'package:version/version.dart';
 | 
			
		||||
 | 
			
		||||
/// Server static configuration
 | 
			
		||||
@@ -11,8 +10,8 @@ class NotificationsPolicy {
 | 
			
		||||
  final bool hasIndependent;
 | 
			
		||||
 | 
			
		||||
  const NotificationsPolicy({
 | 
			
		||||
    @required this.hasFirebase,
 | 
			
		||||
    @required this.hasIndependent,
 | 
			
		||||
    required this.hasFirebase,
 | 
			
		||||
    required this.hasIndependent,
 | 
			
		||||
  })  : assert(hasFirebase != null),
 | 
			
		||||
        assert(hasIndependent != null);
 | 
			
		||||
}
 | 
			
		||||
@@ -28,14 +27,14 @@ class PasswordPolicy {
 | 
			
		||||
  final int minCategoriesPresence;
 | 
			
		||||
 | 
			
		||||
  const PasswordPolicy({
 | 
			
		||||
    @required this.allowMailInPassword,
 | 
			
		||||
    @required this.allowNameInPassword,
 | 
			
		||||
    @required this.minPasswordLength,
 | 
			
		||||
    @required this.minNumberUpperCaseLetters,
 | 
			
		||||
    @required this.minNumberLowerCaseLetters,
 | 
			
		||||
    @required this.minNumberDigits,
 | 
			
		||||
    @required this.minNumberSpecialCharacters,
 | 
			
		||||
    @required this.minCategoriesPresence,
 | 
			
		||||
    required this.allowMailInPassword,
 | 
			
		||||
    required this.allowNameInPassword,
 | 
			
		||||
    required this.minPasswordLength,
 | 
			
		||||
    required this.minNumberUpperCaseLetters,
 | 
			
		||||
    required this.minNumberLowerCaseLetters,
 | 
			
		||||
    required this.minNumberDigits,
 | 
			
		||||
    required this.minNumberSpecialCharacters,
 | 
			
		||||
    required this.minCategoriesPresence,
 | 
			
		||||
  })  : assert(allowMailInPassword != null),
 | 
			
		||||
        assert(allowNameInPassword != null),
 | 
			
		||||
        assert(minPasswordLength != null),
 | 
			
		||||
@@ -55,12 +54,12 @@ class ServerDataConservationPolicy {
 | 
			
		||||
  final int minLikesLifetime;
 | 
			
		||||
 | 
			
		||||
  const ServerDataConservationPolicy({
 | 
			
		||||
    @required this.minInactiveAccountLifetime,
 | 
			
		||||
    @required this.minNotificationLifetime,
 | 
			
		||||
    @required this.minCommentsLifetime,
 | 
			
		||||
    @required this.minPostsLifetime,
 | 
			
		||||
    @required this.minConversationMessagesLifetime,
 | 
			
		||||
    @required this.minLikesLifetime,
 | 
			
		||||
    required this.minInactiveAccountLifetime,
 | 
			
		||||
    required this.minNotificationLifetime,
 | 
			
		||||
    required this.minCommentsLifetime,
 | 
			
		||||
    required this.minPostsLifetime,
 | 
			
		||||
    required this.minConversationMessagesLifetime,
 | 
			
		||||
    required this.minLikesLifetime,
 | 
			
		||||
  })  : assert(minInactiveAccountLifetime != null),
 | 
			
		||||
        assert(minNotificationLifetime != null),
 | 
			
		||||
        assert(minCommentsLifetime != null),
 | 
			
		||||
@@ -85,19 +84,19 @@ class ConversationsPolicy {
 | 
			
		||||
  final int maxLogoHeight;
 | 
			
		||||
 | 
			
		||||
  const ConversationsPolicy({
 | 
			
		||||
    @required this.maxConversationNameLen,
 | 
			
		||||
    @required this.minMessageLen,
 | 
			
		||||
    @required this.maxMessageLen,
 | 
			
		||||
    @required this.allowedFilesType,
 | 
			
		||||
    @required this.filesMaxSize,
 | 
			
		||||
    @required this.writingEventInterval,
 | 
			
		||||
    @required this.writingEventLifetime,
 | 
			
		||||
    @required this.maxMessageImageWidth,
 | 
			
		||||
    @required this.maxMessageImageHeight,
 | 
			
		||||
    @required this.maxThumbnailWidth,
 | 
			
		||||
    @required this.maxThumbnailHeight,
 | 
			
		||||
    @required this.maxLogoWidth,
 | 
			
		||||
    @required this.maxLogoHeight,
 | 
			
		||||
    required this.maxConversationNameLen,
 | 
			
		||||
    required this.minMessageLen,
 | 
			
		||||
    required this.maxMessageLen,
 | 
			
		||||
    required this.allowedFilesType,
 | 
			
		||||
    required this.filesMaxSize,
 | 
			
		||||
    required this.writingEventInterval,
 | 
			
		||||
    required this.writingEventLifetime,
 | 
			
		||||
    required this.maxMessageImageWidth,
 | 
			
		||||
    required this.maxMessageImageHeight,
 | 
			
		||||
    required this.maxThumbnailWidth,
 | 
			
		||||
    required this.maxThumbnailHeight,
 | 
			
		||||
    required this.maxLogoWidth,
 | 
			
		||||
    required this.maxLogoHeight,
 | 
			
		||||
  })  : assert(maxConversationNameLen != null),
 | 
			
		||||
        assert(minMessageLen != null),
 | 
			
		||||
        assert(maxMessageLen != null),
 | 
			
		||||
@@ -121,11 +120,11 @@ class AccountInformationPolicy {
 | 
			
		||||
  final int maxLocationLength;
 | 
			
		||||
 | 
			
		||||
  const AccountInformationPolicy({
 | 
			
		||||
    @required this.minFirstNameLength,
 | 
			
		||||
    @required this.maxFirstNameLength,
 | 
			
		||||
    @required this.minLastNameLength,
 | 
			
		||||
    @required this.maxLastNameLength,
 | 
			
		||||
    @required this.maxLocationLength,
 | 
			
		||||
    required this.minFirstNameLength,
 | 
			
		||||
    required this.maxFirstNameLength,
 | 
			
		||||
    required this.minLastNameLength,
 | 
			
		||||
    required this.maxLastNameLength,
 | 
			
		||||
    required this.maxLocationLength,
 | 
			
		||||
  })  : assert(minFirstNameLength != null),
 | 
			
		||||
        assert(maxFirstNameLength != null),
 | 
			
		||||
        assert(minLastNameLength != null),
 | 
			
		||||
@@ -136,7 +135,7 @@ class AccountInformationPolicy {
 | 
			
		||||
enum BannerNature { Information, Warning, Success }
 | 
			
		||||
 | 
			
		||||
extension BannerNatureExt on BannerNature {
 | 
			
		||||
  static BannerNature fromStr(String s) {
 | 
			
		||||
  static BannerNature fromStr(String? s) {
 | 
			
		||||
    switch (s) {
 | 
			
		||||
      case "information":
 | 
			
		||||
        return BannerNature.Information;
 | 
			
		||||
@@ -151,22 +150,22 @@ extension BannerNatureExt on BannerNature {
 | 
			
		||||
 | 
			
		||||
class Banner {
 | 
			
		||||
  final bool enabled;
 | 
			
		||||
  final int expire;
 | 
			
		||||
  final int? expire;
 | 
			
		||||
  final BannerNature nature;
 | 
			
		||||
  final Map<String, String> message;
 | 
			
		||||
  final String link;
 | 
			
		||||
  final String? link;
 | 
			
		||||
 | 
			
		||||
  const Banner({
 | 
			
		||||
    @required this.enabled,
 | 
			
		||||
    @required this.expire,
 | 
			
		||||
    @required this.nature,
 | 
			
		||||
    @required this.message,
 | 
			
		||||
    @required this.link,
 | 
			
		||||
    required this.enabled,
 | 
			
		||||
    required this.expire,
 | 
			
		||||
    required this.nature,
 | 
			
		||||
    required this.message,
 | 
			
		||||
    required this.link,
 | 
			
		||||
  })  : assert(enabled != null),
 | 
			
		||||
        assert(nature != null),
 | 
			
		||||
        assert(message != null);
 | 
			
		||||
 | 
			
		||||
  bool get visible => enabled && (expire == null || expire > time());
 | 
			
		||||
  bool get visible => enabled && (expire == null || expire! > time());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ServerConfig {
 | 
			
		||||
@@ -176,7 +175,7 @@ class ServerConfig {
 | 
			
		||||
  final String contactEmail;
 | 
			
		||||
  final String playStoreURL;
 | 
			
		||||
  final String androidDirectDownloadURL;
 | 
			
		||||
  final Banner banner;
 | 
			
		||||
  final Banner? banner;
 | 
			
		||||
  final NotificationsPolicy notificationsPolicy;
 | 
			
		||||
  final PasswordPolicy passwordPolicy;
 | 
			
		||||
  final ServerDataConservationPolicy dataConservationPolicy;
 | 
			
		||||
@@ -184,18 +183,18 @@ class ServerConfig {
 | 
			
		||||
  final AccountInformationPolicy accountInformationPolicy;
 | 
			
		||||
 | 
			
		||||
  const ServerConfig({
 | 
			
		||||
    @required this.minSupportedMobileVersion,
 | 
			
		||||
    @required this.termsURL,
 | 
			
		||||
    @required this.privacyPolicyURL,
 | 
			
		||||
    @required this.contactEmail,
 | 
			
		||||
    @required this.playStoreURL,
 | 
			
		||||
    @required this.androidDirectDownloadURL,
 | 
			
		||||
    @required this.banner,
 | 
			
		||||
    @required this.notificationsPolicy,
 | 
			
		||||
    @required this.passwordPolicy,
 | 
			
		||||
    @required this.dataConservationPolicy,
 | 
			
		||||
    @required this.conversationsPolicy,
 | 
			
		||||
    @required this.accountInformationPolicy,
 | 
			
		||||
    required this.minSupportedMobileVersion,
 | 
			
		||||
    required this.termsURL,
 | 
			
		||||
    required this.privacyPolicyURL,
 | 
			
		||||
    required this.contactEmail,
 | 
			
		||||
    required this.playStoreURL,
 | 
			
		||||
    required this.androidDirectDownloadURL,
 | 
			
		||||
    required this.banner,
 | 
			
		||||
    required this.notificationsPolicy,
 | 
			
		||||
    required this.passwordPolicy,
 | 
			
		||||
    required this.dataConservationPolicy,
 | 
			
		||||
    required this.conversationsPolicy,
 | 
			
		||||
    required this.accountInformationPolicy,
 | 
			
		||||
  })  : assert(minSupportedMobileVersion != null),
 | 
			
		||||
        assert(termsURL != null),
 | 
			
		||||
        assert(privacyPolicyURL != null),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import 'package:comunic/models/survey_choice.dart';
 | 
			
		||||
import 'package:comunic/utils/account_utils.dart' as account;
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Survey information
 | 
			
		||||
///
 | 
			
		||||
@@ -17,14 +16,14 @@ class Survey {
 | 
			
		||||
  bool allowNewChoicesCreation;
 | 
			
		||||
 | 
			
		||||
  Survey({
 | 
			
		||||
    @required this.id,
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    @required this.postID,
 | 
			
		||||
    @required this.creationTime,
 | 
			
		||||
    @required this.question,
 | 
			
		||||
    @required this.userChoice,
 | 
			
		||||
    @required this.choices,
 | 
			
		||||
    @required this.allowNewChoicesCreation,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    required this.postID,
 | 
			
		||||
    required this.creationTime,
 | 
			
		||||
    required this.question,
 | 
			
		||||
    required this.userChoice,
 | 
			
		||||
    required this.choices,
 | 
			
		||||
    required this.allowNewChoicesCreation,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(userID != null),
 | 
			
		||||
        assert(postID != null),
 | 
			
		||||
@@ -43,14 +42,14 @@ class Survey {
 | 
			
		||||
  bool get canBlockNewChoicesCreation =>
 | 
			
		||||
      allowNewChoicesCreation && account.userID() == this.userID;
 | 
			
		||||
 | 
			
		||||
  SurveyChoice get userResponse {
 | 
			
		||||
  SurveyChoice? get userResponse {
 | 
			
		||||
    if (!hasResponded) return null;
 | 
			
		||||
 | 
			
		||||
    return choices.firstWhere((e) => e.id == userChoice);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void cancelUserResponse() {
 | 
			
		||||
    if (hasResponded) userResponse.responses--;
 | 
			
		||||
    if (hasResponded) userResponse!.responses--;
 | 
			
		||||
    userChoice = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// Single survey choice
 | 
			
		||||
///
 | 
			
		||||
@@ -10,9 +10,9 @@ class SurveyChoice {
 | 
			
		||||
  int responses;
 | 
			
		||||
 | 
			
		||||
  SurveyChoice({
 | 
			
		||||
    @required this.id,
 | 
			
		||||
    @required this.name,
 | 
			
		||||
    @required this.responses,
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.name,
 | 
			
		||||
    required this.responses,
 | 
			
		||||
  })  : assert(id != null),
 | 
			
		||||
        assert(name != null),
 | 
			
		||||
        assert(responses != null);
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user