mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-11-04 04:04:18 +00:00 
			
		
		
		
	Start to fix null safety migration errors
This commit is contained in:
		@@ -27,14 +27,14 @@ Color get _headerColor => Colors.blueAccent.shade700;
 | 
			
		||||
 | 
			
		||||
class AuthorizedGroupPageScreen extends StatefulWidget {
 | 
			
		||||
  final AdvancedGroupInfo advancedGroupInfo;
 | 
			
		||||
  final int conversationID;
 | 
			
		||||
  final int? conversationID;
 | 
			
		||||
  final Function() needRefresh;
 | 
			
		||||
 | 
			
		||||
  const AuthorizedGroupPageScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.advancedGroupInfo,
 | 
			
		||||
    @required this.conversationID,
 | 
			
		||||
    @required this.needRefresh,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.advancedGroupInfo,
 | 
			
		||||
    required this.conversationID,
 | 
			
		||||
    required this.needRefresh,
 | 
			
		||||
  })  : assert(advancedGroupInfo != null),
 | 
			
		||||
        assert(needRefresh != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
@@ -49,42 +49,42 @@ class _AuthorizedGroupPageScreenState
 | 
			
		||||
    with SingleTickerProviderStateMixin {
 | 
			
		||||
  AdvancedGroupInfo get _group => widget.advancedGroupInfo;
 | 
			
		||||
 | 
			
		||||
  TabController _tabController;
 | 
			
		||||
  TabController? _tabController;
 | 
			
		||||
 | 
			
		||||
  List<_GroupPageTab> get _tabs => [
 | 
			
		||||
        // Posts list
 | 
			
		||||
        _GroupPageTab(
 | 
			
		||||
          widget: (c) => GroupPostsSection(group: _group),
 | 
			
		||||
          label: tr("Posts"),
 | 
			
		||||
          label: tr("Posts")!,
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Forez presence tab
 | 
			
		||||
        _GroupPageTab(
 | 
			
		||||
          widget: (c) => ForezPresenceSection(groupID: _group.id),
 | 
			
		||||
          label: tr("Presence"),
 | 
			
		||||
          label: tr("Presence")!,
 | 
			
		||||
          visible: _group.isForezGroup,
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // About the group
 | 
			
		||||
        _GroupPageTab(
 | 
			
		||||
          widget: (c) => AboutGroupSection(group: _group),
 | 
			
		||||
          label: tr("About"),
 | 
			
		||||
          label: tr("About")!,
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        _GroupPageTab(
 | 
			
		||||
          widget: (c) => GroupMembersSection(groupID: _group.id),
 | 
			
		||||
          label: tr("Members"),
 | 
			
		||||
          visible: _group.isAtLeastModerator || _group.isMembersListPublic,
 | 
			
		||||
          label: tr("Members")!,
 | 
			
		||||
          visible: _group.isAtLeastModerator || _group.isMembersListPublic!,
 | 
			
		||||
        )
 | 
			
		||||
      ].where((element) => element.visible).toList()
 | 
			
		||||
 | 
			
		||||
        // Add group conversations
 | 
			
		||||
        ..insertAll(
 | 
			
		||||
            (_group.isForezGroup ? 2 : 1),
 | 
			
		||||
            _group.conversations
 | 
			
		||||
            _group.conversations!
 | 
			
		||||
                .map((e) => _GroupPageTab(
 | 
			
		||||
                    widget: (c) => GroupConversationSection(conv: e),
 | 
			
		||||
                    label: e.name))
 | 
			
		||||
                    label: e.name!))
 | 
			
		||||
                .toList());
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -94,7 +94,7 @@ class _AuthorizedGroupPageScreenState
 | 
			
		||||
      initialIndex: widget.conversationID == null
 | 
			
		||||
          ? 0
 | 
			
		||||
          : (_group.isForezGroup ? 2 : 1) +
 | 
			
		||||
              _group.conversations
 | 
			
		||||
              _group.conversations!
 | 
			
		||||
                  .indexWhere((element) => element.id == widget.conversationID),
 | 
			
		||||
      vsync: this,
 | 
			
		||||
    );
 | 
			
		||||
@@ -104,7 +104,7 @@ class _AuthorizedGroupPageScreenState
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _tabController.dispose();
 | 
			
		||||
    _tabController!.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -189,7 +189,7 @@ class _AuthorizedGroupPageScreenState
 | 
			
		||||
              _group.isAdmin
 | 
			
		||||
                  ? IconButton(
 | 
			
		||||
                      icon: Icon(Icons.settings, color: _headerTextColor),
 | 
			
		||||
                      onPressed: () => MainController.of(context).push(
 | 
			
		||||
                      onPressed: () => MainController.of(context)!.push(
 | 
			
		||||
                          GroupSettingsScreen(groupID: _group.id),
 | 
			
		||||
                          canShowAsDialog: true))
 | 
			
		||||
                  : Container(),
 | 
			
		||||
@@ -207,9 +207,9 @@ class _GroupPageTab {
 | 
			
		||||
  final String label;
 | 
			
		||||
 | 
			
		||||
  const _GroupPageTab({
 | 
			
		||||
    @required this.widget,
 | 
			
		||||
    required this.widget,
 | 
			
		||||
    this.visible = true,
 | 
			
		||||
    @required this.label,
 | 
			
		||||
    required this.label,
 | 
			
		||||
  })  : assert(widget != null),
 | 
			
		||||
        assert(visible != null),
 | 
			
		||||
        assert(label != null);
 | 
			
		||||
 
 | 
			
		||||
@@ -33,16 +33,16 @@ class CallScreen extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
  /// Use custom application bar. This function takes as parameter a nullable
 | 
			
		||||
  /// String which is the title of the conversation
 | 
			
		||||
  final PreferredSizeWidget Function(String) buildCustomAppBar;
 | 
			
		||||
  final PreferredSizeWidget Function(String?)? buildCustomAppBar;
 | 
			
		||||
 | 
			
		||||
  /// Execute custom action when the call is closed
 | 
			
		||||
  ///
 | 
			
		||||
  /// The default behavior is to pop the page
 | 
			
		||||
  final void Function() onClose;
 | 
			
		||||
  final void Function()? onClose;
 | 
			
		||||
 | 
			
		||||
  const CallScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.convID,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.convID,
 | 
			
		||||
    this.floatingButtons = true,
 | 
			
		||||
    this.buildCustomAppBar,
 | 
			
		||||
    this.onClose,
 | 
			
		||||
@@ -60,15 +60,15 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
  int get convID => widget.convID;
 | 
			
		||||
 | 
			
		||||
  // State properties
 | 
			
		||||
  Conversation _conversation;
 | 
			
		||||
  String _convName;
 | 
			
		||||
  CallConfig _conf;
 | 
			
		||||
  late Conversation _conversation;
 | 
			
		||||
  String? _convName;
 | 
			
		||||
  late CallConfig _conf;
 | 
			
		||||
  var _error = false;
 | 
			
		||||
  CallMembersList _membersList;
 | 
			
		||||
  UsersList _usersList;
 | 
			
		||||
  final _peersConnections = Map<int, RTCPeerConnection>();
 | 
			
		||||
  final _renderers = Map<int, RTCVideoRenderer>();
 | 
			
		||||
  MediaStream _localStream;
 | 
			
		||||
  CallMembersList? _membersList;
 | 
			
		||||
  late UsersList _usersList;
 | 
			
		||||
  final _peersConnections = Map<int?, RTCPeerConnection>();
 | 
			
		||||
  final _renderers = Map<int?, RTCVideoRenderer>();
 | 
			
		||||
  MediaStream? _localStream;
 | 
			
		||||
  var _isLocalStreamVisible = true;
 | 
			
		||||
  var _hideMenuBars = false;
 | 
			
		||||
 | 
			
		||||
@@ -81,16 +81,16 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
      _conversation.callCapabilities == CallCapabilities.VIDEO;
 | 
			
		||||
 | 
			
		||||
  bool get isStreamingAudio =>
 | 
			
		||||
      _localStream != null && _localStream.getAudioTracks().length > 0;
 | 
			
		||||
      _localStream != null && _localStream!.getAudioTracks().length > 0;
 | 
			
		||||
 | 
			
		||||
  bool get isStreamingVideo =>
 | 
			
		||||
      _localStream != null && _localStream.getVideoTracks().length > 0;
 | 
			
		||||
      _localStream != null && _localStream!.getVideoTracks().length > 0;
 | 
			
		||||
 | 
			
		||||
  bool get isAudioMuted =>
 | 
			
		||||
      isStreamingAudio && !_localStream.getAudioTracks()[0].enabled;
 | 
			
		||||
      isStreamingAudio && !_localStream!.getAudioTracks()[0].enabled;
 | 
			
		||||
 | 
			
		||||
  bool get isVideoMuted =>
 | 
			
		||||
      isStreamingVideo && !_localStream.getVideoTracks()[0].enabled;
 | 
			
		||||
      isStreamingVideo && !_localStream!.getVideoTracks()[0].enabled;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
@@ -137,7 +137,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
        try {
 | 
			
		||||
          if (!_usersList.hasUser(e.userID))
 | 
			
		||||
            _usersList.add(await UsersHelper().getSingleWithThrow(e.userID));
 | 
			
		||||
          setState(() => _membersList.add(CallMember(userID: e.userID)));
 | 
			
		||||
          setState(() => _membersList!.add(CallMember(userID: e.userID!)));
 | 
			
		||||
        } catch (e, stack) {
 | 
			
		||||
          print("$e\n$stack");
 | 
			
		||||
        }
 | 
			
		||||
@@ -164,7 +164,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // Connect to ready peers
 | 
			
		||||
      for (final peer in _membersList.readyPeers)
 | 
			
		||||
      for (final peer in _membersList!.readyPeers)
 | 
			
		||||
        await this._memberReady(peer.userID);
 | 
			
		||||
 | 
			
		||||
      setState(() {});
 | 
			
		||||
@@ -183,7 +183,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
      await setWakeLock(false);
 | 
			
		||||
 | 
			
		||||
      // Close all ready members
 | 
			
		||||
      for (final member in _membersList.readyPeers)
 | 
			
		||||
      for (final member in _membersList!.readyPeers)
 | 
			
		||||
        await _removeMember(member.userID);
 | 
			
		||||
 | 
			
		||||
      // Close local stream
 | 
			
		||||
@@ -204,9 +204,9 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
            message: tr("Do you really want to leave this call ?"))) return;
 | 
			
		||||
 | 
			
		||||
    if (widget.onClose == null)
 | 
			
		||||
      MainController.of(context).popPage();
 | 
			
		||||
      MainController.of(context)!.popPage();
 | 
			
		||||
    else
 | 
			
		||||
      widget.onClose();
 | 
			
		||||
      widget.onClose!();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Start streaming on our end
 | 
			
		||||
@@ -232,8 +232,8 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
 | 
			
		||||
      // Start renderer
 | 
			
		||||
      _renderers[userID()] = RTCVideoRenderer();
 | 
			
		||||
      await _renderers[userID()].initialize();
 | 
			
		||||
      _renderers[userID()].srcObject = _localStream;
 | 
			
		||||
      await _renderers[userID()]!.initialize();
 | 
			
		||||
      _renderers[userID()]!.srcObject = _localStream;
 | 
			
		||||
      setState(() {});
 | 
			
		||||
 | 
			
		||||
      // Open stream
 | 
			
		||||
@@ -244,8 +244,8 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
 | 
			
		||||
      _peersConnections[userID()] = peerConnection;
 | 
			
		||||
 | 
			
		||||
      for (final track in _localStream.getTracks()) {
 | 
			
		||||
        await peerConnection.addTrack(track, _localStream);
 | 
			
		||||
      for (final track in _localStream!.getTracks()) {
 | 
			
		||||
        await peerConnection.addTrack(track, _localStream!);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      peerConnection.onAddStream =
 | 
			
		||||
@@ -275,7 +275,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
      await CallsHelper.sendSessionDescription(convID, userID(), offer);
 | 
			
		||||
    } catch (e, stack) {
 | 
			
		||||
      print("Could not start streaming! $e\n$stack");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not start streaming!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not start streaming!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -283,7 +283,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
  Future<void> _stopStreaming() async {
 | 
			
		||||
    // Close peer connection
 | 
			
		||||
    if (_peersConnections.containsKey(userID())) {
 | 
			
		||||
      _peersConnections[userID()].close();
 | 
			
		||||
      _peersConnections[userID()]!.close();
 | 
			
		||||
      _peersConnections.remove(userID());
 | 
			
		||||
 | 
			
		||||
      await CallsHelper.notifyStoppedStreaming(convID);
 | 
			
		||||
@@ -291,13 +291,13 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
 | 
			
		||||
    // Stop local stream
 | 
			
		||||
    if (_localStream != null) {
 | 
			
		||||
      await _localStream.dispose();
 | 
			
		||||
      await _localStream!.dispose();
 | 
			
		||||
      _localStream = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Close renderer
 | 
			
		||||
    if (_renderers.containsKey(userID())) {
 | 
			
		||||
      await _renderers[userID()].dispose();
 | 
			
		||||
      await _renderers[userID()]!.dispose();
 | 
			
		||||
      _renderers.remove(userID());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -324,20 +324,20 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
    // Toggle appropriate mute
 | 
			
		||||
    else {
 | 
			
		||||
      if (!isVideo)
 | 
			
		||||
        _localStream.getAudioTracks()[0].enabled =
 | 
			
		||||
            !_localStream.getAudioTracks()[0].enabled;
 | 
			
		||||
        _localStream!.getAudioTracks()[0].enabled =
 | 
			
		||||
            !_localStream!.getAudioTracks()[0].enabled;
 | 
			
		||||
      else
 | 
			
		||||
        _localStream.getVideoTracks()[0].enabled =
 | 
			
		||||
            !_localStream.getVideoTracks()[0].enabled;
 | 
			
		||||
        _localStream!.getVideoTracks()[0].enabled =
 | 
			
		||||
            !_localStream!.getVideoTracks()[0].enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setState(() {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Call this when a user started to stream media
 | 
			
		||||
  Future<void> _memberReady(int memberID) async {
 | 
			
		||||
  Future<void> _memberReady(int? memberID) async {
 | 
			
		||||
    try {
 | 
			
		||||
      _membersList.getUser(memberID).status = MemberStatus.READY;
 | 
			
		||||
      _membersList!.getUser(memberID).status = MemberStatus.READY;
 | 
			
		||||
      setState(() {});
 | 
			
		||||
 | 
			
		||||
      // Create peer connection
 | 
			
		||||
@@ -353,15 +353,15 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
 | 
			
		||||
      // Create a renderer
 | 
			
		||||
      _renderers[memberID] = RTCVideoRenderer();
 | 
			
		||||
      await _renderers[memberID].initialize();
 | 
			
		||||
      await _renderers[memberID]!.initialize();
 | 
			
		||||
 | 
			
		||||
      // Register callbacks
 | 
			
		||||
      peerConnection.onIceCandidate =
 | 
			
		||||
          (c) => CallsHelper.sendIceCandidate(convID, memberID, c);
 | 
			
		||||
      peerConnection.onAddStream = (s) {
 | 
			
		||||
        setState(() {
 | 
			
		||||
          _membersList.getUser(memberID).stream = s;
 | 
			
		||||
          _renderers[memberID].srcObject = s;
 | 
			
		||||
          _membersList!.getUser(memberID).stream = s;
 | 
			
		||||
          _renderers[memberID]!.srcObject = s;
 | 
			
		||||
        });
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
@@ -371,7 +371,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
      setState(() {});
 | 
			
		||||
    } catch (e, stack) {
 | 
			
		||||
      print("Could not connect to remote peer $e\n$stack!");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not connect to a remote peer!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not connect to a remote peer!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -388,13 +388,13 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
      // Check the kind of signal
 | 
			
		||||
      // SessionDescription
 | 
			
		||||
      if (ev.sessionDescription != null) {
 | 
			
		||||
        await _peersConnections[ev.peerID]
 | 
			
		||||
            .setRemoteDescription(ev.sessionDescription);
 | 
			
		||||
        await _peersConnections[ev.peerID]!
 | 
			
		||||
            .setRemoteDescription(ev.sessionDescription!);
 | 
			
		||||
 | 
			
		||||
        // Send answer if required
 | 
			
		||||
        if (ev.sessionDescription.type == "offer") {
 | 
			
		||||
          final answer = await _peersConnections[ev.peerID].createAnswer({});
 | 
			
		||||
          await _peersConnections[ev.peerID].setLocalDescription(answer);
 | 
			
		||||
        if (ev.sessionDescription!.type == "offer") {
 | 
			
		||||
          final answer = await _peersConnections[ev.peerID]!.createAnswer({});
 | 
			
		||||
          await _peersConnections[ev.peerID]!.setLocalDescription(answer);
 | 
			
		||||
 | 
			
		||||
          await CallsHelper.sendSessionDescription(convID, ev.peerID, answer);
 | 
			
		||||
        }
 | 
			
		||||
@@ -402,41 +402,41 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
 | 
			
		||||
      // Ice Candidate
 | 
			
		||||
      else {
 | 
			
		||||
        await _peersConnections[ev.peerID].addCandidate(ev.candidate);
 | 
			
		||||
        await _peersConnections[ev.peerID]!.addCandidate(ev.candidate!);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e, stack) {
 | 
			
		||||
      print("Error while handling new signal ! $e\n$stack");
 | 
			
		||||
      showSimpleSnack(context, tr("Error while processing new signal!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Error while processing new signal!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Call this when a user has interrupted streaming
 | 
			
		||||
  Future<void> _removeRemotePeerConnection(int memberID) async {
 | 
			
		||||
    final member = _membersList.getUser(memberID);
 | 
			
		||||
  Future<void> _removeRemotePeerConnection(int? memberID) async {
 | 
			
		||||
    final member = _membersList!.getUser(memberID);
 | 
			
		||||
    member.status = MemberStatus.JOINED;
 | 
			
		||||
    setState(() {});
 | 
			
		||||
 | 
			
		||||
    if (_peersConnections.containsKey(memberID)) {
 | 
			
		||||
      await _peersConnections[memberID].close();
 | 
			
		||||
      await _peersConnections[memberID]!.close();
 | 
			
		||||
      _peersConnections.remove(memberID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (_renderers.containsKey(memberID)) {
 | 
			
		||||
      await _renderers[memberID].dispose();
 | 
			
		||||
      await _renderers[memberID]!.dispose();
 | 
			
		||||
      _renderers.remove(memberID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (member.stream != null) {
 | 
			
		||||
      member.stream.dispose();
 | 
			
		||||
      member.stream!.dispose();
 | 
			
		||||
      member.stream = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Call this when a member has completely left the call
 | 
			
		||||
  Future<void> _removeMember(int memberID) async {
 | 
			
		||||
  Future<void> _removeMember(int? memberID) async {
 | 
			
		||||
    await _removeRemotePeerConnection(memberID);
 | 
			
		||||
 | 
			
		||||
    _membersList.removeUser(memberID);
 | 
			
		||||
    _membersList!.removeUser(memberID);
 | 
			
		||||
 | 
			
		||||
    setState(() {});
 | 
			
		||||
  }
 | 
			
		||||
@@ -453,7 +453,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
    switch (option) {
 | 
			
		||||
      // Switch camera
 | 
			
		||||
      case _PopupMenuOption.SWITCH_CAMERA:
 | 
			
		||||
        await Helper.switchCamera(_localStream.getVideoTracks()[0]);
 | 
			
		||||
        await Helper.switchCamera(_localStream!.getVideoTracks()[0]);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      // Stop streaming
 | 
			
		||||
@@ -478,14 +478,14 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
  /// Build application bar
 | 
			
		||||
  PreferredSizeWidget _buildAppBar() {
 | 
			
		||||
    if (widget.buildCustomAppBar != null)
 | 
			
		||||
      return widget.buildCustomAppBar(_convName);
 | 
			
		||||
      return widget.buildCustomAppBar!(_convName);
 | 
			
		||||
 | 
			
		||||
    return AppBar(
 | 
			
		||||
      leading: IconButton(
 | 
			
		||||
        icon: Icon(Icons.arrow_back),
 | 
			
		||||
        onPressed: () => _leaveCall(),
 | 
			
		||||
      ),
 | 
			
		||||
      title: _convName == null ? CircularProgressIndicator() : Text(_convName),
 | 
			
		||||
      title: _convName == null ? CircularProgressIndicator() : Text(_convName!),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -496,7 +496,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
      return buildErrorCard(tr("Could not initialize call!"), actions: [
 | 
			
		||||
        MaterialButton(
 | 
			
		||||
          onPressed: () => _initCall(),
 | 
			
		||||
          child: Text(tr("Try again").toUpperCase()),
 | 
			
		||||
          child: Text(tr("Try again")!.toUpperCase()),
 | 
			
		||||
        )
 | 
			
		||||
      ]);
 | 
			
		||||
 | 
			
		||||
@@ -526,7 +526,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: RichText(
 | 
			
		||||
            text: TextSpan(
 | 
			
		||||
                children: _membersList
 | 
			
		||||
                children: _membersList!
 | 
			
		||||
                    .map((f) => TextSpan(
 | 
			
		||||
                        text: _usersList.getUser(f.userID).displayName + " ",
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
@@ -540,7 +540,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
 | 
			
		||||
  /// Videos area
 | 
			
		||||
  Widget _buildVideosArea(BuildContext context, BoxConstraints constraints) {
 | 
			
		||||
    final availableVideos = _membersList.readyPeers
 | 
			
		||||
    final List<CallMember> availableVideos = _membersList!.readyPeers
 | 
			
		||||
        .where((f) => f.hasVideoStream && _renderers.containsKey(f.userID))
 | 
			
		||||
        .toList();
 | 
			
		||||
 | 
			
		||||
@@ -591,12 +591,12 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildMemberVideo(int peerID) {
 | 
			
		||||
    return RTCVideoView(_renderers[peerID]);
 | 
			
		||||
    return RTCVideoView(_renderers[peerID]!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildLocalVideo() {
 | 
			
		||||
    return Positioned(
 | 
			
		||||
      child: RTCVideoView(_renderers[userID()]),
 | 
			
		||||
      child: RTCVideoView(_renderers[userID()]!),
 | 
			
		||||
      height: 50,
 | 
			
		||||
      width: 50,
 | 
			
		||||
      right: 10,
 | 
			
		||||
@@ -657,12 +657,12 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
              // Switch camera
 | 
			
		||||
              PopupMenuItem(
 | 
			
		||||
                  enabled: isStreamingVideo,
 | 
			
		||||
                  child: Text(tr("Switch camera")),
 | 
			
		||||
                  child: Text(tr("Switch camera")!),
 | 
			
		||||
                  value: _PopupMenuOption.SWITCH_CAMERA),
 | 
			
		||||
 | 
			
		||||
              // Interrupt streaming
 | 
			
		||||
              PopupMenuItem(
 | 
			
		||||
                  child: Text(tr("Stop streaming")),
 | 
			
		||||
                  child: Text(tr("Stop streaming")!),
 | 
			
		||||
                  value: _PopupMenuOption.STOP_STREAMING)
 | 
			
		||||
            ],
 | 
			
		||||
            child: _FooterButton(
 | 
			
		||||
@@ -679,7 +679,7 @@ class _CallScreenState extends SafeState<CallScreen> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _FooterButton extends StatelessWidget {
 | 
			
		||||
  final Function() onPressed;
 | 
			
		||||
  final Function()? onPressed;
 | 
			
		||||
  final Widget icon;
 | 
			
		||||
  final bool visible;
 | 
			
		||||
  final double width;
 | 
			
		||||
@@ -687,13 +687,13 @@ class _FooterButton extends StatelessWidget {
 | 
			
		||||
  final bool roundedButtons;
 | 
			
		||||
 | 
			
		||||
  const _FooterButton({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.icon,
 | 
			
		||||
    @required this.onPressed,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.icon,
 | 
			
		||||
    required this.onPressed,
 | 
			
		||||
    this.visible = true,
 | 
			
		||||
    this.width = 45,
 | 
			
		||||
    this.bgColor = Colors.black,
 | 
			
		||||
    @required this.roundedButtons,
 | 
			
		||||
    required this.roundedButtons,
 | 
			
		||||
  })  : assert(icon != null),
 | 
			
		||||
        assert(visible != null),
 | 
			
		||||
        assert(roundedButtons != null),
 | 
			
		||||
@@ -712,7 +712,7 @@ class _FooterButton extends StatelessWidget {
 | 
			
		||||
        child: FloatingActionButton(
 | 
			
		||||
          child: icon,
 | 
			
		||||
          foregroundColor: Colors.white,
 | 
			
		||||
          onPressed: onPressed == null ? null : () => onPressed(),
 | 
			
		||||
          onPressed: onPressed == null ? null : () => onPressed!(),
 | 
			
		||||
          backgroundColor: bgColor,
 | 
			
		||||
        ),
 | 
			
		||||
      ));
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,8 @@ class ConversationMembersScreen extends StatefulWidget {
 | 
			
		||||
  final int convID;
 | 
			
		||||
 | 
			
		||||
  const ConversationMembersScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.convID,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.convID,
 | 
			
		||||
  })  : assert(convID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -26,8 +26,8 @@ class ConversationMembersScreen extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ConversationMembersScreenState extends State<ConversationMembersScreen> {
 | 
			
		||||
  Conversation _conversation;
 | 
			
		||||
  UsersList _members;
 | 
			
		||||
  late Conversation _conversation;
 | 
			
		||||
  late UsersList _members;
 | 
			
		||||
 | 
			
		||||
  Future<void> _refresh() async {
 | 
			
		||||
    _conversation =
 | 
			
		||||
@@ -38,28 +38,28 @@ class _ConversationMembersScreenState extends State<ConversationMembersScreen> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(title: Text(tr("Conversation members"))),
 | 
			
		||||
      appBar: AppBar(title: Text(tr("Conversation members")!)),
 | 
			
		||||
      body: AsyncScreenWidget(
 | 
			
		||||
        onReload: _refresh,
 | 
			
		||||
        onBuild: _buildMembersList,
 | 
			
		||||
        errorMessage:
 | 
			
		||||
            tr("Could not load the list of members of this conversation!"),
 | 
			
		||||
            tr("Could not load the list of members of this conversation!")!,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildMembersList() => ListView.builder(
 | 
			
		||||
        itemBuilder: _buildItem,
 | 
			
		||||
        itemCount: _conversation.members.length,
 | 
			
		||||
        itemCount: _conversation.members!.length,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  Widget _buildItem(BuildContext context, int index) {
 | 
			
		||||
    final member = _conversation.members[index];
 | 
			
		||||
    final member = _conversation.members![index];
 | 
			
		||||
    final user = _members.getUser(member.userID);
 | 
			
		||||
    return ListTile(
 | 
			
		||||
      leading: AccountImageWidget(user: user),
 | 
			
		||||
      title: Text(user.displayName),
 | 
			
		||||
      subtitle: Text(member.isAdmin ? tr("Admin") : tr("Member")),
 | 
			
		||||
      subtitle: Text(member.isAdmin ? tr("Admin")! : tr("Member")!),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ enum _OlderMessagesLevel { NONE, LOADING, NO_MORE_AVAILABLE }
 | 
			
		||||
class ConversationScreen extends StatefulWidget {
 | 
			
		||||
  final int conversationID;
 | 
			
		||||
 | 
			
		||||
  const ConversationScreen({Key key, this.conversationID})
 | 
			
		||||
  const ConversationScreen({Key? key, required this.conversationID})
 | 
			
		||||
      : assert(conversationID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -56,8 +56,8 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
  final UsersHelper _usersHelper = UsersHelper();
 | 
			
		||||
 | 
			
		||||
  // Class members
 | 
			
		||||
  Conversation _conversation;
 | 
			
		||||
  ConversationMessagesList _messages;
 | 
			
		||||
  late Conversation _conversation;
 | 
			
		||||
  ConversationMessagesList? _messages;
 | 
			
		||||
  UsersList _usersInfo = UsersList();
 | 
			
		||||
  ErrorLevel _error = ErrorLevel.NONE;
 | 
			
		||||
  final _textFieldFocus = FocusNode();
 | 
			
		||||
@@ -66,21 +66,21 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
 | 
			
		||||
  bool _isSendingMessage = false;
 | 
			
		||||
  TextEditingController _textController = TextEditingController();
 | 
			
		||||
  ScrollWatcher _scrollController;
 | 
			
		||||
  ScrollWatcher? _scrollController;
 | 
			
		||||
  _OlderMessagesLevel _loadingOlderMessages = _OlderMessagesLevel.NONE;
 | 
			
		||||
 | 
			
		||||
  int _lastWritingEventSent = 0;
 | 
			
		||||
 | 
			
		||||
  CancelToken _sendCancel;
 | 
			
		||||
  double _sendProgress;
 | 
			
		||||
  CancelToken? _sendCancel;
 | 
			
		||||
  double? _sendProgress;
 | 
			
		||||
 | 
			
		||||
  String get textMessage => _textController.text;
 | 
			
		||||
 | 
			
		||||
  bool get _isMessageValid =>
 | 
			
		||||
      textMessage.length >=
 | 
			
		||||
          ServerConfigurationHelper.config.conversationsPolicy.minMessageLen &&
 | 
			
		||||
          ServerConfigurationHelper.config!.conversationsPolicy.minMessageLen &&
 | 
			
		||||
      textMessage.length <
 | 
			
		||||
          ServerConfigurationHelper.config.conversationsPolicy.maxMessageLen;
 | 
			
		||||
          ServerConfigurationHelper.config!.conversationsPolicy.maxMessageLen;
 | 
			
		||||
 | 
			
		||||
  showKeyboard() => _textFieldFocus.requestFocus();
 | 
			
		||||
 | 
			
		||||
@@ -179,14 +179,14 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
    this.listen<UpdatedConversationMessageEvent>((ev) async {
 | 
			
		||||
      if (ev.msg.convID == widget.conversationID) {
 | 
			
		||||
        await _conversationsHelper.saveMessage(ev.msg);
 | 
			
		||||
        setState(() => _messages.replace(ev.msg));
 | 
			
		||||
        setState(() => _messages!.replace(ev.msg));
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.listen<DeletedConversationMessageEvent>((ev) async {
 | 
			
		||||
      if (ev.msg.convID == widget.conversationID) {
 | 
			
		||||
        await _conversationsHelper.removeMessage(ev.msg);
 | 
			
		||||
        setState(() => _messages.removeMsg(ev.msg.id));
 | 
			
		||||
        setState(() => _messages!.removeMsg(ev.msg.id));
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -216,7 +216,7 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
      //First, get the messages
 | 
			
		||||
      final messages = await _conversationsHelper.getNewMessages(
 | 
			
		||||
        conversationID: widget.conversationID,
 | 
			
		||||
        lastMessageID: _messages == null ? 0 : _messages.lastMessageID,
 | 
			
		||||
        lastMessageID: _messages == null ? 0 : _messages!.lastMessageID,
 | 
			
		||||
        online: online,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@@ -235,14 +235,14 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
  Future<void> _loadOlderMessages() async {
 | 
			
		||||
    if (_loadingOlderMessages != _OlderMessagesLevel.NONE ||
 | 
			
		||||
        _messages == null ||
 | 
			
		||||
        _messages.length == 0) return;
 | 
			
		||||
        _messages!.length == 0) return;
 | 
			
		||||
    try {
 | 
			
		||||
      // Let's start to load older messages
 | 
			
		||||
      _setLoadingOlderMessagesState(_OlderMessagesLevel.LOADING);
 | 
			
		||||
 | 
			
		||||
      final messages = await _conversationsHelper.getOlderMessages(
 | 
			
		||||
          conversationID: widget.conversationID,
 | 
			
		||||
          oldestMessagesID: _messages.firstMessageID);
 | 
			
		||||
          oldestMessagesID: _messages!.firstMessageID);
 | 
			
		||||
 | 
			
		||||
      // Mark as not loading anymore
 | 
			
		||||
      _setLoadingOlderMessagesState(_OlderMessagesLevel.NONE);
 | 
			
		||||
@@ -280,14 +280,14 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
      if (_messages == null)
 | 
			
		||||
        _messages = messages;
 | 
			
		||||
      else
 | 
			
		||||
        _messages.addAll(messages);
 | 
			
		||||
        _messages!.addAll(messages);
 | 
			
		||||
 | 
			
		||||
      //Reverse the order of the messages (if required)
 | 
			
		||||
      if (messages.length > 0) {
 | 
			
		||||
        _messages.sort();
 | 
			
		||||
        final reverse = _messages.reversed;
 | 
			
		||||
        _messages!.sort();
 | 
			
		||||
        final Iterable<ConversationMessage> reverse = _messages!.reversed;
 | 
			
		||||
        _messages = ConversationMessagesList();
 | 
			
		||||
        _messages.addAll(reverse);
 | 
			
		||||
        _messages!.addAll(reverse);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -300,20 +300,20 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
    try {
 | 
			
		||||
      final file = await showPickFileDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        maxFileSize: srvConfig.conversationsPolicy.filesMaxSize,
 | 
			
		||||
        allowedMimeTypes: srvConfig.conversationsPolicy.allowedFilesType,
 | 
			
		||||
        imageMaxWidth: srvConfig.conversationsPolicy.maxMessageImageWidth,
 | 
			
		||||
        imageMaxHeight: srvConfig.conversationsPolicy.maxMessageImageHeight,
 | 
			
		||||
        maxFileSize: srvConfig!.conversationsPolicy.filesMaxSize,
 | 
			
		||||
        allowedMimeTypes: srvConfig!.conversationsPolicy.allowedFilesType,
 | 
			
		||||
        imageMaxWidth: srvConfig!.conversationsPolicy.maxMessageImageWidth,
 | 
			
		||||
        imageMaxHeight: srvConfig!.conversationsPolicy.maxMessageImageHeight,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (file == null) return;
 | 
			
		||||
 | 
			
		||||
      BytesFile thumbnail;
 | 
			
		||||
      BytesFile? thumbnail;
 | 
			
		||||
 | 
			
		||||
      if (isVideo(lookupMimeType(file.filename)))
 | 
			
		||||
      if (isVideo(lookupMimeType(file.filename)!))
 | 
			
		||||
        thumbnail = await generateVideoThumbnail(
 | 
			
		||||
          videoFile: file,
 | 
			
		||||
          maxWidth: srvConfig.conversationsPolicy.maxThumbnailWidth,
 | 
			
		||||
          maxWidth: srvConfig!.conversationsPolicy.maxThumbnailWidth,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
      _sendCancel = CancelToken();
 | 
			
		||||
@@ -331,7 +331,7 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
      assert(res == SendMessageResult.SUCCESS);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      showSimpleSnack(context, tr("Failed to send a file!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Failed to send a file!")!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setState(() {
 | 
			
		||||
@@ -363,8 +363,8 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
        SnackBar(
 | 
			
		||||
          content: Text(
 | 
			
		||||
            result == SendMessageResult.MESSAGE_REJECTED
 | 
			
		||||
                ? tr("Message rejected by the server!")
 | 
			
		||||
                : tr("Could not send message!"),
 | 
			
		||||
                ? tr("Message rejected by the server!")!
 | 
			
		||||
                : tr("Could not send message!")!,
 | 
			
		||||
          ),
 | 
			
		||||
          duration: Duration(milliseconds: 500),
 | 
			
		||||
        ),
 | 
			
		||||
@@ -395,7 +395,7 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
  Widget _buildNoMessagesNotice() {
 | 
			
		||||
    return Expanded(
 | 
			
		||||
      child: Center(
 | 
			
		||||
        child: Text(tr("There is no message yet in this conversation.")),
 | 
			
		||||
        child: Text(tr("There is no message yet in this conversation.")!),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@@ -406,15 +406,15 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
        child: ListView.builder(
 | 
			
		||||
      controller: _scrollController,
 | 
			
		||||
      reverse: true,
 | 
			
		||||
      itemCount: _messages.length,
 | 
			
		||||
      itemCount: _messages!.length,
 | 
			
		||||
      itemBuilder: (c, i) => _buildMessageItem(i),
 | 
			
		||||
    ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildMessageItem(int msgIndex) {
 | 
			
		||||
    final msg = _messages[msgIndex];
 | 
			
		||||
    final msg = _messages![msgIndex];
 | 
			
		||||
    final nextMessage =
 | 
			
		||||
        msgIndex + 1 < _messages.length ? _messages[msgIndex + 1] : null;
 | 
			
		||||
        msgIndex + 1 < _messages!.length ? _messages![msgIndex + 1] : null;
 | 
			
		||||
 | 
			
		||||
    return Column(
 | 
			
		||||
      children: <Widget>[
 | 
			
		||||
@@ -427,7 +427,7 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
            ? Container(
 | 
			
		||||
                alignment: Alignment.center,
 | 
			
		||||
                child: ServerConversationMessageTile(
 | 
			
		||||
                    message: msg.serverMessage, users: _usersInfo),
 | 
			
		||||
                    message: msg.serverMessage!, users: _usersInfo),
 | 
			
		||||
              )
 | 
			
		||||
            : Container(
 | 
			
		||||
                margin: EdgeInsets.symmetric(vertical: 5),
 | 
			
		||||
@@ -442,7 +442,7 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildSenderLayout(
 | 
			
		||||
      ConversationMessage message, ConversationMessage previousMessage) {
 | 
			
		||||
      ConversationMessage message, ConversationMessage? previousMessage) {
 | 
			
		||||
    final messageRadius = Radius.circular(10);
 | 
			
		||||
 | 
			
		||||
    return Container(
 | 
			
		||||
@@ -466,7 +466,7 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildReceiverLayout(
 | 
			
		||||
      ConversationMessage message, ConversationMessage previousMessage) {
 | 
			
		||||
      ConversationMessage message, ConversationMessage? previousMessage) {
 | 
			
		||||
    final messageRadius = Radius.circular(10);
 | 
			
		||||
 | 
			
		||||
    return Row(children: [
 | 
			
		||||
@@ -640,11 +640,11 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
              flex: 5,
 | 
			
		||||
            ),
 | 
			
		||||
            Spacer(flex: 1),
 | 
			
		||||
            Text("${(_sendProgress * 100).toInt()}%"),
 | 
			
		||||
            Text("${(_sendProgress! * 100).toInt()}%"),
 | 
			
		||||
            Spacer(flex: 1),
 | 
			
		||||
            OutlinedButton(
 | 
			
		||||
              onPressed: () => _sendCancel.cancel(),
 | 
			
		||||
              child: Text(tr("Cancel").toUpperCase()),
 | 
			
		||||
              onPressed: () => _sendCancel!.cancel(),
 | 
			
		||||
              child: Text(tr("Cancel")!.toUpperCase()),
 | 
			
		||||
            ),
 | 
			
		||||
            Spacer(flex: 1),
 | 
			
		||||
          ],
 | 
			
		||||
@@ -667,8 +667,8 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
              ? _buildLoadingOlderMessage()
 | 
			
		||||
              : null,
 | 
			
		||||
        ),
 | 
			
		||||
        _messages.length == 0 ? _buildNoMessagesNotice() : _buildMessagesList(),
 | 
			
		||||
        UserWritingInConvNotifier(convID: _conversation.id),
 | 
			
		||||
        _messages!.length == 0 ? _buildNoMessagesNotice() : _buildMessagesList(),
 | 
			
		||||
        UserWritingInConvNotifier(convID: _conversation.id!),
 | 
			
		||||
        _sendCancel != null ? _buildSendingWidget() : _buildSendMessageForm(),
 | 
			
		||||
        _showEmojiPicker ? _buildEmojiContainer() : Container(),
 | 
			
		||||
      ],
 | 
			
		||||
@@ -681,7 +681,7 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
      final t = time();
 | 
			
		||||
 | 
			
		||||
      if (t - _lastWritingEventSent <
 | 
			
		||||
          srvConfig.conversationsPolicy.writingEventInterval) return;
 | 
			
		||||
          srvConfig!.conversationsPolicy.writingEventInterval) return;
 | 
			
		||||
 | 
			
		||||
      _lastWritingEventSent = t;
 | 
			
		||||
      await ConversationsHelper.sendWritingEvent(_conversation.id);
 | 
			
		||||
@@ -692,7 +692,7 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
 | 
			
		||||
  /// Request message statistics
 | 
			
		||||
  void _requestMessageStats(ConversationMessage message) async {
 | 
			
		||||
    MainController.of(context)
 | 
			
		||||
    MainController.of(context)!
 | 
			
		||||
        .openConversationMessageStats(_conversation, message);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -700,20 +700,20 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
  Future<void> _updateMessage(ConversationMessage message) async {
 | 
			
		||||
    final newContent = await askUserString(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: tr("Update message"),
 | 
			
		||||
      message: tr("Please enter new message content:"),
 | 
			
		||||
      defaultValue: message.message.content,
 | 
			
		||||
      hint: tr("New message"),
 | 
			
		||||
      title: tr("Update message")!,
 | 
			
		||||
      message: tr("Please enter new message content:")!,
 | 
			
		||||
      defaultValue: message.message.content!,
 | 
			
		||||
      hint: tr("New message")!,
 | 
			
		||||
      minLength:
 | 
			
		||||
          ServerConfigurationHelper.config.conversationsPolicy.minMessageLen,
 | 
			
		||||
          ServerConfigurationHelper.config!.conversationsPolicy.minMessageLen,
 | 
			
		||||
      maxLength:
 | 
			
		||||
          ServerConfigurationHelper.config.conversationsPolicy.maxMessageLen,
 | 
			
		||||
          ServerConfigurationHelper.config!.conversationsPolicy.maxMessageLen,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (newContent == null) return;
 | 
			
		||||
 | 
			
		||||
    if (!await _conversationsHelper.updateMessage(message.id, newContent)) {
 | 
			
		||||
      showSimpleSnack(context, tr("Could not update message content!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not update message content!")!);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -723,21 +723,21 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
    final choice = await showDialog<bool>(
 | 
			
		||||
      context: context,
 | 
			
		||||
      builder: (c) => AlertDialog(
 | 
			
		||||
        title: Text(tr("Confirm deletion")),
 | 
			
		||||
        title: Text(tr("Confirm deletion")!),
 | 
			
		||||
        content: Text(
 | 
			
		||||
          tr("Do you really want to delete this message ? The operation can not be cancelled !"),
 | 
			
		||||
          tr("Do you really want to delete this message ? The operation can not be cancelled !")!,
 | 
			
		||||
          textAlign: TextAlign.justify,
 | 
			
		||||
        ),
 | 
			
		||||
        actions: <Widget>[
 | 
			
		||||
          TextButton(
 | 
			
		||||
            child: Text(
 | 
			
		||||
              tr("Cancel").toUpperCase(),
 | 
			
		||||
              tr("Cancel")!.toUpperCase(),
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: () => Navigator.pop(c, false),
 | 
			
		||||
          ),
 | 
			
		||||
          TextButton(
 | 
			
		||||
            child: Text(
 | 
			
		||||
              tr("Confirm").toUpperCase(),
 | 
			
		||||
              tr("Confirm")!.toUpperCase(),
 | 
			
		||||
              style: TextStyle(color: Colors.red),
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: () => Navigator.pop(c, true),
 | 
			
		||||
@@ -750,6 +750,6 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
 | 
			
		||||
 | 
			
		||||
    // Execute the request
 | 
			
		||||
    if (!await _conversationsHelper.deleteMessage(message.id))
 | 
			
		||||
      showSimpleSnack(context, tr("Could not delete conversation message!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not delete conversation message!")!);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,10 +21,10 @@ import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class ConversationsListScreen extends StatefulWidget {
 | 
			
		||||
  final bool useSmallFAB;
 | 
			
		||||
  final Function() onOpen;
 | 
			
		||||
  final Function()? onOpen;
 | 
			
		||||
 | 
			
		||||
  const ConversationsListScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.useSmallFAB = false,
 | 
			
		||||
    this.onOpen,
 | 
			
		||||
  })  : assert(useSmallFAB != null),
 | 
			
		||||
@@ -37,9 +37,9 @@ class ConversationsListScreen extends StatefulWidget {
 | 
			
		||||
class _ConversationScreenState extends SafeState<ConversationsListScreen> {
 | 
			
		||||
  final ConversationsHelper _conversationsHelper = ConversationsHelper();
 | 
			
		||||
  final UsersHelper _usersHelper = UsersHelper();
 | 
			
		||||
  ConversationsList _list;
 | 
			
		||||
  UsersList _users;
 | 
			
		||||
  GroupsList _groups;
 | 
			
		||||
  ConversationsList? _list;
 | 
			
		||||
  late UsersList _users;
 | 
			
		||||
  GroupsList? _groups;
 | 
			
		||||
  LoadErrorLevel _error = LoadErrorLevel.NONE;
 | 
			
		||||
  final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
 | 
			
		||||
      GlobalKey<RefreshIndicatorState>();
 | 
			
		||||
@@ -49,7 +49,7 @@ class _ConversationScreenState extends SafeState<ConversationsListScreen> {
 | 
			
		||||
    super.initState();
 | 
			
		||||
 | 
			
		||||
    this.listen<NewNumberUnreadConversations>(
 | 
			
		||||
        (d) => _refreshIndicatorKey.currentState.show());
 | 
			
		||||
        (d) => _refreshIndicatorKey.currentState!.show());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -94,9 +94,9 @@ class _ConversationScreenState extends SafeState<ConversationsListScreen> {
 | 
			
		||||
      tr("Could not retrieve the list of conversations!"),
 | 
			
		||||
      actions: <Widget>[
 | 
			
		||||
        TextButton(
 | 
			
		||||
          onPressed: () => _refreshIndicatorKey.currentState.show(),
 | 
			
		||||
          onPressed: () => _refreshIndicatorKey.currentState!.show(),
 | 
			
		||||
          child: Text(
 | 
			
		||||
            tr("Retry").toUpperCase(),
 | 
			
		||||
            tr("Retry")!.toUpperCase(),
 | 
			
		||||
            style: TextStyle(
 | 
			
		||||
              color: Colors.white,
 | 
			
		||||
            ),
 | 
			
		||||
@@ -108,23 +108,23 @@ class _ConversationScreenState extends SafeState<ConversationsListScreen> {
 | 
			
		||||
 | 
			
		||||
  /// Open a conversation
 | 
			
		||||
  void _openConversation(Conversation conv) {
 | 
			
		||||
    MainController.of(context).openConversation(conv);
 | 
			
		||||
    if (widget.onOpen != null) widget.onOpen();
 | 
			
		||||
    MainController.of(context)!.openConversation(conv);
 | 
			
		||||
    if (widget.onOpen != null) widget.onOpen!();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Create a new conversation
 | 
			
		||||
  void _createConversation() {
 | 
			
		||||
    MainController.of(context).push(
 | 
			
		||||
    MainController.of(context)!.push(
 | 
			
		||||
      CreateConversationScreen(),
 | 
			
		||||
      canShowAsDialog: true,
 | 
			
		||||
      hideNavBar: true,
 | 
			
		||||
    );
 | 
			
		||||
    if (widget.onOpen != null) widget.onOpen();
 | 
			
		||||
    if (widget.onOpen != null) widget.onOpen!();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Handle conversations updated requests
 | 
			
		||||
  void _updateConversation(Conversation conversation) {
 | 
			
		||||
    MainController.of(context).openConversationSettingsRoute(conversation);
 | 
			
		||||
    MainController.of(context)!.openConversationSettingsRoute(conversation);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Handle conversation deletion request
 | 
			
		||||
@@ -153,7 +153,7 @@ class _ConversationScreenState extends SafeState<ConversationsListScreen> {
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Failed to leave conversation! $e => $s");
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(tr("Could not leave the conversation!"))));
 | 
			
		||||
          SnackBar(content: Text(tr("Could not leave the conversation!")!)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Reload the list of conversations
 | 
			
		||||
@@ -181,11 +181,11 @@ class _ConversationScreenState extends SafeState<ConversationsListScreen> {
 | 
			
		||||
                  physics: AlwaysScrollableScrollPhysics(),
 | 
			
		||||
                  controller: ScrollController(),
 | 
			
		||||
                  itemBuilder: (context, index) {
 | 
			
		||||
                    if (_list[index].isGroupConversation &&
 | 
			
		||||
                        !_list[index].following) return Container();
 | 
			
		||||
                    if (_list![index].isGroupConversation &&
 | 
			
		||||
                        !_list![index].following) return Container();
 | 
			
		||||
 | 
			
		||||
                    return ConversationTile(
 | 
			
		||||
                      conversation: _list.elementAt(index),
 | 
			
		||||
                      conversation: _list!.elementAt(index),
 | 
			
		||||
                      usersList: _users,
 | 
			
		||||
                      groupsList: _groups,
 | 
			
		||||
                      onOpen: (c) {
 | 
			
		||||
@@ -195,7 +195,7 @@ class _ConversationScreenState extends SafeState<ConversationsListScreen> {
 | 
			
		||||
                      onRequestLeave: _requestLeaveConversation,
 | 
			
		||||
                    );
 | 
			
		||||
                  },
 | 
			
		||||
                  itemCount: _list.length,
 | 
			
		||||
                  itemCount: _list!.length,
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ enum _ErrorsLevel { NONE, MINOR, MAJOR }
 | 
			
		||||
class FriendsListScreen extends StatefulWidget {
 | 
			
		||||
  final bool showAppBar;
 | 
			
		||||
 | 
			
		||||
  const FriendsListScreen({Key key, this.showAppBar = true}) : super(key: key);
 | 
			
		||||
  const FriendsListScreen({Key? key, this.showAppBar = true}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<StatefulWidget> createState() => _FriendsListScreenState();
 | 
			
		||||
@@ -35,8 +35,8 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
 | 
			
		||||
  /// Widget members
 | 
			
		||||
  _ErrorsLevel _error = _ErrorsLevel.NONE;
 | 
			
		||||
  FriendsList _friendsList;
 | 
			
		||||
  UsersList _usersInfo;
 | 
			
		||||
  FriendsList? _friendsList;
 | 
			
		||||
  late UsersList _usersInfo;
 | 
			
		||||
  GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
 | 
			
		||||
      GlobalKey<RefreshIndicatorState>();
 | 
			
		||||
 | 
			
		||||
@@ -61,7 +61,7 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
 | 
			
		||||
  /// Refresh the list of friends
 | 
			
		||||
  Future<void> _refreshList() async {
 | 
			
		||||
    await _refreshIndicatorKey.currentState.show();
 | 
			
		||||
    await _refreshIndicatorKey.currentState!.show();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Load the list of friends
 | 
			
		||||
@@ -72,7 +72,7 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
    final list = await _friendsHelper.getList(online: online);
 | 
			
		||||
 | 
			
		||||
    // Check if there is no cache yet
 | 
			
		||||
    if (!online && list.isEmpty) return;
 | 
			
		||||
    if (!online && list!.isEmpty) return;
 | 
			
		||||
 | 
			
		||||
    // Check for errors
 | 
			
		||||
    if (list == null) return _gotError();
 | 
			
		||||
@@ -98,7 +98,7 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: _refreshList,
 | 
			
		||||
            child: Text(
 | 
			
		||||
              tr("Retry").toUpperCase(),
 | 
			
		||||
              tr("Retry")!.toUpperCase(),
 | 
			
		||||
              style: TextStyle(color: Colors.white),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
@@ -109,7 +109,7 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
  Widget build(BuildContext context) => Scaffold(
 | 
			
		||||
        appBar: widget.showAppBar
 | 
			
		||||
            ? AppBar(
 | 
			
		||||
                title: Text(tr("Your friends list")),
 | 
			
		||||
                title: Text(tr("Your friends list")!),
 | 
			
		||||
              )
 | 
			
		||||
            : null,
 | 
			
		||||
        body: _buildBody(),
 | 
			
		||||
@@ -131,18 +131,18 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
            onRefresh: () => _loadList(true),
 | 
			
		||||
            child: ListView.builder(
 | 
			
		||||
                physics: AlwaysScrollableScrollPhysics(),
 | 
			
		||||
                itemCount: _friendsList.length,
 | 
			
		||||
                itemBuilder: (c, i) => _friendsList[i].accepted
 | 
			
		||||
                itemCount: _friendsList!.length,
 | 
			
		||||
                itemBuilder: (c, i) => _friendsList![i].accepted
 | 
			
		||||
                    ? AcceptedFriendTile(
 | 
			
		||||
                        friend: _friendsList[i],
 | 
			
		||||
                        user: _usersInfo.getUser(_friendsList[i].id),
 | 
			
		||||
                        friend: _friendsList![i],
 | 
			
		||||
                        user: _usersInfo.getUser(_friendsList![i].id),
 | 
			
		||||
                        onOpenPrivateConversation: _openPrivateConversation,
 | 
			
		||||
                        onSetFollowing: _setFollowingFriend,
 | 
			
		||||
                        onRequestDelete: _deleteFriend,
 | 
			
		||||
                      )
 | 
			
		||||
                    : PendingFriendTile(
 | 
			
		||||
                        friend: _friendsList[i],
 | 
			
		||||
                        user: _usersInfo.getUser(_friendsList[i].id),
 | 
			
		||||
                        friend: _friendsList![i],
 | 
			
		||||
                        user: _usersInfo.getUser(_friendsList![i].id),
 | 
			
		||||
                        onRespond: _respondRequest,
 | 
			
		||||
                      )),
 | 
			
		||||
          ),
 | 
			
		||||
@@ -154,7 +154,7 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
  /// Respond to friendship request
 | 
			
		||||
  Future<void> _respondRequest(Friend f, bool accept) async {
 | 
			
		||||
    if (!await _friendsHelper.respondRequest(f.id, accept))
 | 
			
		||||
      showSimpleSnack(context, tr("Could not respond to friendship request!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not respond to friendship request!")!);
 | 
			
		||||
 | 
			
		||||
    // Load the list of friends again
 | 
			
		||||
    _refreshList();
 | 
			
		||||
@@ -163,7 +163,7 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
  /// Update following status of a friend
 | 
			
		||||
  Future<void> _setFollowingFriend(Friend friend, bool follow) async {
 | 
			
		||||
    if (!await _friendsHelper.setFollowing(friend.id, follow))
 | 
			
		||||
      showSimpleSnack(context, tr("Could not update following status!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not update following status!")!);
 | 
			
		||||
 | 
			
		||||
    _refreshList();
 | 
			
		||||
  }
 | 
			
		||||
@@ -173,18 +173,18 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
    final choice = await showDialog<bool>(
 | 
			
		||||
      context: context,
 | 
			
		||||
      builder: (b) => AlertDialog(
 | 
			
		||||
        title: Text(tr("Delete friend")),
 | 
			
		||||
        title: Text(tr("Delete friend")!),
 | 
			
		||||
        content: Text(tr(
 | 
			
		||||
            "Are you sure do you want to remove this friend from your list of friends ? A friendship request will have to be sent to get this user back to your list!")),
 | 
			
		||||
            "Are you sure do you want to remove this friend from your list of friends ? A friendship request will have to be sent to get this user back to your list!")!),
 | 
			
		||||
        actions: <Widget>[
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: () => Navigator.pop(context, false),
 | 
			
		||||
            child: Text(tr("Cancel").toUpperCase()),
 | 
			
		||||
            child: Text(tr("Cancel")!.toUpperCase()),
 | 
			
		||||
          ),
 | 
			
		||||
          TextButton(
 | 
			
		||||
            onPressed: () => Navigator.pop(context, true),
 | 
			
		||||
            child: Text(
 | 
			
		||||
              tr("Confirm").toUpperCase(),
 | 
			
		||||
              tr("Confirm")!.toUpperCase(),
 | 
			
		||||
              style: TextStyle(color: Colors.red),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
@@ -197,7 +197,7 @@ class _FriendsListScreenState extends SafeState<FriendsListScreen> {
 | 
			
		||||
    // Forward the request to the server
 | 
			
		||||
    if (!await _friendsHelper.removeFriend(f.id))
 | 
			
		||||
      showSimpleSnack(
 | 
			
		||||
          context, tr("Could not delete this person from your friends list!"));
 | 
			
		||||
          context, tr("Could not delete this person from your friends list!")!);
 | 
			
		||||
 | 
			
		||||
    // Refresh list
 | 
			
		||||
    _refreshList();
 | 
			
		||||
 
 | 
			
		||||
@@ -16,9 +16,9 @@ class GroupAccessDeniedScreen extends StatefulWidget {
 | 
			
		||||
  final Function() onMembershipAcquired;
 | 
			
		||||
 | 
			
		||||
  const GroupAccessDeniedScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.groupID,
 | 
			
		||||
    @required this.onMembershipAcquired,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.groupID,
 | 
			
		||||
    required this.onMembershipAcquired,
 | 
			
		||||
  })  : assert(groupID != null),
 | 
			
		||||
        assert(onMembershipAcquired != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
@@ -29,7 +29,7 @@ class GroupAccessDeniedScreen extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _GroupAccessDeniedScreenState extends SafeState<GroupAccessDeniedScreen> {
 | 
			
		||||
  Group _group;
 | 
			
		||||
  Group? _group;
 | 
			
		||||
 | 
			
		||||
  bool error = false;
 | 
			
		||||
 | 
			
		||||
@@ -45,7 +45,7 @@ class _GroupAccessDeniedScreenState extends SafeState<GroupAccessDeniedScreen> {
 | 
			
		||||
      return buildErrorCard(tr("Could not get basic group information!"),
 | 
			
		||||
          actions: [
 | 
			
		||||
            MaterialButton(
 | 
			
		||||
              child: Text(tr("Try again").toUpperCase()),
 | 
			
		||||
              child: Text(tr("Try again")!.toUpperCase()),
 | 
			
		||||
              onPressed: () => this._refresh(),
 | 
			
		||||
            )
 | 
			
		||||
          ]);
 | 
			
		||||
@@ -61,20 +61,20 @@ class _GroupAccessDeniedScreenState extends SafeState<GroupAccessDeniedScreen> {
 | 
			
		||||
            Spacer(
 | 
			
		||||
              flex: 5,
 | 
			
		||||
            ),
 | 
			
		||||
            GroupIcon(group: _group),
 | 
			
		||||
            GroupIcon(group: _group!),
 | 
			
		||||
            Spacer(),
 | 
			
		||||
            Text(
 | 
			
		||||
              _group.displayName,
 | 
			
		||||
              _group!.displayName,
 | 
			
		||||
              style: TextStyle(fontSize: 20),
 | 
			
		||||
            ),
 | 
			
		||||
            Spacer(),
 | 
			
		||||
            Text(
 | 
			
		||||
              tr("A registration is required to access this group page."),
 | 
			
		||||
              tr("A registration is required to access this group page.")!,
 | 
			
		||||
              textAlign: TextAlign.center,
 | 
			
		||||
            ),
 | 
			
		||||
            Spacer(),
 | 
			
		||||
            GroupMembershipWidget(
 | 
			
		||||
              group: _group,
 | 
			
		||||
              group: _group!,
 | 
			
		||||
              onUpdated: () => this._refresh(),
 | 
			
		||||
              onError: () => this._refresh(),
 | 
			
		||||
            ),
 | 
			
		||||
 
 | 
			
		||||
@@ -12,11 +12,11 @@ import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class GroupPageScreen extends StatefulWidget {
 | 
			
		||||
  final int groupID;
 | 
			
		||||
  final int conversationID;
 | 
			
		||||
  final int? conversationID;
 | 
			
		||||
 | 
			
		||||
  const GroupPageScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.groupID,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.groupID,
 | 
			
		||||
    this.conversationID,
 | 
			
		||||
  })  : assert(groupID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
@@ -28,7 +28,7 @@ class GroupPageScreen extends StatefulWidget {
 | 
			
		||||
class _GroupPageScreenState extends SafeState<GroupPageScreen> {
 | 
			
		||||
  int get groupID => widget.groupID;
 | 
			
		||||
 | 
			
		||||
  GetAdvancedInfoResult _getGroupResult;
 | 
			
		||||
  GetAdvancedInfoResult? _getGroupResult;
 | 
			
		||||
  var error = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -44,7 +44,7 @@ class _GroupPageScreenState extends SafeState<GroupPageScreen> {
 | 
			
		||||
          actions: [
 | 
			
		||||
            MaterialButton(
 | 
			
		||||
              onPressed: () => _refreshPage(),
 | 
			
		||||
              child: Text(tr("Try again").toUpperCase()),
 | 
			
		||||
              child: Text(tr("Try again")!.toUpperCase()),
 | 
			
		||||
            )
 | 
			
		||||
          ]);
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +52,7 @@ class _GroupPageScreenState extends SafeState<GroupPageScreen> {
 | 
			
		||||
    if (_getGroupResult == null) return buildCenteredProgressBar();
 | 
			
		||||
 | 
			
		||||
    // Check if access to the group was denied
 | 
			
		||||
    if (_getGroupResult.status == GetAdvancedInfoStatus.ACCESS_DENIED)
 | 
			
		||||
    if (_getGroupResult!.status == GetAdvancedInfoStatus.ACCESS_DENIED)
 | 
			
		||||
      return GroupAccessDeniedScreen(
 | 
			
		||||
        groupID: groupID,
 | 
			
		||||
        onMembershipAcquired: () => _refreshPage(),
 | 
			
		||||
@@ -60,7 +60,7 @@ class _GroupPageScreenState extends SafeState<GroupPageScreen> {
 | 
			
		||||
 | 
			
		||||
    // Now we can show group page
 | 
			
		||||
    return AuthorizedGroupPageScreen(
 | 
			
		||||
      advancedGroupInfo: _getGroupResult.info,
 | 
			
		||||
      advancedGroupInfo: _getGroupResult!.info!,
 | 
			
		||||
      conversationID: widget.conversationID,
 | 
			
		||||
      needRefresh: () => _refreshPage(),
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@ class AboutGroupSection extends StatelessWidget {
 | 
			
		||||
  final AdvancedGroupInfo group;
 | 
			
		||||
 | 
			
		||||
  const AboutGroupSection({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.group,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.group,
 | 
			
		||||
  })  : assert(group != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +25,7 @@ class AboutGroupSection extends StatelessWidget {
 | 
			
		||||
          group.hasURL
 | 
			
		||||
              ? ListTile(
 | 
			
		||||
                  leading: Icon(Icons.link),
 | 
			
		||||
                  title: Text(tr("URL")),
 | 
			
		||||
                  title: Text(tr("URL")!),
 | 
			
		||||
                  subtitle: Text(group.url),
 | 
			
		||||
                  onTap: () => launch(group.url),
 | 
			
		||||
                )
 | 
			
		||||
@@ -35,7 +35,7 @@ class AboutGroupSection extends StatelessWidget {
 | 
			
		||||
          group.hasDescription
 | 
			
		||||
              ? ListTile(
 | 
			
		||||
                  leading: Icon(Icons.note),
 | 
			
		||||
                  title: Text(tr("Description")),
 | 
			
		||||
                  title: Text(tr("Description")!),
 | 
			
		||||
                  subtitle: Text(group.description),
 | 
			
		||||
                )
 | 
			
		||||
              : Container(),
 | 
			
		||||
@@ -44,64 +44,64 @@ class AboutGroupSection extends StatelessWidget {
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: Icon(Icons.access_time),
 | 
			
		||||
            title: Text("Created"),
 | 
			
		||||
            subtitle: Text(diffTimeFromNowToStr(group.timeCreate)),
 | 
			
		||||
            subtitle: Text(diffTimeFromNowToStr(group.timeCreate!)!),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Number of members
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: Icon(Icons.group),
 | 
			
		||||
            title: Text(tr("Members")),
 | 
			
		||||
            title: Text(tr("Members")!),
 | 
			
		||||
            subtitle: Text(
 | 
			
		||||
                tr("%1% members", args: {"1": group.numberMembers.toString()})),
 | 
			
		||||
                tr("%1% members", args: {"1": group.numberMembers.toString()})!),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Who can create posts
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: Icon(Icons.add),
 | 
			
		||||
            title: Text(tr("Who can create posts")),
 | 
			
		||||
            title: Text(tr("Who can create posts")!),
 | 
			
		||||
            subtitle: Text(
 | 
			
		||||
                group.postCreationLevel == GroupPostCreationLevel.MEMBERS
 | 
			
		||||
                    ? tr("Every members")
 | 
			
		||||
                    : tr("Only moderators and administrators")),
 | 
			
		||||
                    ? tr("Every members")!
 | 
			
		||||
                    : tr("Only moderators and administrators")!),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Registration process
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: Icon(Icons.login),
 | 
			
		||||
            title: Text(tr("Registration process")),
 | 
			
		||||
            title: Text(tr("Registration process")!),
 | 
			
		||||
            subtitle: Text(group.registrationLevel ==
 | 
			
		||||
                    GroupRegistrationLevel.CLOSED
 | 
			
		||||
                ? tr("On invitation only")
 | 
			
		||||
                ? tr("On invitation only")!
 | 
			
		||||
                : (group.registrationLevel == GroupRegistrationLevel.MODERATED
 | 
			
		||||
                    ? tr("A moderator has to approve requests")
 | 
			
		||||
                    : tr("Anyone can join the group without approval"))),
 | 
			
		||||
                    ? tr("A moderator has to approve requests")!
 | 
			
		||||
                    : tr("Anyone can join the group without approval")!)),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Group visibility
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: Icon(Icons.remove_red_eye),
 | 
			
		||||
            title: Text(tr("Visibility")),
 | 
			
		||||
            title: Text(tr("Visibility")!),
 | 
			
		||||
            subtitle: Text(group.visibilityLevel == GroupVisibilityLevel.SECRETE
 | 
			
		||||
                ? tr("Secrete group")
 | 
			
		||||
                ? tr("Secrete group")!
 | 
			
		||||
                : (group.visibilityLevel == GroupVisibilityLevel.PRIVATE
 | 
			
		||||
                    ? tr("Private group")
 | 
			
		||||
                    : tr("Open group"))),
 | 
			
		||||
                    ? tr("Private group")!
 | 
			
		||||
                    : tr("Open group")!)),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Group members visibility
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: Icon(Icons.remove_red_eye),
 | 
			
		||||
            title: Text(tr("Members list visibility")),
 | 
			
		||||
            title: Text(tr("Members list visibility")!),
 | 
			
		||||
            subtitle:
 | 
			
		||||
                Text(group.isMembersListPublic ? tr("Public") : tr("Private")),
 | 
			
		||||
                Text(group.isMembersListPublic! ? tr("Public")! : tr("Private")!),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          group.isForezGroup
 | 
			
		||||
              ? // Group members visibility
 | 
			
		||||
              ListTile(
 | 
			
		||||
                  leading: Icon(Icons.info_outline),
 | 
			
		||||
                  title: Text(tr("Forez group")),
 | 
			
		||||
                  subtitle: Text(tr("Forez special features enabled")),
 | 
			
		||||
                  title: Text(tr("Forez group")!),
 | 
			
		||||
                  subtitle: Text(tr("Forez special features enabled")!),
 | 
			
		||||
                )
 | 
			
		||||
              : Container(),
 | 
			
		||||
        ],
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,8 @@ class ForezPresenceSection extends StatefulWidget {
 | 
			
		||||
  final int groupID;
 | 
			
		||||
 | 
			
		||||
  const ForezPresenceSection({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.groupID,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.groupID,
 | 
			
		||||
  })  : assert(groupID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -26,17 +26,17 @@ class ForezPresenceSection extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ForezPresenceSectionState extends State<ForezPresenceSection> {
 | 
			
		||||
  PresenceSet _presences;
 | 
			
		||||
  UsersList _users;
 | 
			
		||||
  PresenceSet? _presences;
 | 
			
		||||
  late UsersList _users;
 | 
			
		||||
  DateTime _currentDay = DateTime.now();
 | 
			
		||||
 | 
			
		||||
  List<int> get _currentListOfUsers => _presences.getUsersAtDate(_currentDay);
 | 
			
		||||
  List<int> get _currentListOfUsers => _presences!.getUsersAtDate(_currentDay);
 | 
			
		||||
 | 
			
		||||
  Future<void> _refresh() async {
 | 
			
		||||
    await ForezPresenceHelper.refreshCache(widget.groupID);
 | 
			
		||||
 | 
			
		||||
    _presences = await ForezPresenceHelper.getAll(widget.groupID);
 | 
			
		||||
    _users = await UsersHelper().getList(_presences.usersID);
 | 
			
		||||
    _users = await UsersHelper().getList(_presences!.usersID);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -71,7 +71,7 @@ class _ForezPresenceSectionState extends State<ForezPresenceSection> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildCalendar() => PresenceCalendarWidget(
 | 
			
		||||
        presenceSet: _presences,
 | 
			
		||||
        presenceSet: _presences!,
 | 
			
		||||
        mode: CalendarDisplayMode.MULTIPLE_USERS,
 | 
			
		||||
        selectedDay: _currentDay,
 | 
			
		||||
        onDayClicked: _selectDay,
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ class GroupConversationSection extends StatelessWidget {
 | 
			
		||||
  final Conversation conv;
 | 
			
		||||
 | 
			
		||||
  const GroupConversationSection({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.conv,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.conv,
 | 
			
		||||
  })  : assert(conv != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -21,7 +21,7 @@ class GroupConversationSection extends StatelessWidget {
 | 
			
		||||
  Widget build(BuildContext context) => Stack(
 | 
			
		||||
        children: [
 | 
			
		||||
          ConversationScreen(
 | 
			
		||||
            conversationID: conv.id,
 | 
			
		||||
            conversationID: conv.id!,
 | 
			
		||||
          ),
 | 
			
		||||
          Positioned(
 | 
			
		||||
            right: 1.0,
 | 
			
		||||
@@ -31,12 +31,12 @@ class GroupConversationSection extends StatelessWidget {
 | 
			
		||||
                    ? IconButton(
 | 
			
		||||
                        icon: Icon(Icons.phone),
 | 
			
		||||
                        onPressed: () =>
 | 
			
		||||
                            MainController.of(context).startCall(conv.id),
 | 
			
		||||
                            MainController.of(context)!.startCall(conv.id!),
 | 
			
		||||
                      )
 | 
			
		||||
                    : Container(),
 | 
			
		||||
                IconButton(
 | 
			
		||||
                  icon: Icon(Icons.settings),
 | 
			
		||||
                  onPressed: () => MainController.of(context)
 | 
			
		||||
                  onPressed: () => MainController.of(context)!
 | 
			
		||||
                      .openConversationSettingsRoute(conv),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import 'package:flutter/material.dart';
 | 
			
		||||
class GroupMembersSection extends StatefulWidget {
 | 
			
		||||
  final int groupID;
 | 
			
		||||
 | 
			
		||||
  const GroupMembersSection({Key key, this.groupID})
 | 
			
		||||
  const GroupMembersSection({Key? key, required this.groupID})
 | 
			
		||||
      : assert(groupID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -31,9 +31,9 @@ class GroupMembersSection extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
class _GroupMembersSectionState extends State<GroupMembersSection> {
 | 
			
		||||
  final _key = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
  Group _group;
 | 
			
		||||
  GroupMembersList _members;
 | 
			
		||||
  UsersList _users;
 | 
			
		||||
  late Group _group;
 | 
			
		||||
  late GroupMembersList _members;
 | 
			
		||||
  late UsersList _users;
 | 
			
		||||
 | 
			
		||||
  Future<void> _refresh() async {
 | 
			
		||||
    final group = await GroupsHelper().getSingle(widget.groupID, force: true);
 | 
			
		||||
@@ -50,7 +50,7 @@ class _GroupMembersSectionState extends State<GroupMembersSection> {
 | 
			
		||||
        key: _key,
 | 
			
		||||
        onReload: _refresh,
 | 
			
		||||
        onBuild: _buildBodyContent,
 | 
			
		||||
        errorMessage: tr("Could not load the list of members of this group!"),
 | 
			
		||||
        errorMessage: tr("Could not load the list of members of this group!")!,
 | 
			
		||||
        showOldDataWhileUpdating: true,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@@ -70,7 +70,7 @@ class _GroupMembersSectionState extends State<GroupMembersSection> {
 | 
			
		||||
      group: _group,
 | 
			
		||||
      membership: membership,
 | 
			
		||||
      user: user,
 | 
			
		||||
      onUpdated: () => _key.currentState.refresh(),
 | 
			
		||||
      onUpdated: () => _key.currentState!.refresh(),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -93,40 +93,40 @@ class _GroupMembersSectionState extends State<GroupMembersSection> {
 | 
			
		||||
      await GroupsHelper.sendInvitation(widget.groupID, userID);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Could not invite a user! $e\n$s");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not invite a user!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not invite a user!")!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _key.currentState.refresh();
 | 
			
		||||
    _key.currentState!.refresh();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
List<MultiChoiceEntry<GroupMembershipLevel>> get _membershipLevels => [
 | 
			
		||||
      MultiChoiceEntry(
 | 
			
		||||
          id: GroupMembershipLevel.ADMINISTRATOR,
 | 
			
		||||
          title: tr("Administrator"),
 | 
			
		||||
          title: tr("Administrator")!,
 | 
			
		||||
          subtitle: tr("Can change members privileges and group settings")),
 | 
			
		||||
      MultiChoiceEntry(
 | 
			
		||||
          id: GroupMembershipLevel.MODERATOR,
 | 
			
		||||
          title: tr("Moderator"),
 | 
			
		||||
          title: tr("Moderator")!,
 | 
			
		||||
          subtitle: tr(
 | 
			
		||||
              "Can always create posts, invite users and respond to membership request")),
 | 
			
		||||
      MultiChoiceEntry(
 | 
			
		||||
          id: GroupMembershipLevel.MEMBER,
 | 
			
		||||
          title: tr("Member"),
 | 
			
		||||
          title: tr("Member")!,
 | 
			
		||||
          subtitle: tr("Can access to all group posts")),
 | 
			
		||||
      MultiChoiceEntry(
 | 
			
		||||
        id: GroupMembershipLevel.PENDING,
 | 
			
		||||
        title: tr("Requested"),
 | 
			
		||||
        title: tr("Requested")!,
 | 
			
		||||
        hidden: true,
 | 
			
		||||
      ),
 | 
			
		||||
      MultiChoiceEntry(
 | 
			
		||||
        id: GroupMembershipLevel.INVITED,
 | 
			
		||||
        title: tr("Invited"),
 | 
			
		||||
        title: tr("Invited")!,
 | 
			
		||||
        hidden: true,
 | 
			
		||||
      ),
 | 
			
		||||
      MultiChoiceEntry(
 | 
			
		||||
        id: GroupMembershipLevel.VISITOR,
 | 
			
		||||
        title: tr("Visitor"),
 | 
			
		||||
        title: tr("Visitor")!,
 | 
			
		||||
        hidden: true,
 | 
			
		||||
      ),
 | 
			
		||||
    ];
 | 
			
		||||
@@ -138,11 +138,11 @@ class _GroupMembershipTile extends StatefulWidget {
 | 
			
		||||
  final void Function() onUpdated;
 | 
			
		||||
 | 
			
		||||
  const _GroupMembershipTile({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.group,
 | 
			
		||||
    @required this.membership,
 | 
			
		||||
    @required this.user,
 | 
			
		||||
    @required this.onUpdated,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.group,
 | 
			
		||||
    required this.membership,
 | 
			
		||||
    required this.user,
 | 
			
		||||
    required this.onUpdated,
 | 
			
		||||
  })  : assert(group != null),
 | 
			
		||||
        assert(membership != null),
 | 
			
		||||
        assert(user != null),
 | 
			
		||||
@@ -176,7 +176,7 @@ class __GroupMembershipTileState extends State<_GroupMembershipTile> {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildTrailing() {
 | 
			
		||||
  Widget? _buildTrailing() {
 | 
			
		||||
    switch (widget.membership.level) {
 | 
			
		||||
      case GroupMembershipLevel.ADMINISTRATOR:
 | 
			
		||||
      case GroupMembershipLevel.MODERATOR:
 | 
			
		||||
@@ -205,14 +205,14 @@ class __GroupMembershipTileState extends State<_GroupMembershipTile> {
 | 
			
		||||
      itemBuilder: (c) => [
 | 
			
		||||
        // Change membership level
 | 
			
		||||
        PopupMenuItem(
 | 
			
		||||
          child: Text(tr("Change level")),
 | 
			
		||||
          child: Text(tr("Change level")!),
 | 
			
		||||
          enabled: !_isCurrentUser && widget.group.isAdmin,
 | 
			
		||||
          value: _MemberMenuOptions.CHANGE_LEVEL,
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Remove membership
 | 
			
		||||
        PopupMenuItem(
 | 
			
		||||
          child: Text(tr("Remove")),
 | 
			
		||||
          child: Text(tr("Remove")!),
 | 
			
		||||
          value: _MemberMenuOptions.DELETE,
 | 
			
		||||
          enabled: !_isCurrentUser,
 | 
			
		||||
        ),
 | 
			
		||||
@@ -247,7 +247,7 @@ class __GroupMembershipTileState extends State<_GroupMembershipTile> {
 | 
			
		||||
      await GroupsHelper.setNewLevel(groupID, memberID, newLevel);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Could not change group membership level! $e\n$s");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not change group membership level!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not change group membership level!")!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    widget.onUpdated();
 | 
			
		||||
@@ -263,7 +263,7 @@ class __GroupMembershipTileState extends State<_GroupMembershipTile> {
 | 
			
		||||
      await GroupsHelper.removeMemberFromGroup(groupID, memberID);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Could not remove membership! $e\n$s");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not remove this membership!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not remove this membership!")!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    widget.onUpdated();
 | 
			
		||||
@@ -272,7 +272,7 @@ class __GroupMembershipTileState extends State<_GroupMembershipTile> {
 | 
			
		||||
  Widget _buildInvitedCase() {
 | 
			
		||||
    return MaterialButton(
 | 
			
		||||
      onPressed: _cancelMembership,
 | 
			
		||||
      child: Text(tr("Cancel").toUpperCase()),
 | 
			
		||||
      child: Text(tr("Cancel")!.toUpperCase()),
 | 
			
		||||
      textColor: Colors.red,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@@ -282,7 +282,7 @@ class __GroupMembershipTileState extends State<_GroupMembershipTile> {
 | 
			
		||||
      await GroupsHelper.cancelInvitation(groupID, memberID);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Could not cancel invitation! $e\n$s");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not cancel invitation!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not cancel invitation!")!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    widget.onUpdated();
 | 
			
		||||
@@ -294,12 +294,12 @@ class __GroupMembershipTileState extends State<_GroupMembershipTile> {
 | 
			
		||||
      children: <Widget>[
 | 
			
		||||
        MaterialButton(
 | 
			
		||||
          onPressed: () => _respondRequest(false),
 | 
			
		||||
          child: Text(tr("Reject").toUpperCase()),
 | 
			
		||||
          child: Text(tr("Reject")!.toUpperCase()),
 | 
			
		||||
          textColor: Colors.red,
 | 
			
		||||
        ),
 | 
			
		||||
        MaterialButton(
 | 
			
		||||
          onPressed: () => _respondRequest(true),
 | 
			
		||||
          child: Text(tr("Accept").toUpperCase()),
 | 
			
		||||
          child: Text(tr("Accept")!.toUpperCase()),
 | 
			
		||||
          textColor: Colors.green,
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
@@ -311,7 +311,7 @@ class __GroupMembershipTileState extends State<_GroupMembershipTile> {
 | 
			
		||||
      await GroupsHelper.respondRequest(groupID, memberID, accept);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Could not respond to membership request! $e\n$s");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not respond to membership request!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not respond to membership request!")!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    widget.onUpdated();
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ class GroupPostsSection extends StatefulWidget {
 | 
			
		||||
  final AdvancedGroupInfo group;
 | 
			
		||||
 | 
			
		||||
  const GroupPostsSection({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.group,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.group,
 | 
			
		||||
  })  : assert(group != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -31,7 +31,7 @@ class _GroupPostsSectionState extends State<GroupPostsSection> {
 | 
			
		||||
    return PostCreateFormWidget(
 | 
			
		||||
      postTarget: PostTarget.GROUP_PAGE,
 | 
			
		||||
      targetID: widget.group.id,
 | 
			
		||||
      onCreated: () => _postsKey.currentState.loadPostsList(getOlder: false),
 | 
			
		||||
      onCreated: () => _postsKey.currentState!.loadPostsList(getOlder: false),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ import 'package:flutter_settings_ui/flutter_settings_ui.dart';
 | 
			
		||||
class GroupSettingsScreen extends StatefulWidget {
 | 
			
		||||
  final int groupID;
 | 
			
		||||
 | 
			
		||||
  const GroupSettingsScreen({Key key, @required this.groupID})
 | 
			
		||||
  const GroupSettingsScreen({Key? key, required this.groupID})
 | 
			
		||||
      : assert(groupID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +43,7 @@ class GroupSettingsScreen extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
  AdvancedGroupInfo _groupSettings;
 | 
			
		||||
  AdvancedGroupInfo? _groupSettings;
 | 
			
		||||
 | 
			
		||||
  final _key = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
 | 
			
		||||
@@ -53,13 +53,13 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
 | 
			
		||||
  Future<void> _updateSettings() async {
 | 
			
		||||
    try {
 | 
			
		||||
      await GroupsHelper.setSettings(_groupSettings);
 | 
			
		||||
      await GroupsHelper.setSettings(_groupSettings!);
 | 
			
		||||
    } catch (e, stack) {
 | 
			
		||||
      print("Could not update group settings! $e\n$stack");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not update group settings!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not update group settings!")!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _key.currentState.refresh();
 | 
			
		||||
    _key.currentState!.refresh();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -67,7 +67,7 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: ComunicBackButton(),
 | 
			
		||||
        title: Text(tr("Group settings")),
 | 
			
		||||
        title: Text(tr("Group settings")!),
 | 
			
		||||
      ),
 | 
			
		||||
      body: _buildBody(),
 | 
			
		||||
    );
 | 
			
		||||
@@ -78,7 +78,7 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
      key: _key,
 | 
			
		||||
      onReload: _refresh,
 | 
			
		||||
      onBuild: _buildContent,
 | 
			
		||||
      errorMessage: tr("Could not get group settings!"),
 | 
			
		||||
      errorMessage: tr("Could not get group settings!")!,
 | 
			
		||||
      showOldDataWhileUpdating: true,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@@ -96,65 +96,65 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildGeneralSection() {
 | 
			
		||||
  SettingsSection _buildGeneralSection() {
 | 
			
		||||
    return SettingsSection(
 | 
			
		||||
      title: tr("General information"),
 | 
			
		||||
      tiles: [
 | 
			
		||||
        // Group ID
 | 
			
		||||
        SettingsTile(
 | 
			
		||||
          title: tr("Group ID"),
 | 
			
		||||
          subtitle: _groupSettings.id.toString(),
 | 
			
		||||
          subtitle: _groupSettings!.id.toString(),
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Group name
 | 
			
		||||
        TextEditSettingsTile(
 | 
			
		||||
            title: tr("Group name"),
 | 
			
		||||
            currValue: _groupSettings.name,
 | 
			
		||||
            title: tr("Group name")!,
 | 
			
		||||
            currValue: _groupSettings!.name,
 | 
			
		||||
            onChanged: (s) {
 | 
			
		||||
              _groupSettings.name = s;
 | 
			
		||||
              _groupSettings!.name = s;
 | 
			
		||||
              _updateSettings();
 | 
			
		||||
            }),
 | 
			
		||||
 | 
			
		||||
        // Group virtual directory
 | 
			
		||||
        SettingsTile(
 | 
			
		||||
          title: tr("Virtual directory (optional)"),
 | 
			
		||||
          subtitle: _groupSettings.virtualDirectory,
 | 
			
		||||
          subtitle: _groupSettings!.virtualDirectory,
 | 
			
		||||
          onPressed: (_) async {
 | 
			
		||||
            final newDir = await showVirtualDirectoryDialog(
 | 
			
		||||
              context: context,
 | 
			
		||||
              initialDirectory: _groupSettings.virtualDirectory,
 | 
			
		||||
              id: _groupSettings.id,
 | 
			
		||||
              initialDirectory: _groupSettings!.virtualDirectory,
 | 
			
		||||
              id: _groupSettings!.id,
 | 
			
		||||
              type: VirtualDirectoryTargetType.GROUP,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (newDir == null) return;
 | 
			
		||||
 | 
			
		||||
            _groupSettings.virtualDirectory = newDir;
 | 
			
		||||
            _groupSettings!.virtualDirectory = newDir;
 | 
			
		||||
            _updateSettings();
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Group URL
 | 
			
		||||
        TextEditSettingsTile(
 | 
			
		||||
          title: tr("Group URL (optional)"),
 | 
			
		||||
          currValue: _groupSettings.url,
 | 
			
		||||
          title: tr("Group URL (optional)")!,
 | 
			
		||||
          currValue: _groupSettings!.url,
 | 
			
		||||
          checkInput: validateUrl,
 | 
			
		||||
          allowEmptyValues: true,
 | 
			
		||||
          onChanged: (s) {
 | 
			
		||||
            _groupSettings.url = s;
 | 
			
		||||
            _groupSettings!.url = s;
 | 
			
		||||
            _updateSettings();
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Group description
 | 
			
		||||
        TextEditSettingsTile(
 | 
			
		||||
            title: tr("Group description (optional)"),
 | 
			
		||||
            currValue: _groupSettings.description,
 | 
			
		||||
            title: tr("Group description (optional)")!,
 | 
			
		||||
            currValue: _groupSettings!.description,
 | 
			
		||||
            maxLines: 3,
 | 
			
		||||
            maxLength: 255,
 | 
			
		||||
            allowEmptyValues: true,
 | 
			
		||||
            onChanged: (s) {
 | 
			
		||||
              _groupSettings.description = s;
 | 
			
		||||
              _groupSettings!.description = s;
 | 
			
		||||
              _updateSettings();
 | 
			
		||||
            }),
 | 
			
		||||
      ],
 | 
			
		||||
@@ -164,18 +164,18 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
  List<MultiChoiceEntry<GroupVisibilityLevel>> get _visibilityLevels => [
 | 
			
		||||
        MultiChoiceEntry(
 | 
			
		||||
          id: GroupVisibilityLevel.OPEN,
 | 
			
		||||
          title: tr("Open group"),
 | 
			
		||||
          title: tr("Open group")!,
 | 
			
		||||
          subtitle:
 | 
			
		||||
              tr("Group information & public posts are available to everyone."),
 | 
			
		||||
        ),
 | 
			
		||||
        MultiChoiceEntry(
 | 
			
		||||
          id: GroupVisibilityLevel.PRIVATE,
 | 
			
		||||
          title: tr("Private group"),
 | 
			
		||||
          title: tr("Private group")!,
 | 
			
		||||
          subtitle: tr("The group is accessible to accepted members only."),
 | 
			
		||||
        ),
 | 
			
		||||
        MultiChoiceEntry(
 | 
			
		||||
          id: GroupVisibilityLevel.SECRETE,
 | 
			
		||||
          title: tr("Secrete group"),
 | 
			
		||||
          title: tr("Secrete group")!,
 | 
			
		||||
          subtitle: tr("The group is visible only to invited members."),
 | 
			
		||||
        ),
 | 
			
		||||
      ];
 | 
			
		||||
@@ -183,19 +183,19 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
  List<MultiChoiceEntry<GroupRegistrationLevel>> get _registrationLevels => [
 | 
			
		||||
        MultiChoiceEntry(
 | 
			
		||||
          id: GroupRegistrationLevel.OPEN,
 | 
			
		||||
          title: tr("Open registration"),
 | 
			
		||||
          title: tr("Open registration")!,
 | 
			
		||||
          subtitle: tr(
 | 
			
		||||
              "Everyone can choose to join the group without moderator approval"),
 | 
			
		||||
        ),
 | 
			
		||||
        MultiChoiceEntry(
 | 
			
		||||
          id: GroupRegistrationLevel.MODERATED,
 | 
			
		||||
          title: tr("Moderated registration"),
 | 
			
		||||
          title: tr("Moderated registration")!,
 | 
			
		||||
          subtitle: tr(
 | 
			
		||||
              "Everyone can request a membership, but a moderator review the request"),
 | 
			
		||||
        ),
 | 
			
		||||
        MultiChoiceEntry(
 | 
			
		||||
          id: GroupRegistrationLevel.CLOSED,
 | 
			
		||||
          title: tr("Closed registration"),
 | 
			
		||||
          title: tr("Closed registration")!,
 | 
			
		||||
          subtitle: tr(
 | 
			
		||||
              "The only way to join the group is to be invited by a moderator"),
 | 
			
		||||
        ),
 | 
			
		||||
@@ -204,48 +204,48 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
  List<MultiChoiceEntry<GroupPostCreationLevel>> get _postsCreationLevels => [
 | 
			
		||||
        MultiChoiceEntry(
 | 
			
		||||
          id: GroupPostCreationLevel.MEMBERS,
 | 
			
		||||
          title: tr("All members"),
 | 
			
		||||
          title: tr("All members")!,
 | 
			
		||||
          subtitle:
 | 
			
		||||
              tr("All the members of the group can create posts on the group"),
 | 
			
		||||
        ),
 | 
			
		||||
        MultiChoiceEntry(
 | 
			
		||||
          id: GroupPostCreationLevel.MODERATORS,
 | 
			
		||||
          title: tr("Moderators only"),
 | 
			
		||||
          title: tr("Moderators only")!,
 | 
			
		||||
          subtitle: tr(
 | 
			
		||||
              "Only moderators and administrators of the group can create posts on it"),
 | 
			
		||||
        ),
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
  Widget _buildAccessRestrictions() => SettingsSection(
 | 
			
		||||
  SettingsSection _buildAccessRestrictions() => SettingsSection(
 | 
			
		||||
        title: tr("Access restrictions"),
 | 
			
		||||
        tiles: [
 | 
			
		||||
          // Group visibility
 | 
			
		||||
          MultiChoicesSettingsTile(
 | 
			
		||||
              title: tr("Group visibility"),
 | 
			
		||||
              title: tr("Group visibility")!,
 | 
			
		||||
              choices: _visibilityLevels,
 | 
			
		||||
              currentValue: _groupSettings.visibilityLevel,
 | 
			
		||||
              onChanged: (v) {
 | 
			
		||||
                _groupSettings.visibilityLevel = v;
 | 
			
		||||
              currentValue: _groupSettings!.visibilityLevel,
 | 
			
		||||
              onChanged: (dynamic v) {
 | 
			
		||||
                _groupSettings!.visibilityLevel = v;
 | 
			
		||||
                _updateSettings();
 | 
			
		||||
              }),
 | 
			
		||||
 | 
			
		||||
          // Group registration level
 | 
			
		||||
          MultiChoicesSettingsTile(
 | 
			
		||||
              title: tr("Group registration level"),
 | 
			
		||||
              title: tr("Group registration level")!,
 | 
			
		||||
              choices: _registrationLevels,
 | 
			
		||||
              currentValue: _groupSettings.registrationLevel,
 | 
			
		||||
              onChanged: (v) {
 | 
			
		||||
                _groupSettings.registrationLevel = v;
 | 
			
		||||
              currentValue: _groupSettings!.registrationLevel,
 | 
			
		||||
              onChanged: (dynamic v) {
 | 
			
		||||
                _groupSettings!.registrationLevel = v;
 | 
			
		||||
                _updateSettings();
 | 
			
		||||
              }),
 | 
			
		||||
 | 
			
		||||
          // Group posts creation levels
 | 
			
		||||
          MultiChoicesSettingsTile(
 | 
			
		||||
              title: tr("Posts creation level"),
 | 
			
		||||
              title: tr("Posts creation level")!,
 | 
			
		||||
              choices: _postsCreationLevels,
 | 
			
		||||
              currentValue: _groupSettings.postCreationLevel,
 | 
			
		||||
              onChanged: (s) {
 | 
			
		||||
                _groupSettings.postCreationLevel = s;
 | 
			
		||||
              currentValue: _groupSettings!.postCreationLevel,
 | 
			
		||||
              onChanged: (dynamic s) {
 | 
			
		||||
                _groupSettings!.postCreationLevel = s;
 | 
			
		||||
                _updateSettings();
 | 
			
		||||
              }),
 | 
			
		||||
 | 
			
		||||
@@ -253,10 +253,10 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
          SettingsTile.switchTile(
 | 
			
		||||
            title: tr("Make members list public"),
 | 
			
		||||
            onToggle: (s) {
 | 
			
		||||
              _groupSettings.isMembersListPublic = s;
 | 
			
		||||
              _groupSettings!.isMembersListPublic = s;
 | 
			
		||||
              _updateSettings();
 | 
			
		||||
            },
 | 
			
		||||
            switchValue: _groupSettings.isMembersListPublic,
 | 
			
		||||
            switchValue: _groupSettings!.isMembersListPublic,
 | 
			
		||||
            titleMaxLines: 2,
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
@@ -266,19 +266,19 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
      get _conversationMinMembershipLevel => [
 | 
			
		||||
            MultiChoiceEntry(
 | 
			
		||||
              id: GroupMembershipLevel.ADMINISTRATOR,
 | 
			
		||||
              title: tr("Administrators only"),
 | 
			
		||||
              title: tr("Administrators only")!,
 | 
			
		||||
              subtitle: tr(
 | 
			
		||||
                  "Only the administrators of the group can access the conversation"),
 | 
			
		||||
            ),
 | 
			
		||||
            MultiChoiceEntry(
 | 
			
		||||
              id: GroupMembershipLevel.MODERATOR,
 | 
			
		||||
              title: tr("Moderators and administrators"),
 | 
			
		||||
              title: tr("Moderators and administrators")!,
 | 
			
		||||
              subtitle: tr(
 | 
			
		||||
                  "Only moderators and administrators of the group can access the conversation"),
 | 
			
		||||
            ),
 | 
			
		||||
            MultiChoiceEntry(
 | 
			
		||||
              id: GroupMembershipLevel.MEMBER,
 | 
			
		||||
              title: tr("All members"),
 | 
			
		||||
              title: tr("All members")!,
 | 
			
		||||
              subtitle: tr(
 | 
			
		||||
                  "All the members of the group can access the conversation"),
 | 
			
		||||
            ),
 | 
			
		||||
@@ -286,17 +286,17 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
 | 
			
		||||
  SettingsSection _buildConversationsArea() => SettingsSection(
 | 
			
		||||
        title: tr("Group conversations"),
 | 
			
		||||
        tiles: _groupSettings.conversations
 | 
			
		||||
        tiles: _groupSettings!.conversations!
 | 
			
		||||
            .map(
 | 
			
		||||
              (e) {
 | 
			
		||||
                SettingsTile tile =
 | 
			
		||||
                    MultiChoicesSettingsTile<GroupMembershipLevel>(
 | 
			
		||||
                  title: e.name,
 | 
			
		||||
                    MultiChoicesSettingsTile<GroupMembershipLevel?>(
 | 
			
		||||
                  title: e.name!,
 | 
			
		||||
                  choices: _conversationMinMembershipLevel,
 | 
			
		||||
                  currentValue: e.groupMinMembershipLevel,
 | 
			
		||||
                  leading: e.hasLogo
 | 
			
		||||
                      ? CachedNetworkImage(
 | 
			
		||||
                          imageUrl: e.logoURL,
 | 
			
		||||
                          imageUrl: e.logoURL!,
 | 
			
		||||
                          width: 30,
 | 
			
		||||
                        )
 | 
			
		||||
                      : Icon(Icons.group, size: 30),
 | 
			
		||||
@@ -324,13 +324,13 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
    try {
 | 
			
		||||
      final name = await askUserString(
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: tr("New conversation name"),
 | 
			
		||||
        message: tr("Please give a name to the new conversation"),
 | 
			
		||||
        title: tr("New conversation name")!,
 | 
			
		||||
        message: tr("Please give a name to the new conversation")!,
 | 
			
		||||
        defaultValue: "",
 | 
			
		||||
        hint: tr("Name"),
 | 
			
		||||
        hint: tr("Name")!,
 | 
			
		||||
        minLength: 1,
 | 
			
		||||
        maxLength: ServerConfigurationHelper
 | 
			
		||||
            .config.conversationsPolicy.maxConversationNameLen,
 | 
			
		||||
            .config!.conversationsPolicy.maxConversationNameLen,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (name == null) return;
 | 
			
		||||
@@ -345,27 +345,27 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
      if (visibility == null) return;
 | 
			
		||||
 | 
			
		||||
      await GroupsHelper.createGroupConversation(NewGroupConversation(
 | 
			
		||||
        groupID: _groupSettings.id,
 | 
			
		||||
        groupID: _groupSettings!.id,
 | 
			
		||||
        name: name,
 | 
			
		||||
        minMembershipLevel: visibility,
 | 
			
		||||
      ));
 | 
			
		||||
 | 
			
		||||
      _key.currentState.refresh();
 | 
			
		||||
      _key.currentState!.refresh();
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to create a conversation!"));
 | 
			
		||||
      snack(context, tr("Failed to create a conversation!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _changeConversationVisibility(
 | 
			
		||||
      Conversation conv, GroupMembershipLevel newLevel) async {
 | 
			
		||||
      Conversation conv, GroupMembershipLevel? newLevel) async {
 | 
			
		||||
    try {
 | 
			
		||||
      await GroupsHelper.setConversationVisibility(conv.id, newLevel);
 | 
			
		||||
 | 
			
		||||
      _key.currentState.refresh();
 | 
			
		||||
      _key.currentState!.refresh();
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to change conversation visibility level!"));
 | 
			
		||||
      snack(context, tr("Failed to change conversation visibility level!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -378,21 +378,21 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
 | 
			
		||||
      await GroupsHelper.deleteConversation(conv.id);
 | 
			
		||||
 | 
			
		||||
      _key.currentState.refresh();
 | 
			
		||||
      _key.currentState!.refresh();
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to delete conversation!"));
 | 
			
		||||
      snack(context, tr("Failed to delete conversation!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildGroupLogoArea() {
 | 
			
		||||
  SettingsSection _buildGroupLogoArea() {
 | 
			
		||||
    return SettingsSection(
 | 
			
		||||
      title: tr("Group logo"),
 | 
			
		||||
      tiles: [
 | 
			
		||||
        // Current logo
 | 
			
		||||
        SettingsTile(
 | 
			
		||||
          title: tr("Current logo"),
 | 
			
		||||
          leading: GroupIcon(group: _groupSettings),
 | 
			
		||||
          leading: GroupIcon(group: _groupSettings!),
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Upload a new logo
 | 
			
		||||
@@ -421,10 +421,10 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
    try {
 | 
			
		||||
      final logo = await pickImage(context);
 | 
			
		||||
      if (logo == null) return;
 | 
			
		||||
      await _doUploadLogo(logo.bytes);
 | 
			
		||||
      await _doUploadLogo(logo.bytes as Uint8List?);
 | 
			
		||||
    } catch (e, stack) {
 | 
			
		||||
      print("Could not upload new logo! $e\n$stack");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not upload new logo!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not upload new logo!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -435,13 +435,13 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
      await _doUploadLogo(newLogo);
 | 
			
		||||
    } catch (e, stack) {
 | 
			
		||||
      print("Could not generate new logo! $e\n$stack");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not generate new random logo!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not generate new random logo!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _doUploadLogo(Uint8List bytes) async {
 | 
			
		||||
    await GroupsHelper.uploadNewLogo(_groupSettings.id, bytes);
 | 
			
		||||
    _key.currentState.refresh();
 | 
			
		||||
  Future<void> _doUploadLogo(Uint8List? bytes) async {
 | 
			
		||||
    await GroupsHelper.uploadNewLogo(_groupSettings!.id, bytes);
 | 
			
		||||
    _key.currentState!.refresh();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Delete previous group logo
 | 
			
		||||
@@ -452,15 +452,15 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
          message: tr("Do you really want to delete the logo of this group ?")))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
      await GroupsHelper.deleteLogo(_groupSettings.id);
 | 
			
		||||
      _key.currentState.refresh();
 | 
			
		||||
      await GroupsHelper.deleteLogo(_groupSettings!.id);
 | 
			
		||||
      _key.currentState!.refresh();
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Could not delete group logo! $e\n$s");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not delete group logo!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not delete group logo!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildDangerZone() {
 | 
			
		||||
  SettingsSection _buildDangerZone() {
 | 
			
		||||
    return SettingsSection(
 | 
			
		||||
      title: tr("Danger zone"),
 | 
			
		||||
      tiles: [
 | 
			
		||||
@@ -485,12 +485,12 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
 | 
			
		||||
              "Do you really want to delete this group ? All the posts related to it will be permanently deleted!")))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
      await GroupsHelper.deleteGroup(_groupSettings.id, password);
 | 
			
		||||
      await GroupsHelper.deleteGroup(_groupSettings!.id, password);
 | 
			
		||||
 | 
			
		||||
      MainController.of(context).popPage();
 | 
			
		||||
      MainController.of(context)!.popPage();
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Could not delete the group! $e\n$s");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not delete the group"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not delete the group")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ class GroupsListScreen extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _GroupsListScreenState extends SafeState<GroupsListScreen> {
 | 
			
		||||
  GroupsList _groups;
 | 
			
		||||
  GroupsList? _groups;
 | 
			
		||||
  bool _error = false;
 | 
			
		||||
 | 
			
		||||
  final _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
 | 
			
		||||
@@ -41,7 +41,7 @@ class _GroupsListScreenState extends SafeState<GroupsListScreen> {
 | 
			
		||||
          hide: !_error,
 | 
			
		||||
          actions: [
 | 
			
		||||
            MaterialButton(
 | 
			
		||||
              child: Text(tr("Try again").toUpperCase()),
 | 
			
		||||
              child: Text(tr("Try again")!.toUpperCase()),
 | 
			
		||||
              onPressed: () => _refreshList(),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
@@ -61,19 +61,19 @@ class _GroupsListScreenState extends SafeState<GroupsListScreen> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildGroupsList() => ListView(
 | 
			
		||||
        children: (_groups.values.toList()
 | 
			
		||||
        children: (_groups!.values.toList()
 | 
			
		||||
              ..sort((one, two) => two.id.compareTo(one.id)))
 | 
			
		||||
            .map((g) => ListTile(
 | 
			
		||||
                  leading: GroupIcon(group: g),
 | 
			
		||||
                  title: Text(g.displayName),
 | 
			
		||||
                  subtitle: GroupMembershipWidget(
 | 
			
		||||
                    group: g,
 | 
			
		||||
                    onUpdated: () => _refreshIndicatorKey.currentState.show(),
 | 
			
		||||
                    onUpdated: () => _refreshIndicatorKey.currentState!.show(),
 | 
			
		||||
                  ),
 | 
			
		||||
                  trailing: IconButton(
 | 
			
		||||
                      icon: Icon(Icons.delete),
 | 
			
		||||
                      onPressed: () => _deleteGroup(g)),
 | 
			
		||||
                  onTap: () => MainController.of(context).openGroup(g.id),
 | 
			
		||||
                  onTap: () => MainController.of(context)!.openGroup(g.id),
 | 
			
		||||
                ))
 | 
			
		||||
            .toList(),
 | 
			
		||||
      );
 | 
			
		||||
@@ -116,10 +116,10 @@ class _GroupsListScreenState extends SafeState<GroupsListScreen> {
 | 
			
		||||
 | 
			
		||||
    if (!await GroupsHelper().removeMembership(g.id))
 | 
			
		||||
      showSimpleSnack(
 | 
			
		||||
          context, tr("Could not remove your membership to this group!"));
 | 
			
		||||
          context, tr("Could not remove your membership to this group!")!);
 | 
			
		||||
 | 
			
		||||
    // Refresh the list of groups
 | 
			
		||||
    _refreshIndicatorKey.currentState.show();
 | 
			
		||||
    _refreshIndicatorKey.currentState!.show();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Add a group
 | 
			
		||||
@@ -127,10 +127,10 @@ class _GroupsListScreenState extends SafeState<GroupsListScreen> {
 | 
			
		||||
    try {
 | 
			
		||||
      final name = await askUserString(
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: tr("Group name"),
 | 
			
		||||
        message: tr("Name of the group to create"),
 | 
			
		||||
        title: tr("Group name")!,
 | 
			
		||||
        message: tr("Name of the group to create")!,
 | 
			
		||||
        defaultValue: "",
 | 
			
		||||
        hint: tr("Name of the group"),
 | 
			
		||||
        hint: tr("Name of the group")!,
 | 
			
		||||
        maxLength: 50,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@@ -138,10 +138,10 @@ class _GroupsListScreenState extends SafeState<GroupsListScreen> {
 | 
			
		||||
 | 
			
		||||
      final groupID = await GroupsHelper.create(name);
 | 
			
		||||
 | 
			
		||||
      MainController.of(context).openGroup(groupID);
 | 
			
		||||
      MainController.of(context)!.openGroup(groupID);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      print("Could not create a new group! $e\n$s");
 | 
			
		||||
      showSimpleSnack(context, tr("Could not create a new group!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not create a new group!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ class NotificationsScreen extends StatefulWidget {
 | 
			
		||||
  final bool useSmallDeleteButton;
 | 
			
		||||
 | 
			
		||||
  const NotificationsScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.useSmallDeleteButton = false,
 | 
			
		||||
  })  : assert(useSmallDeleteButton != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
@@ -38,9 +38,9 @@ class NotificationsScreen extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _NotificationsScreenState extends SafeState<NotificationsScreen> {
 | 
			
		||||
  NotificationsList _list;
 | 
			
		||||
  UsersList _users;
 | 
			
		||||
  GroupsList _groups;
 | 
			
		||||
  NotificationsList? _list;
 | 
			
		||||
  late UsersList _users;
 | 
			
		||||
  late GroupsList _groups;
 | 
			
		||||
  _Status _status = _Status.LOADING;
 | 
			
		||||
 | 
			
		||||
  final _refreshKey = GlobalKey<RefreshIndicatorState>();
 | 
			
		||||
@@ -64,12 +64,12 @@ class _NotificationsScreenState extends SafeState<NotificationsScreen> {
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      setStatus(_Status.NONE);
 | 
			
		||||
    } on Exception catch (e) {
 | 
			
		||||
    } on Exception catch (e, s) {
 | 
			
		||||
      print("Exception while getting the list of notifications!");
 | 
			
		||||
      print(e);
 | 
			
		||||
    } on Error catch (e) {
 | 
			
		||||
      print("$e, $s");
 | 
			
		||||
    } on Error catch (e, s) {
 | 
			
		||||
      print("Error while getting the list of notifications!");
 | 
			
		||||
      print(e.stackTrace);
 | 
			
		||||
      print("$e, $s");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -77,7 +77,7 @@ class _NotificationsScreenState extends SafeState<NotificationsScreen> {
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
 | 
			
		||||
    this.listen<NewNumberNotifsEvent>((d) => _refreshKey.currentState.show());
 | 
			
		||||
    this.listen<NewNumberNotifsEvent>((d) => _refreshKey.currentState!.show());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -121,14 +121,14 @@ class _NotificationsScreenState extends SafeState<NotificationsScreen> {
 | 
			
		||||
 | 
			
		||||
  /// Build body
 | 
			
		||||
  Widget _buildBody() {
 | 
			
		||||
    if (_status == _Status.ERROR || _list.length == 0)
 | 
			
		||||
    if (_status == _Status.ERROR || _list!.length == 0)
 | 
			
		||||
      return SingleChildScrollView(
 | 
			
		||||
        physics: AlwaysScrollableScrollPhysics(),
 | 
			
		||||
        child: _buildSingleChildCases(),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    return ListView(
 | 
			
		||||
      children: _list
 | 
			
		||||
      children: _list!
 | 
			
		||||
          .map((f) => _NotificationTile(
 | 
			
		||||
                notification: f,
 | 
			
		||||
                usersList: _users,
 | 
			
		||||
@@ -146,16 +146,16 @@ class _NotificationsScreenState extends SafeState<NotificationsScreen> {
 | 
			
		||||
          actions: [
 | 
			
		||||
            MaterialButton(
 | 
			
		||||
              onPressed: () => _loadList(),
 | 
			
		||||
              child: Text(tr("Try again".toUpperCase())),
 | 
			
		||||
              child: Text(tr("Try again".toUpperCase())!),
 | 
			
		||||
            )
 | 
			
		||||
          ]);
 | 
			
		||||
 | 
			
		||||
    // When there is no notification
 | 
			
		||||
    if (_list.length == 0)
 | 
			
		||||
    if (_list!.length == 0)
 | 
			
		||||
      return Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: Center(
 | 
			
		||||
          child: Text(tr("You do not have any notification now.")),
 | 
			
		||||
          child: Text(tr("You do not have any notification now.")!),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@@ -165,7 +165,7 @@ class _NotificationsScreenState extends SafeState<NotificationsScreen> {
 | 
			
		||||
  /// Delete a notification
 | 
			
		||||
  void _deleteNotification(n.Notification notif) async {
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _list.remove(notif);
 | 
			
		||||
      _list!.remove(notif);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    NotificationsHelper().markSeen(notif);
 | 
			
		||||
@@ -179,11 +179,11 @@ class _NotificationsScreenState extends SafeState<NotificationsScreen> {
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    if (!await NotificationsHelper().deleteAllNotifications()) {
 | 
			
		||||
      showSimpleSnack(context, tr("Could not delete all your notifications!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not delete all your notifications!")!);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _refreshKey.currentState.show();
 | 
			
		||||
    _refreshKey.currentState!.show();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -194,11 +194,11 @@ class _NotificationTile extends StatelessWidget {
 | 
			
		||||
  final void Function(n.Notification) onDelete;
 | 
			
		||||
 | 
			
		||||
  const _NotificationTile({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.notification,
 | 
			
		||||
    @required this.usersList,
 | 
			
		||||
    @required this.groupsList,
 | 
			
		||||
    @required this.onDelete,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.notification,
 | 
			
		||||
    required this.usersList,
 | 
			
		||||
    required this.groupsList,
 | 
			
		||||
    required this.onDelete,
 | 
			
		||||
  })  : assert(notification != null),
 | 
			
		||||
        assert(usersList != null),
 | 
			
		||||
        assert(groupsList != null),
 | 
			
		||||
@@ -214,51 +214,51 @@ class _NotificationTile extends StatelessWidget {
 | 
			
		||||
    switch (notification.type) {
 | 
			
		||||
      // Comment
 | 
			
		||||
      case n.NotificationType.COMMENT_CREATED:
 | 
			
		||||
        message += tr("posted a comment");
 | 
			
		||||
        message += tr("posted a comment")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      // Friendship requests
 | 
			
		||||
      case n.NotificationType.SENT_FRIEND_REQUEST:
 | 
			
		||||
        message += tr("sent you a friendship request.");
 | 
			
		||||
        message += tr("sent you a friendship request.")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case n.NotificationType.ACCEPTED_FRIEND_REQUEST:
 | 
			
		||||
        message += tr("accepted your friendship request.");
 | 
			
		||||
        message += tr("accepted your friendship request.")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case n.NotificationType.REJECTED_FRIEND_REQUEST:
 | 
			
		||||
        message += tr("rejected your friendship request.");
 | 
			
		||||
        message += tr("rejected your friendship request.")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      // Groups membership
 | 
			
		||||
      case n.NotificationType.SENT_GROUP_MEMBERSHIP_INVITATION:
 | 
			
		||||
        message += tr("invited you to join the group");
 | 
			
		||||
        message += tr("invited you to join the group")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case n.NotificationType.ACCEPTED_GROUP_MEMBERSHIP_INVITATION:
 | 
			
		||||
        message += tr("accepted his invitation to join the group");
 | 
			
		||||
        message += tr("accepted his invitation to join the group")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case n.NotificationType.REJECTED_GROUP_MEMBERSHIP_INVITATION:
 | 
			
		||||
        message += tr("rejected his invitation to join the group");
 | 
			
		||||
        message += tr("rejected his invitation to join the group")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case n.NotificationType.SENT_GROUP_MEMBERSHIP_REQUEST:
 | 
			
		||||
        message += tr("sent a request to join the group");
 | 
			
		||||
        message += tr("sent a request to join the group")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case n.NotificationType.ACCEPTED_GROUP_MEMBERSHIP_REQUEST:
 | 
			
		||||
        message += tr("accepted you request to join the group");
 | 
			
		||||
        message += tr("accepted you request to join the group")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case n.NotificationType.REJECTED_GROUP_MEMBERSHIP_REQUEST:
 | 
			
		||||
        message += tr("rejected your request to join the group");
 | 
			
		||||
        message += tr("rejected your request to join the group")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      // Generic element creation
 | 
			
		||||
      case n.NotificationType.ELEM_CREATED:
 | 
			
		||||
        if (notification.onElemType == n.NotificationElementType.POST)
 | 
			
		||||
          message += tr("created a new post");
 | 
			
		||||
          message += tr("created a new post")!;
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case n.NotificationType.ELEM_UPDATED:
 | 
			
		||||
@@ -274,24 +274,24 @@ class _NotificationTile extends StatelessWidget {
 | 
			
		||||
    // User page
 | 
			
		||||
    if (notification.fromContainerType == n.NotificationElementType.USER_PAGE) {
 | 
			
		||||
      if (notification.fromUser == notification.fromContainerId)
 | 
			
		||||
        message += tr("on his / her page");
 | 
			
		||||
        message += tr("on his / her page")!;
 | 
			
		||||
      else
 | 
			
		||||
        message += tr("on %user_name%'s page", args: {
 | 
			
		||||
          "user_name": usersList.getUser(notification.fromContainerId).fullName
 | 
			
		||||
        });
 | 
			
		||||
        })!;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Group page
 | 
			
		||||
    if (notification.fromContainerType ==
 | 
			
		||||
        n.NotificationElementType.GROUP_PAGE) {
 | 
			
		||||
      message += tr("on the group %group%.", args: {
 | 
			
		||||
        "group": groupsList[notification.fromContainerId].displayName
 | 
			
		||||
      });
 | 
			
		||||
        "group": groupsList[notification.fromContainerId]!.displayName
 | 
			
		||||
      })!;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Group membership
 | 
			
		||||
    if (notification.onElemType == n.NotificationElementType.GROUP_MEMBERSHIP)
 | 
			
		||||
      message += groupsList[notification.onElemId].displayName;
 | 
			
		||||
      message += groupsList[notification.onElemId]!.displayName;
 | 
			
		||||
 | 
			
		||||
    return CustomListTile(
 | 
			
		||||
      leading: AccountImageWidget(
 | 
			
		||||
@@ -299,11 +299,11 @@ class _NotificationTile extends StatelessWidget {
 | 
			
		||||
      ),
 | 
			
		||||
      onTap: () => _onTap(context),
 | 
			
		||||
      title: Text(message),
 | 
			
		||||
      subtitle: Text(diffTimeFromNowToStr(notification.timeCreate)),
 | 
			
		||||
      subtitle: Text(diffTimeFromNowToStr(notification.timeCreate)!),
 | 
			
		||||
      onLongPressOpenMenu: (position) {
 | 
			
		||||
        showMenu(context: context, position: position, items: [
 | 
			
		||||
          PopupMenuItem(
 | 
			
		||||
            child: Text(tr("Delete")),
 | 
			
		||||
            child: Text(tr("Delete")!),
 | 
			
		||||
            value: _PopupMenuActions.DELETE,
 | 
			
		||||
          ),
 | 
			
		||||
        ]).then(_popupMenuAction);
 | 
			
		||||
@@ -311,7 +311,7 @@ class _NotificationTile extends StatelessWidget {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _popupMenuAction(_PopupMenuActions value) {
 | 
			
		||||
  void _popupMenuAction(_PopupMenuActions? value) {
 | 
			
		||||
    switch (value) {
 | 
			
		||||
      case _PopupMenuActions.DELETE:
 | 
			
		||||
        onDelete(notification);
 | 
			
		||||
@@ -327,10 +327,10 @@ class _NotificationTile extends StatelessWidget {
 | 
			
		||||
      openUserPage(userID: notification.fromUser, context: context);
 | 
			
		||||
    } else if (notification.onElemType ==
 | 
			
		||||
        n.NotificationElementType.GROUP_MEMBERSHIP) {
 | 
			
		||||
      MainController.of(context).openGroup(notification.onElemId);
 | 
			
		||||
      MainController.of(context)!.openGroup(notification.onElemId);
 | 
			
		||||
    } else {
 | 
			
		||||
      showSimpleSnack(context,
 | 
			
		||||
          tr("This kind of notification is not supported yet by this application."));
 | 
			
		||||
          tr("This kind of notification is not supported yet by this application.")!);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,8 @@ class OtherUserFriendsListScreen extends StatefulWidget {
 | 
			
		||||
  final bool enableAppBar;
 | 
			
		||||
 | 
			
		||||
  const OtherUserFriendsListScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.userID,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.userID,
 | 
			
		||||
    this.enableAppBar = true,
 | 
			
		||||
  })  : assert(userID != null),
 | 
			
		||||
        assert(enableAppBar != null),
 | 
			
		||||
@@ -33,12 +33,12 @@ class _OtherUserFriendsListScreenState
 | 
			
		||||
  final FriendsHelper friendsHelper = FriendsHelper();
 | 
			
		||||
  final UsersHelper usersHelper = UsersHelper();
 | 
			
		||||
 | 
			
		||||
  Set<int> _friendsList;
 | 
			
		||||
  UsersList _usersInfo;
 | 
			
		||||
  late Set<int> _friendsList;
 | 
			
		||||
  UsersList? _usersInfo;
 | 
			
		||||
  bool _error = false;
 | 
			
		||||
 | 
			
		||||
  String get _routeName => tr("Friends of %name%",
 | 
			
		||||
      args: {"name": _usersInfo.getUser(widget.userID).displayName});
 | 
			
		||||
  String? get _routeName => tr("Friends of %name%",
 | 
			
		||||
      args: {"name": _usersInfo!.getUser(widget.userID).displayName});
 | 
			
		||||
 | 
			
		||||
  void setError(bool e) => setState(() => _error = e);
 | 
			
		||||
 | 
			
		||||
@@ -79,16 +79,16 @@ class _OtherUserFriendsListScreenState
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: widget.enableAppBar
 | 
			
		||||
          ? AppBar(
 | 
			
		||||
              title: Text(_routeName),
 | 
			
		||||
              title: Text(_routeName!),
 | 
			
		||||
            )
 | 
			
		||||
          : null,
 | 
			
		||||
      body: ListView.builder(
 | 
			
		||||
        itemCount: _friendsList.length,
 | 
			
		||||
        itemBuilder: (c, i) => SimpleUserTile(
 | 
			
		||||
          user: _usersInfo.getUser(_friendsList.elementAt(i)),
 | 
			
		||||
          user: _usersInfo!.getUser(_friendsList.elementAt(i)),
 | 
			
		||||
          onTap: (u) => openUserPage(
 | 
			
		||||
            context: context,
 | 
			
		||||
            userID: u.id,
 | 
			
		||||
            userID: u.id!,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
@@ -104,7 +104,7 @@ class _OtherUserFriendsListScreenState
 | 
			
		||||
        actions: [
 | 
			
		||||
          TextButton(
 | 
			
		||||
            child: Text(
 | 
			
		||||
              tr("Try again").toUpperCase(),
 | 
			
		||||
              tr("Try again")!.toUpperCase(),
 | 
			
		||||
              style: TextStyle(color: Colors.white),
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: load,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,9 @@ class SearchScreen extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SearchScreenState extends State<SearchScreen> {
 | 
			
		||||
  SearchResultsList _searchResultsList;
 | 
			
		||||
  UsersList _usersList;
 | 
			
		||||
  GroupsList _groupsList;
 | 
			
		||||
  SearchResultsList? _searchResultsList;
 | 
			
		||||
  late UsersList _usersList;
 | 
			
		||||
  late GroupsList _groupsList;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@@ -44,13 +44,13 @@ class _SearchScreenState extends State<SearchScreen> {
 | 
			
		||||
              ? Container()
 | 
			
		||||
              : Expanded(
 | 
			
		||||
                  child: ListView(
 | 
			
		||||
                    children: _searchResultsList
 | 
			
		||||
                    children: _searchResultsList!
 | 
			
		||||
                        .map((f) => f.kind == SearchResultKind.USER
 | 
			
		||||
                            ? _SearchResultUser(
 | 
			
		||||
                                user: _usersList.getUser(f.id),
 | 
			
		||||
                              )
 | 
			
		||||
                            : _SearchResultGroup(
 | 
			
		||||
                                group: _groupsList[f.id],
 | 
			
		||||
                                group: _groupsList[f.id]!,
 | 
			
		||||
                              ))
 | 
			
		||||
                        .toList(),
 | 
			
		||||
                  ),
 | 
			
		||||
@@ -78,7 +78,7 @@ class _SearchScreenState extends State<SearchScreen> {
 | 
			
		||||
      print(e);
 | 
			
		||||
      print(stack);
 | 
			
		||||
 | 
			
		||||
      showSimpleSnack(context, tr("Could not peform search!"));
 | 
			
		||||
      showSimpleSnack(context, tr("Could not peform search!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -86,7 +86,7 @@ class _SearchScreenState extends State<SearchScreen> {
 | 
			
		||||
class _SearchResultUser extends StatelessWidget {
 | 
			
		||||
  final User user;
 | 
			
		||||
 | 
			
		||||
  const _SearchResultUser({Key key, this.user})
 | 
			
		||||
  const _SearchResultUser({Key? key, required this.user})
 | 
			
		||||
      : assert(user != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -97,7 +97,7 @@ class _SearchResultUser extends StatelessWidget {
 | 
			
		||||
        user: user,
 | 
			
		||||
      ),
 | 
			
		||||
      title: Text(user.displayName),
 | 
			
		||||
      onTap: () => MainController.of(context).openUserPage(user.id),
 | 
			
		||||
      onTap: () => MainController.of(context)!.openUserPage(user.id!),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -105,7 +105,7 @@ class _SearchResultUser extends StatelessWidget {
 | 
			
		||||
class _SearchResultGroup extends StatelessWidget {
 | 
			
		||||
  final Group group;
 | 
			
		||||
 | 
			
		||||
  const _SearchResultGroup({Key key, this.group})
 | 
			
		||||
  const _SearchResultGroup({Key? key, required this.group})
 | 
			
		||||
      : assert(group != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -114,8 +114,8 @@ class _SearchResultGroup extends StatelessWidget {
 | 
			
		||||
    return ListTile(
 | 
			
		||||
      leading: GroupIcon(group: group),
 | 
			
		||||
      title: Text(group.displayName),
 | 
			
		||||
      subtitle: Text(tr("Group")),
 | 
			
		||||
      onTap: () => MainController.of(context).openGroup(group.id),
 | 
			
		||||
      subtitle: Text(tr("Group")!),
 | 
			
		||||
      onTap: () => MainController.of(context)!.openGroup(group.id),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import 'package:comunic/helpers/users_helper.dart';
 | 
			
		||||
import 'package:comunic/lists/groups_list.dart';
 | 
			
		||||
import 'package:comunic/lists/unread_conversations_list.dart';
 | 
			
		||||
import 'package:comunic/lists/users_list.dart';
 | 
			
		||||
import 'package:comunic/models/unread_conversation.dart';
 | 
			
		||||
import 'package:comunic/ui/routes/main_route/main_route.dart';
 | 
			
		||||
import 'package:comunic/ui/widgets/async_screen_widget.dart';
 | 
			
		||||
import 'package:comunic/ui/widgets/conversation_image_widget.dart';
 | 
			
		||||
@@ -25,9 +26,9 @@ class UnreadConversationsScreen extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
class _UnreadConversationsScreenState
 | 
			
		||||
    extends SafeState<UnreadConversationsScreen> {
 | 
			
		||||
  UnreadConversationsList _list;
 | 
			
		||||
  UsersList _users;
 | 
			
		||||
  GroupsList _groups;
 | 
			
		||||
  late UnreadConversationsList _list;
 | 
			
		||||
  UsersList? _users;
 | 
			
		||||
  GroupsList? _groups;
 | 
			
		||||
 | 
			
		||||
  final _key = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
 | 
			
		||||
@@ -41,7 +42,7 @@ class _UnreadConversationsScreenState
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
 | 
			
		||||
    listen<NewNumberUnreadConversations>((e) => _key.currentState.refresh());
 | 
			
		||||
    listen<NewNumberUnreadConversations>((e) => _key.currentState!.refresh());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -50,7 +51,7 @@ class _UnreadConversationsScreenState
 | 
			
		||||
      key: _key,
 | 
			
		||||
      onReload: _refresh,
 | 
			
		||||
      onBuild: _buildList,
 | 
			
		||||
      errorMessage: tr("Could not load the list of unread conversations!"),
 | 
			
		||||
      errorMessage: tr("Could not load the list of unread conversations!")!,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -61,7 +62,7 @@ class _UnreadConversationsScreenState
 | 
			
		||||
        child: Padding(
 | 
			
		||||
          padding: const EdgeInsets.all(8.0),
 | 
			
		||||
          child: Text(
 | 
			
		||||
            tr("You do not have any unread conversation yet..."),
 | 
			
		||||
            tr("You do not have any unread conversation yet...")!,
 | 
			
		||||
            textAlign: TextAlign.center,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
@@ -74,34 +75,34 @@ class _UnreadConversationsScreenState
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _tileBuilder(BuildContext context, int index) {
 | 
			
		||||
    final conv = _list[index];
 | 
			
		||||
    final UnreadConversation conv = _list[index];
 | 
			
		||||
 | 
			
		||||
    final message = _list[index].message;
 | 
			
		||||
 | 
			
		||||
    final singleUserConv = conv.conv.members.length < 3;
 | 
			
		||||
    final singleUserConv = conv.conv.members!.length < 3;
 | 
			
		||||
 | 
			
		||||
    String messageStr;
 | 
			
		||||
    String? messageStr;
 | 
			
		||||
    if (message.hasFile)
 | 
			
		||||
      messageStr = tr("New file");
 | 
			
		||||
    else if (message.hasMessage)
 | 
			
		||||
      messageStr = singleUserConv
 | 
			
		||||
          ? message.message.content
 | 
			
		||||
          : tr("%1% : %2%", args: {
 | 
			
		||||
              "1": _users.getUser(message.userID).fullName,
 | 
			
		||||
              "1": _users!.getUser(message.userID).fullName,
 | 
			
		||||
              "2": message.message.content,
 | 
			
		||||
            });
 | 
			
		||||
    else
 | 
			
		||||
      message.serverMessage.getText(_users);
 | 
			
		||||
      message.serverMessage!.getText(_users);
 | 
			
		||||
 | 
			
		||||
    return ListTile(
 | 
			
		||||
      leading: ConversationImageWidget(
 | 
			
		||||
        conversation: conv.conv,
 | 
			
		||||
        users: _users,
 | 
			
		||||
        users: _users!,
 | 
			
		||||
        group: conv.conv.isGroupConversation
 | 
			
		||||
            ? _groups.getGroup(conv.conv.groupID)
 | 
			
		||||
            ? _groups!.getGroup(conv.conv.groupID)
 | 
			
		||||
            : null,
 | 
			
		||||
      ),
 | 
			
		||||
      title: Text(ConversationsHelper.getConversationName(conv.conv, _users)),
 | 
			
		||||
      title: Text(ConversationsHelper.getConversationName(conv.conv, _users)!),
 | 
			
		||||
      subtitle: RichText(
 | 
			
		||||
        text: TextSpan(style: Theme.of(context).textTheme.bodyText2, children: [
 | 
			
		||||
          TextSpan(
 | 
			
		||||
@@ -110,9 +111,9 @@ class _UnreadConversationsScreenState
 | 
			
		||||
          ),
 | 
			
		||||
        ]),
 | 
			
		||||
      ),
 | 
			
		||||
      trailing: Text(diffTimeFromNowToStr(conv.message.timeSent)),
 | 
			
		||||
      trailing: Text(diffTimeFromNowToStr(conv.message.timeSent!)!),
 | 
			
		||||
      onTap: () =>
 | 
			
		||||
          MainController.of(context).openConversationById(conv.conv.id),
 | 
			
		||||
          MainController.of(context)!.openConversationById(conv.conv.id!),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ class UpdateConversationScreen extends StatefulWidget {
 | 
			
		||||
  final convID;
 | 
			
		||||
 | 
			
		||||
  const UpdateConversationScreen({
 | 
			
		||||
    Key key,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    this.convID,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -41,19 +41,19 @@ class UpdateConversationScreen extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
  Conversation _conversation;
 | 
			
		||||
  late Conversation _conversation;
 | 
			
		||||
 | 
			
		||||
  TextEditingController _nameController = TextEditingController();
 | 
			
		||||
  TextEditingController _colorController = TextEditingController();
 | 
			
		||||
  UsersList _members = UsersList();
 | 
			
		||||
  Set<int> _admins = Set();
 | 
			
		||||
  Set<int?> _admins = Set();
 | 
			
		||||
  bool _followConversation = true;
 | 
			
		||||
  bool _canEveryoneAddMembers = true;
 | 
			
		||||
  String _image;
 | 
			
		||||
  bool? _canEveryoneAddMembers = true;
 | 
			
		||||
  String? _image;
 | 
			
		||||
 | 
			
		||||
  String get _conversationColor => _colorController.text;
 | 
			
		||||
 | 
			
		||||
  Color get _color {
 | 
			
		||||
  Color? get _color {
 | 
			
		||||
    if (_conversationColor == null || _conversationColor.isEmpty) return null;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
@@ -72,7 +72,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
      isUpdating && _conversation.isGroupConversation;
 | 
			
		||||
 | 
			
		||||
  bool get _canAddMembers =>
 | 
			
		||||
      (isAdmin || _conversation.canEveryoneAddMembers) &&
 | 
			
		||||
      (isAdmin || _conversation.canEveryoneAddMembers!) &&
 | 
			
		||||
      (!isUpdating || !_conversation.isManaged);
 | 
			
		||||
 | 
			
		||||
  get _isValid => _members.length > 0;
 | 
			
		||||
@@ -104,8 +104,8 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          leading: ComunicBackButton(),
 | 
			
		||||
          title: Text(isUpdating
 | 
			
		||||
              ? tr("Update conversation")
 | 
			
		||||
              : tr("Create a conversation")),
 | 
			
		||||
              ? tr("Update conversation")!
 | 
			
		||||
              : tr("Create a conversation")!),
 | 
			
		||||
          actions: [
 | 
			
		||||
            IconButton(
 | 
			
		||||
                icon: Icon(Icons.check),
 | 
			
		||||
@@ -115,7 +115,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
        body: AsyncScreenWidget(
 | 
			
		||||
          onReload: _init,
 | 
			
		||||
          onBuild: _buildBody,
 | 
			
		||||
          errorMessage: tr("Failed to load conversation settings!"),
 | 
			
		||||
          errorMessage: tr("Failed to load conversation settings!")!,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@@ -126,7 +126,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            _isGroupConversation
 | 
			
		||||
                ? Text(tr("This conversation is managed by a group"))
 | 
			
		||||
                ? Text(tr("This conversation is managed by a group")!)
 | 
			
		||||
                : Container(),
 | 
			
		||||
 | 
			
		||||
            // Conversation name
 | 
			
		||||
@@ -163,7 +163,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
                    _followConversation = b;
 | 
			
		||||
                  }),
 | 
			
		||||
                ),
 | 
			
		||||
                Text(tr("Follow conversation"))
 | 
			
		||||
                Text(tr("Follow conversation")!)
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
 | 
			
		||||
@@ -173,7 +173,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
                : Row(
 | 
			
		||||
                    children: <Widget>[
 | 
			
		||||
                      Switch.adaptive(
 | 
			
		||||
                        value: _canEveryoneAddMembers,
 | 
			
		||||
                        value: _canEveryoneAddMembers!,
 | 
			
		||||
                        onChanged: isAdmin
 | 
			
		||||
                            ? (b) => setState(() {
 | 
			
		||||
                                  _canEveryoneAddMembers = b;
 | 
			
		||||
@@ -182,7 +182,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
                      ),
 | 
			
		||||
                      Flexible(
 | 
			
		||||
                          child: Text(tr(
 | 
			
		||||
                              "Allow all members of the conversation to add users")))
 | 
			
		||||
                              "Allow all members of the conversation to add users")!))
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
 | 
			
		||||
@@ -190,7 +190,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
            PickUserWidget(
 | 
			
		||||
                resetOnChoose: true,
 | 
			
		||||
                keepFocusOnChoose: true,
 | 
			
		||||
                label: tr("Add member"),
 | 
			
		||||
                label: tr("Add member")!,
 | 
			
		||||
                enabled: _canAddMembers,
 | 
			
		||||
                onSelectUser: (user) => _addMember(user)),
 | 
			
		||||
 | 
			
		||||
@@ -215,12 +215,12 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
                onSelected: (choice) => _membersMenuItemSelected(user, choice),
 | 
			
		||||
                itemBuilder: (c) => <PopupMenuEntry<_MembersMenuChoices>>[
 | 
			
		||||
                  PopupMenuItem(
 | 
			
		||||
                    child: Text(tr("Toggle admin status")),
 | 
			
		||||
                    child: Text(tr("Toggle admin status")!),
 | 
			
		||||
                    value: _MembersMenuChoices.TOGGLE_ADMIN_STATUS,
 | 
			
		||||
                    enabled: isUpdating && isAdmin && user.id != userID(),
 | 
			
		||||
                  ),
 | 
			
		||||
                  PopupMenuItem(
 | 
			
		||||
                    child: Text(tr("Remove")),
 | 
			
		||||
                    child: Text(tr("Remove")!),
 | 
			
		||||
                    value: _MembersMenuChoices.REMOVE,
 | 
			
		||||
                    enabled: isAdmin && user.id != userID(),
 | 
			
		||||
                  ),
 | 
			
		||||
@@ -260,7 +260,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
      setState(() => _members.insert(0, user));
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to add member to conversation!"));
 | 
			
		||||
      snack(context, tr("Failed to add member to conversation!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -275,14 +275,14 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to remove member!"));
 | 
			
		||||
      snack(context, tr("Failed to remove member!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _toggleAdminStatus(User user) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final setAdmin = !_admins.contains(user.id);
 | 
			
		||||
      await ConversationsHelper.setAdmin(_conversation.id, user.id, setAdmin);
 | 
			
		||||
      await ConversationsHelper.setAdmin(_conversation.id!, user.id!, setAdmin);
 | 
			
		||||
 | 
			
		||||
      setState(() {
 | 
			
		||||
        if (!setAdmin)
 | 
			
		||||
@@ -292,7 +292,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to toggle admin status of user!"));
 | 
			
		||||
      snack(context, tr("Failed to toggle admin status of user!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -306,17 +306,17 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
                name: _nameController.text,
 | 
			
		||||
                members: _members.map((element) => element.id).toList(),
 | 
			
		||||
                follow: _followConversation,
 | 
			
		||||
                canEveryoneAddMembers: _canEveryoneAddMembers,
 | 
			
		||||
                color: _color));
 | 
			
		||||
                canEveryoneAddMembers: _canEveryoneAddMembers!,
 | 
			
		||||
                color: _color)) ;
 | 
			
		||||
 | 
			
		||||
        MainController.of(context).popPage();
 | 
			
		||||
        MainController.of(context).openConversationById(conversationID);
 | 
			
		||||
        MainController.of(context)!.popPage();
 | 
			
		||||
        MainController.of(context)!.openConversationById(conversationID);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Update conversation settings
 | 
			
		||||
      final newSettings = NewConversationsSettings(
 | 
			
		||||
        convID: _conversation.id,
 | 
			
		||||
        convID: _conversation.id!,
 | 
			
		||||
        following: _followConversation,
 | 
			
		||||
        isComplete: isAdmin,
 | 
			
		||||
        name: _nameController.text,
 | 
			
		||||
@@ -326,10 +326,10 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
 | 
			
		||||
      await ConversationsHelper.updateConversation(newSettings);
 | 
			
		||||
 | 
			
		||||
      MainController.of(context).popPage();
 | 
			
		||||
      MainController.of(context)!.popPage();
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to update conversation settings!"));
 | 
			
		||||
      snack(context, tr("Failed to update conversation settings!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -337,12 +337,12 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
  Widget _buildConversationImageWidget() => Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          SizedBox(height: 10),
 | 
			
		||||
          Text(tr("Conversation logo"),
 | 
			
		||||
          Text(tr("Conversation logo")!,
 | 
			
		||||
              style: TextStyle(fontWeight: FontWeight.bold)),
 | 
			
		||||
          SizedBox(height: 5),
 | 
			
		||||
          _image == null
 | 
			
		||||
              ? Text("No logo defined yet.")
 | 
			
		||||
              : CachedNetworkImage(imageUrl: _image),
 | 
			
		||||
              : CachedNetworkImage(imageUrl: _image!),
 | 
			
		||||
          SizedBox(height: 5),
 | 
			
		||||
          isAdmin
 | 
			
		||||
              ? Row(
 | 
			
		||||
@@ -350,14 +350,14 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
                  children: [
 | 
			
		||||
                    OutlinedButton(
 | 
			
		||||
                      onPressed: _uploadNewLogo,
 | 
			
		||||
                      child: Text(tr("Change logo")),
 | 
			
		||||
                      child: Text(tr("Change logo")!),
 | 
			
		||||
                    ),
 | 
			
		||||
                    SizedBox(width: 5),
 | 
			
		||||
                    _image == null
 | 
			
		||||
                        ? Container()
 | 
			
		||||
                        : ElevatedButton(
 | 
			
		||||
                            onPressed: _deleteLogo,
 | 
			
		||||
                            child: Text(tr("Delete logo")),
 | 
			
		||||
                            child: Text(tr("Delete logo")!),
 | 
			
		||||
                            style: ButtonStyle(
 | 
			
		||||
                                backgroundColor:
 | 
			
		||||
                                    MaterialStateProperty.all(Colors.red)),
 | 
			
		||||
@@ -375,8 +375,8 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
      final newLogo = await showPickFileDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        allowedMimeTypes: ["image/png", "image/jpeg", "image/gif"],
 | 
			
		||||
        imageMaxWidth: srvConfig.conversationsPolicy.maxLogoWidth,
 | 
			
		||||
        imageMaxHeight: srvConfig.conversationsPolicy.maxLogoHeight,
 | 
			
		||||
        imageMaxWidth: srvConfig!.conversationsPolicy.maxLogoWidth,
 | 
			
		||||
        imageMaxHeight: srvConfig!.conversationsPolicy.maxLogoHeight,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (newLogo == null) return;
 | 
			
		||||
@@ -388,7 +388,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
      setState(() => _image = newConvSettings.logoURL);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to change conversation logo !"));
 | 
			
		||||
      snack(context, tr("Failed to change conversation logo !")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -404,7 +404,7 @@ class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
      setState(() => _image = null);
 | 
			
		||||
    } catch (e, s) {
 | 
			
		||||
      logError(e, s);
 | 
			
		||||
      snack(context, tr("Failed to remove conversation logo!"));
 | 
			
		||||
      snack(context, tr("Failed to remove conversation logo!")!);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ import 'package:flutter/material.dart';
 | 
			
		||||
class UserAccessDeniedScreen extends StatefulWidget {
 | 
			
		||||
  final int userID;
 | 
			
		||||
 | 
			
		||||
  const UserAccessDeniedScreen({Key key, @required this.userID})
 | 
			
		||||
  const UserAccessDeniedScreen({Key? key, required this.userID})
 | 
			
		||||
      : assert(userID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -31,8 +31,8 @@ class _UserAccessDeniedScreenState extends SafeState<UserAccessDeniedScreen> {
 | 
			
		||||
 | 
			
		||||
  final _key = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
 | 
			
		||||
  FriendStatus _status;
 | 
			
		||||
  User _user;
 | 
			
		||||
  late FriendStatus _status;
 | 
			
		||||
  late User _user;
 | 
			
		||||
 | 
			
		||||
  Future<void> refresh() async {
 | 
			
		||||
    final status = await friendsHelper.getFriendshipStatus(widget.userID);
 | 
			
		||||
@@ -40,7 +40,7 @@ class _UserAccessDeniedScreenState extends SafeState<UserAccessDeniedScreen> {
 | 
			
		||||
 | 
			
		||||
    // Check if the two users are friend now
 | 
			
		||||
    if (status.areFriend) {
 | 
			
		||||
      final controller = MainController.of(context);
 | 
			
		||||
      final controller = MainController.of(context)!;
 | 
			
		||||
      controller.popPage();
 | 
			
		||||
      controller.openUserPage(widget.userID);
 | 
			
		||||
    }
 | 
			
		||||
@@ -55,7 +55,7 @@ class _UserAccessDeniedScreenState extends SafeState<UserAccessDeniedScreen> {
 | 
			
		||||
        key: _key,
 | 
			
		||||
        onReload: refresh,
 | 
			
		||||
        onBuild: _buildPage,
 | 
			
		||||
        errorMessage: tr("Could not load friendship information!"));
 | 
			
		||||
        errorMessage: tr("Could not load friendship information!")!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildPage() {
 | 
			
		||||
@@ -77,10 +77,10 @@ class _UserAccessDeniedScreenState extends SafeState<UserAccessDeniedScreen> {
 | 
			
		||||
                _user.displayName,
 | 
			
		||||
                style: TextStyle(fontSize: 25.0),
 | 
			
		||||
              ),
 | 
			
		||||
              Text(tr("This account is private.")),
 | 
			
		||||
              Text(tr("This account is private.")!),
 | 
			
		||||
              FriendshipStatusWidget(
 | 
			
		||||
                status: _status,
 | 
			
		||||
                onFriendshipUpdated: () => _key.currentState.refresh(),
 | 
			
		||||
                onFriendshipUpdated: () => _key.currentState!.refresh(),
 | 
			
		||||
              )
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ enum _PageStatus { LOADING, ERROR, READY }
 | 
			
		||||
class UserPageScreen extends StatefulWidget {
 | 
			
		||||
  final int userID;
 | 
			
		||||
 | 
			
		||||
  const UserPageScreen({Key key, @required this.userID})
 | 
			
		||||
  const UserPageScreen({Key? key, required this.userID})
 | 
			
		||||
      : assert(userID != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -34,8 +34,8 @@ class _UserPageScreenState extends SafeState<UserPageScreen> {
 | 
			
		||||
 | 
			
		||||
  // Objects members
 | 
			
		||||
  _PageStatus _status = _PageStatus.LOADING;
 | 
			
		||||
  AdvancedUserInfo _userInfo;
 | 
			
		||||
  FriendStatus _frienshipStatus;
 | 
			
		||||
  late AdvancedUserInfo _userInfo;
 | 
			
		||||
  FriendStatus? _frienshipStatus;
 | 
			
		||||
  final _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
 | 
			
		||||
 | 
			
		||||
  _setStatus(_PageStatus s) => setState(() => _status = s);
 | 
			
		||||
@@ -65,7 +65,7 @@ class _UserPageScreenState extends SafeState<UserPageScreen> {
 | 
			
		||||
      _setStatus(_PageStatus.ERROR);
 | 
			
		||||
 | 
			
		||||
      if (e.cause == GetUserAdvancedInformationErrorCause.NOT_AUTHORIZED) {
 | 
			
		||||
        final controller = MainController.of(context);
 | 
			
		||||
        final controller = MainController.of(context)!;
 | 
			
		||||
        controller.popPage();
 | 
			
		||||
        controller.openUserAccessDeniedPage(widget.userID);
 | 
			
		||||
      }
 | 
			
		||||
@@ -94,7 +94,7 @@ class _UserPageScreenState extends SafeState<UserPageScreen> {
 | 
			
		||||
  Widget _buildError() {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: Text(tr("Error")),
 | 
			
		||||
        title: Text(tr("Error")!),
 | 
			
		||||
      ),
 | 
			
		||||
      body: Center(
 | 
			
		||||
          child:
 | 
			
		||||
@@ -102,7 +102,7 @@ class _UserPageScreenState extends SafeState<UserPageScreen> {
 | 
			
		||||
        TextButton(
 | 
			
		||||
          onPressed: _getUserInfo,
 | 
			
		||||
          child: Text(
 | 
			
		||||
            tr("Retry").toUpperCase(),
 | 
			
		||||
            tr("Retry")!.toUpperCase(),
 | 
			
		||||
            style: TextStyle(color: Colors.white),
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
@@ -114,12 +114,12 @@ class _UserPageScreenState extends SafeState<UserPageScreen> {
 | 
			
		||||
    return isTablet(context)
 | 
			
		||||
        ? UserPageTablet(
 | 
			
		||||
            userInfo: _userInfo,
 | 
			
		||||
            onNeedRefresh: () => _refreshIndicatorKey.currentState.show(),
 | 
			
		||||
            onNeedRefresh: () => _refreshIndicatorKey.currentState!.show(),
 | 
			
		||||
            friendshipStatus: _frienshipStatus,
 | 
			
		||||
          )
 | 
			
		||||
        : UserMobilePage(
 | 
			
		||||
            userInfo: _userInfo,
 | 
			
		||||
            onNeedRefresh: () => _refreshIndicatorKey.currentState.show(),
 | 
			
		||||
            onNeedRefresh: () => _refreshIndicatorKey.currentState!.show(),
 | 
			
		||||
          );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,8 +19,8 @@ class AboutUserSection extends StatefulWidget {
 | 
			
		||||
  final AdvancedUserInfo user;
 | 
			
		||||
 | 
			
		||||
  const AboutUserSection({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.user,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.user,
 | 
			
		||||
  })  : assert(user != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +29,7 @@ class AboutUserSection extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _AboutUserSectionState extends State<AboutUserSection> {
 | 
			
		||||
  FriendStatus _friendStatus;
 | 
			
		||||
  late FriendStatus _friendStatus;
 | 
			
		||||
 | 
			
		||||
  final _screenKey = GlobalKey<AsyncScreenWidgetState>();
 | 
			
		||||
 | 
			
		||||
@@ -38,14 +38,14 @@ class _AboutUserSectionState extends State<AboutUserSection> {
 | 
			
		||||
      _friendStatus = await FriendsHelper().getFriendshipStatus(widget.user.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _toggleRefresh() => _screenKey.currentState.refresh();
 | 
			
		||||
  void _toggleRefresh() => _screenKey.currentState!.refresh();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => AsyncScreenWidget(
 | 
			
		||||
      key: _screenKey,
 | 
			
		||||
      onReload: _init,
 | 
			
		||||
      onBuild: _buildList,
 | 
			
		||||
      errorMessage: tr("Failed to load user information!"));
 | 
			
		||||
      errorMessage: tr("Failed to load user information!")!);
 | 
			
		||||
 | 
			
		||||
  Widget _buildList() => ListView(
 | 
			
		||||
        children: [
 | 
			
		||||
@@ -58,7 +58,7 @@ class _AboutUserSectionState extends State<AboutUserSection> {
 | 
			
		||||
          widget.user.hasPersonalWebsite
 | 
			
		||||
              ? ListTile(
 | 
			
		||||
                  leading: Icon(Icons.link),
 | 
			
		||||
                  title: Text(tr("Personal Website")),
 | 
			
		||||
                  title: Text(tr("Personal Website")!),
 | 
			
		||||
                  subtitle: Text(widget.user.personalWebsite),
 | 
			
		||||
                  onTap: () => launch(widget.user.personalWebsite),
 | 
			
		||||
                )
 | 
			
		||||
@@ -68,7 +68,7 @@ class _AboutUserSectionState extends State<AboutUserSection> {
 | 
			
		||||
          widget.user.hasPublicNote
 | 
			
		||||
              ? ListTile(
 | 
			
		||||
                  leading: Icon(Icons.note),
 | 
			
		||||
                  title: Text(tr("Note")),
 | 
			
		||||
                  title: Text(tr("Note")!),
 | 
			
		||||
                  subtitle: TextWidget(
 | 
			
		||||
                      content: DisplayedString(widget.user.publicNote)),
 | 
			
		||||
                )
 | 
			
		||||
@@ -78,7 +78,7 @@ class _AboutUserSectionState extends State<AboutUserSection> {
 | 
			
		||||
          widget.user.hasVirtualDirectory
 | 
			
		||||
              ? ListTile(
 | 
			
		||||
                  leading: Icon(Icons.alternate_email),
 | 
			
		||||
                  title: Text(tr("Virtual directory")),
 | 
			
		||||
                  title: Text(tr("Virtual directory")!),
 | 
			
		||||
                  subtitle: Text("@${widget.user.virtualDirectory}"))
 | 
			
		||||
              : Container(),
 | 
			
		||||
 | 
			
		||||
@@ -87,10 +87,10 @@ class _AboutUserSectionState extends State<AboutUserSection> {
 | 
			
		||||
              ? Container()
 | 
			
		||||
              : ListTile(
 | 
			
		||||
                  leading: Icon(Icons.email_outlined),
 | 
			
		||||
                  title: Text(tr("Email address")),
 | 
			
		||||
                  subtitle: Text(widget.user.emailAddress),
 | 
			
		||||
                  title: Text(tr("Email address")!),
 | 
			
		||||
                  subtitle: Text(widget.user.emailAddress!),
 | 
			
		||||
                  onTap: () =>
 | 
			
		||||
                      copyToClipboard(context, widget.user.emailAddress),
 | 
			
		||||
                      copyToClipboard(context, widget.user.emailAddress!),
 | 
			
		||||
                ),
 | 
			
		||||
 | 
			
		||||
          // User location
 | 
			
		||||
@@ -98,16 +98,16 @@ class _AboutUserSectionState extends State<AboutUserSection> {
 | 
			
		||||
              ? Container()
 | 
			
		||||
              : ListTile(
 | 
			
		||||
                  leading: Icon(Icons.location_on),
 | 
			
		||||
                  title: Text(tr("Location")),
 | 
			
		||||
                  subtitle: Text(widget.user.location),
 | 
			
		||||
                  onTap: () => copyToClipboard(context, widget.user.location),
 | 
			
		||||
                  title: Text(tr("Location")!),
 | 
			
		||||
                  subtitle: Text(widget.user.location!),
 | 
			
		||||
                  onTap: () => copyToClipboard(context, widget.user.location!),
 | 
			
		||||
                ),
 | 
			
		||||
 | 
			
		||||
          // Number of friends
 | 
			
		||||
          widget.user.isFriendsListPublic
 | 
			
		||||
              ? ListTile(
 | 
			
		||||
                  leading: Icon(Icons.group),
 | 
			
		||||
                  title: Text(tr("Number of friends")),
 | 
			
		||||
                  title: Text(tr("Number of friends")!),
 | 
			
		||||
                  subtitle: Text(widget.user.numberFriends.toString()),
 | 
			
		||||
                )
 | 
			
		||||
              : Container(),
 | 
			
		||||
@@ -115,20 +115,20 @@ class _AboutUserSectionState extends State<AboutUserSection> {
 | 
			
		||||
          // Member for
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: Icon(Icons.access_time_rounded),
 | 
			
		||||
            title: Text(tr("Member for")),
 | 
			
		||||
            title: Text(tr("Member for")!),
 | 
			
		||||
            subtitle:
 | 
			
		||||
                Text(diffTimeFromNowToStr(widget.user.accountCreationTime)),
 | 
			
		||||
                Text(diffTimeFromNowToStr(widget.user.accountCreationTime)!),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Account visibility
 | 
			
		||||
          ListTile(
 | 
			
		||||
            leading: Icon(Icons.remove_red_eye),
 | 
			
		||||
            title: Text(tr("Account visibility")),
 | 
			
		||||
            title: Text(tr("Account visibility")!),
 | 
			
		||||
            subtitle: Text(widget.user.pageVisibility == UserPageVisibility.OPEN
 | 
			
		||||
                ? tr("Open page")
 | 
			
		||||
                ? tr("Open page")!
 | 
			
		||||
                : (widget.user.pageVisibility == UserPageVisibility.PUBLIC
 | 
			
		||||
                    ? tr("Public page")
 | 
			
		||||
                    : tr("Private page"))),
 | 
			
		||||
                    ? tr("Public page")!
 | 
			
		||||
                    : tr("Private page")!)),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@ class UserPageHeader extends StatelessWidget {
 | 
			
		||||
  final Color bgColor;
 | 
			
		||||
 | 
			
		||||
  const UserPageHeader({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.user,
 | 
			
		||||
    @required this.bgColor,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.user,
 | 
			
		||||
    required this.bgColor,
 | 
			
		||||
  })  : assert(user != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@ class UserPostsSection extends StatefulWidget {
 | 
			
		||||
  final AdvancedUserInfo user;
 | 
			
		||||
 | 
			
		||||
  const UserPostsSection({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.user,
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.user,
 | 
			
		||||
  })  : assert(user != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +23,7 @@ class UserPostsSection extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _UserPostsSectionState extends State<UserPostsSection> {
 | 
			
		||||
  int get _userID => widget.user.id;
 | 
			
		||||
  int? get _userID => widget.user.id;
 | 
			
		||||
 | 
			
		||||
  final _postsKey = GlobalKey<PostsListWidgetState>();
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +34,7 @@ class _UserPostsSectionState extends State<UserPostsSection> {
 | 
			
		||||
          widget.user.canPostTexts
 | 
			
		||||
              ? PostCreateFormWidget(
 | 
			
		||||
                  postTarget: PostTarget.USER_PAGE,
 | 
			
		||||
                  targetID: _userID,
 | 
			
		||||
                  targetID: _userID!,
 | 
			
		||||
                  onCreated: _postCreated,
 | 
			
		||||
                )
 | 
			
		||||
              : Container()
 | 
			
		||||
@@ -45,6 +45,6 @@ class _UserPostsSectionState extends State<UserPostsSection> {
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void _postCreated() {
 | 
			
		||||
    _postsKey.currentState.loadPostsList(getOlder: false);
 | 
			
		||||
    _postsKey.currentState!.loadPostsList(getOlder: false);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user