mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	Merge branch 'krille/experimental-new-design' into 'main'
feat: Implement experimental new design See merge request famedly/fluffychat!363
This commit is contained in:
		
						commit
						34124f24be
					
				@ -9,6 +9,7 @@ class DefaultAppBarSearchField extends StatefulWidget {
 | 
				
			|||||||
  final String hintText;
 | 
					  final String hintText;
 | 
				
			||||||
  final EdgeInsets padding;
 | 
					  final EdgeInsets padding;
 | 
				
			||||||
  final bool readOnly;
 | 
					  final bool readOnly;
 | 
				
			||||||
 | 
					  final Widget prefixIcon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const DefaultAppBarSearchField({
 | 
					  const DefaultAppBarSearchField({
 | 
				
			||||||
    Key key,
 | 
					    Key key,
 | 
				
			||||||
@ -20,6 +21,7 @@ class DefaultAppBarSearchField extends StatefulWidget {
 | 
				
			|||||||
    this.hintText,
 | 
					    this.hintText,
 | 
				
			||||||
    this.padding,
 | 
					    this.padding,
 | 
				
			||||||
    this.readOnly = false,
 | 
					    this.readOnly = false,
 | 
				
			||||||
 | 
					    this.prefixIcon,
 | 
				
			||||||
  }) : super(key: key);
 | 
					  }) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -73,12 +75,18 @@ class _DefaultAppBarSearchFieldState extends State<DefaultAppBarSearchField> {
 | 
				
			|||||||
        readOnly: widget.readOnly,
 | 
					        readOnly: widget.readOnly,
 | 
				
			||||||
        decoration: InputDecoration(
 | 
					        decoration: InputDecoration(
 | 
				
			||||||
          prefixText: widget.prefixText,
 | 
					          prefixText: widget.prefixText,
 | 
				
			||||||
 | 
					          enabledBorder: OutlineInputBorder(
 | 
				
			||||||
 | 
					            borderRadius: BorderRadius.circular(12),
 | 
				
			||||||
 | 
					            borderSide:
 | 
				
			||||||
 | 
					                BorderSide(color: Theme.of(context).secondaryHeaderColor),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
          contentPadding: EdgeInsets.only(
 | 
					          contentPadding: EdgeInsets.only(
 | 
				
			||||||
            top: 8,
 | 
					            top: 8,
 | 
				
			||||||
            bottom: 8,
 | 
					            bottom: 8,
 | 
				
			||||||
            left: 16,
 | 
					            left: 16,
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          hintText: widget.hintText,
 | 
					          hintText: widget.hintText,
 | 
				
			||||||
 | 
					          prefixIcon: widget.prefixIcon,
 | 
				
			||||||
          suffixIcon: !widget.readOnly &&
 | 
					          suffixIcon: !widget.readOnly &&
 | 
				
			||||||
                  (_focusNode.hasFocus ||
 | 
					                  (_focusNode.hasFocus ||
 | 
				
			||||||
                      (widget.suffix == null &&
 | 
					                      (widget.suffix == null &&
 | 
				
			||||||
 | 
				
			|||||||
@ -1,94 +0,0 @@
 | 
				
			|||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
					 | 
				
			||||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
					 | 
				
			||||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					 | 
				
			||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
					 | 
				
			||||||
import 'matrix.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DefaultDrawer extends StatelessWidget {
 | 
					 | 
				
			||||||
  void _drawerTapAction(BuildContext context, String route) {
 | 
					 | 
				
			||||||
    Navigator.of(context).pop();
 | 
					 | 
				
			||||||
    AdaptivePageLayout.of(context).pushNamedAndRemoveUntilIsFirst(route);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _setStatus(BuildContext context) async {
 | 
					 | 
				
			||||||
    final client = Matrix.of(context).client;
 | 
					 | 
				
			||||||
    final input = await showTextInputDialog(
 | 
					 | 
				
			||||||
      title: L10n.of(context).setStatus,
 | 
					 | 
				
			||||||
      context: context,
 | 
					 | 
				
			||||||
      textFields: [
 | 
					 | 
				
			||||||
        DialogTextField(
 | 
					 | 
				
			||||||
          hintText: L10n.of(context).statusExampleMessage,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    if (input == null || input.single.isEmpty) return;
 | 
					 | 
				
			||||||
    await showFutureLoadingDialog(
 | 
					 | 
				
			||||||
      context: context,
 | 
					 | 
				
			||||||
      future: () => client.sendPresence(
 | 
					 | 
				
			||||||
        client.userID,
 | 
					 | 
				
			||||||
        PresenceType.online,
 | 
					 | 
				
			||||||
        statusMsg: input.single,
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    Navigator.of(context).pop();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					 | 
				
			||||||
    return Drawer(
 | 
					 | 
				
			||||||
      child: SafeArea(
 | 
					 | 
				
			||||||
        child: ListView(
 | 
					 | 
				
			||||||
          padding: EdgeInsets.zero,
 | 
					 | 
				
			||||||
          children: <Widget>[
 | 
					 | 
				
			||||||
            ListTile(
 | 
					 | 
				
			||||||
              leading: Icon(Icons.edit_outlined),
 | 
					 | 
				
			||||||
              title: Text(L10n.of(context).setStatus),
 | 
					 | 
				
			||||||
              onTap: () => _setStatus(context),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Divider(height: 1),
 | 
					 | 
				
			||||||
            ListTile(
 | 
					 | 
				
			||||||
              leading: Icon(Icons.people_outline),
 | 
					 | 
				
			||||||
              title: Text(L10n.of(context).createNewGroup),
 | 
					 | 
				
			||||||
              onTap: () => _drawerTapAction(context, '/newgroup'),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            ListTile(
 | 
					 | 
				
			||||||
              leading: Icon(Icons.person_add_outlined),
 | 
					 | 
				
			||||||
              title: Text(L10n.of(context).newPrivateChat),
 | 
					 | 
				
			||||||
              onTap: () => _drawerTapAction(context, '/newprivatechat'),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Divider(height: 1),
 | 
					 | 
				
			||||||
            ListTile(
 | 
					 | 
				
			||||||
              leading: Icon(Icons.archive_outlined),
 | 
					 | 
				
			||||||
              title: Text(L10n.of(context).archive),
 | 
					 | 
				
			||||||
              onTap: () => _drawerTapAction(
 | 
					 | 
				
			||||||
                context,
 | 
					 | 
				
			||||||
                '/archive',
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            ListTile(
 | 
					 | 
				
			||||||
              leading: Icon(Icons.group_work_outlined),
 | 
					 | 
				
			||||||
              title: Text(L10n.of(context).discoverGroups),
 | 
					 | 
				
			||||||
              onTap: () => _drawerTapAction(
 | 
					 | 
				
			||||||
                context,
 | 
					 | 
				
			||||||
                '/discover',
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Divider(height: 1),
 | 
					 | 
				
			||||||
            ListTile(
 | 
					 | 
				
			||||||
              leading: Icon(Icons.settings_outlined),
 | 
					 | 
				
			||||||
              title: Text(L10n.of(context).settings),
 | 
					 | 
				
			||||||
              onTap: () => _drawerTapAction(
 | 
					 | 
				
			||||||
                context,
 | 
					 | 
				
			||||||
                '/settings',
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          ],
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										134
									
								
								lib/components/list_items/status_list_tile.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								lib/components/list_items/status_list_tile.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,134 @@
 | 
				
			|||||||
 | 
					import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
				
			||||||
 | 
					import 'package:cached_network_image/cached_network_image.dart';
 | 
				
			||||||
 | 
					import 'package:famedlysdk/famedlysdk.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/avatar.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/utils/fluffy_share.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/utils/status.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/cupertino.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import '../../utils/string_color.dart';
 | 
				
			||||||
 | 
					import '../../utils/date_time_extension.dart';
 | 
				
			||||||
 | 
					import '../matrix.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StatusListTile extends StatelessWidget {
 | 
				
			||||||
 | 
					  final Status status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const StatusListTile({Key key, @required this.status}) : super(key: key);
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final text = status.message;
 | 
				
			||||||
 | 
					    final isImage = text.startsWith('mxc://') && text.split(' ').length == 1;
 | 
				
			||||||
 | 
					    return FutureBuilder<Profile>(
 | 
				
			||||||
 | 
					        future: Matrix.of(context).client.getProfileFromUserId(status.senderId),
 | 
				
			||||||
 | 
					        builder: (context, snapshot) {
 | 
				
			||||||
 | 
					          final displayname =
 | 
				
			||||||
 | 
					              snapshot.data?.displayname ?? status.senderId.localpart;
 | 
				
			||||||
 | 
					          final avatarUrl = snapshot.data?.avatarUrl;
 | 
				
			||||||
 | 
					          return Column(
 | 
				
			||||||
 | 
					            mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					              ListTile(
 | 
				
			||||||
 | 
					                leading: Avatar(avatarUrl, displayname),
 | 
				
			||||||
 | 
					                title: Row(
 | 
				
			||||||
 | 
					                  crossAxisAlignment: CrossAxisAlignment.baseline,
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    Text(displayname,
 | 
				
			||||||
 | 
					                        style: TextStyle(fontWeight: FontWeight.bold)),
 | 
				
			||||||
 | 
					                    SizedBox(width: 4),
 | 
				
			||||||
 | 
					                    Text(status.dateTime.localizedTime(context),
 | 
				
			||||||
 | 
					                        style: TextStyle(fontSize: 14)),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                subtitle: Text(status.senderId),
 | 
				
			||||||
 | 
					                trailing: PopupMenuButton(
 | 
				
			||||||
 | 
					                  onSelected: (_) => AdaptivePageLayout.of(context).pushNamed(
 | 
				
			||||||
 | 
					                      '/settings/ignore',
 | 
				
			||||||
 | 
					                      arguments: status.senderId),
 | 
				
			||||||
 | 
					                  itemBuilder: (_) => [
 | 
				
			||||||
 | 
					                    PopupMenuItem(
 | 
				
			||||||
 | 
					                      child: Text(L10n.of(context).ignore),
 | 
				
			||||||
 | 
					                      value: 'ignore',
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              isImage
 | 
				
			||||||
 | 
					                  ? CachedNetworkImage(
 | 
				
			||||||
 | 
					                      imageUrl: Uri.parse(text).getThumbnail(
 | 
				
			||||||
 | 
					                        Matrix.of(context).client,
 | 
				
			||||||
 | 
					                        width: 360,
 | 
				
			||||||
 | 
					                        height: 360,
 | 
				
			||||||
 | 
					                        method: ThumbnailMethod.scale,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      fit: BoxFit.cover,
 | 
				
			||||||
 | 
					                      width: double.infinity,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                  : Container(
 | 
				
			||||||
 | 
					                      height: 256,
 | 
				
			||||||
 | 
					                      color: text.color,
 | 
				
			||||||
 | 
					                      alignment: Alignment.center,
 | 
				
			||||||
 | 
					                      child: SingleChildScrollView(
 | 
				
			||||||
 | 
					                        padding: EdgeInsets.all(12),
 | 
				
			||||||
 | 
					                        child: Text(
 | 
				
			||||||
 | 
					                          text,
 | 
				
			||||||
 | 
					                          textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					                          style: TextStyle(
 | 
				
			||||||
 | 
					                            color: Colors.white,
 | 
				
			||||||
 | 
					                            fontSize: 24,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					              Padding(
 | 
				
			||||||
 | 
					                padding: const EdgeInsets.only(right: 12.0, left: 12.0),
 | 
				
			||||||
 | 
					                child: Row(
 | 
				
			||||||
 | 
					                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    IconButton(
 | 
				
			||||||
 | 
					                      icon: Icon(CupertinoIcons.chat_bubble),
 | 
				
			||||||
 | 
					                      onPressed: () async {
 | 
				
			||||||
 | 
					                        final result = await showFutureLoadingDialog(
 | 
				
			||||||
 | 
					                          context: context,
 | 
				
			||||||
 | 
					                          future: () => User(
 | 
				
			||||||
 | 
					                            status.senderId,
 | 
				
			||||||
 | 
					                            room:
 | 
				
			||||||
 | 
					                                Room(id: '', client: Matrix.of(context).client),
 | 
				
			||||||
 | 
					                          ).startDirectChat(),
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        if (result.error == null) {
 | 
				
			||||||
 | 
					                          await AdaptivePageLayout.of(context)
 | 
				
			||||||
 | 
					                              .pushNamed('/rooms/${result.result}');
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    IconButton(
 | 
				
			||||||
 | 
					                      icon: Icon(Icons.ios_share),
 | 
				
			||||||
 | 
					                      onPressed: () => AdaptivePageLayout.of(context)
 | 
				
			||||||
 | 
					                          .pushNamed('/newstatus', arguments: status.message),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    IconButton(
 | 
				
			||||||
 | 
					                      icon: Icon(Icons.share_outlined),
 | 
				
			||||||
 | 
					                      onPressed: () => FluffyShare.share(
 | 
				
			||||||
 | 
					                        '$displayname: ${status.message}',
 | 
				
			||||||
 | 
					                        context,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    IconButton(
 | 
				
			||||||
 | 
					                      icon: Icon(Icons.delete_outlined),
 | 
				
			||||||
 | 
					                      onPressed: () => showFutureLoadingDialog(
 | 
				
			||||||
 | 
					                        context: context,
 | 
				
			||||||
 | 
					                        future: () => Matrix.of(context)
 | 
				
			||||||
 | 
					                            .removeStatusOfUser(status.senderId),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -10,6 +10,7 @@ import 'package:fluffychat/utils/firebase_controller.dart';
 | 
				
			|||||||
import 'package:fluffychat/utils/matrix_locals.dart';
 | 
					import 'package:fluffychat/utils/matrix_locals.dart';
 | 
				
			||||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
					import 'package:fluffychat/utils/platform_infos.dart';
 | 
				
			||||||
import 'package:fluffychat/utils/sentry_controller.dart';
 | 
					import 'package:fluffychat/utils/sentry_controller.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/utils/status.dart';
 | 
				
			||||||
import 'package:flushbar/flushbar.dart';
 | 
					import 'package:flushbar/flushbar.dart';
 | 
				
			||||||
import 'package:flutter/foundation.dart';
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
@ -126,6 +127,7 @@ class MatrixState extends State<Matrix> {
 | 
				
			|||||||
  StreamSubscription onKeyVerificationRequestSub;
 | 
					  StreamSubscription onKeyVerificationRequestSub;
 | 
				
			||||||
  StreamSubscription onJitsiCallSub;
 | 
					  StreamSubscription onJitsiCallSub;
 | 
				
			||||||
  StreamSubscription onNotification;
 | 
					  StreamSubscription onNotification;
 | 
				
			||||||
 | 
					  StreamSubscription<Presence> onPresence;
 | 
				
			||||||
  StreamSubscription<LoginState> onLoginStateChanged;
 | 
					  StreamSubscription<LoginState> onLoginStateChanged;
 | 
				
			||||||
  StreamSubscription<UiaRequest> onUiaRequest;
 | 
					  StreamSubscription<UiaRequest> onUiaRequest;
 | 
				
			||||||
  StreamSubscription<html.Event> onFocusSub;
 | 
					  StreamSubscription<html.Event> onFocusSub;
 | 
				
			||||||
@ -288,6 +290,10 @@ class MatrixState extends State<Matrix> {
 | 
				
			|||||||
    LoadingDialog.defaultBackLabel = L10n.of(context).close;
 | 
					    LoadingDialog.defaultBackLabel = L10n.of(context).close;
 | 
				
			||||||
    LoadingDialog.defaultOnError = (Object e) => e.toLocalizedString(context);
 | 
					    LoadingDialog.defaultOnError = (Object e) => e.toLocalizedString(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onPresence ??= client.onPresence.stream
 | 
				
			||||||
 | 
					        .where((p) => p.presence?.statusMsg != null)
 | 
				
			||||||
 | 
					        .listen(_onPresence);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onRoomKeyRequestSub ??=
 | 
					    onRoomKeyRequestSub ??=
 | 
				
			||||||
        client.onRoomKeyRequest.stream.listen((RoomKeyRequest request) async {
 | 
					        client.onRoomKeyRequest.stream.listen((RoomKeyRequest request) async {
 | 
				
			||||||
      final room = request.room;
 | 
					      final room = request.room;
 | 
				
			||||||
@ -395,6 +401,45 @@ class MatrixState extends State<Matrix> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Map<String, Status> get statuses {
 | 
				
			||||||
 | 
					    if (client.accountData.containsKey(Status.namespace)) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        return client.accountData[Status.namespace].content
 | 
				
			||||||
 | 
					            .map((k, v) => MapEntry(k, Status.fromJson(v)));
 | 
				
			||||||
 | 
					      } catch (e, s) {
 | 
				
			||||||
 | 
					        Logs()
 | 
				
			||||||
 | 
					            .e('Unable to parse status account data. Clearing up now...', e, s);
 | 
				
			||||||
 | 
					        client.setAccountData(client.userID, Status.namespace, {});
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onPresence(Presence presence) async {
 | 
				
			||||||
 | 
					    if (statuses[presence.senderId]?.message != presence.presence.statusMsg) {
 | 
				
			||||||
 | 
					      Logs().v('Update status from ${presence.senderId}');
 | 
				
			||||||
 | 
					      await client.setAccountData(
 | 
				
			||||||
 | 
					        client.userID,
 | 
				
			||||||
 | 
					        Status.namespace,
 | 
				
			||||||
 | 
					        statuses.map((k, v) => MapEntry(k, v.toJson()))
 | 
				
			||||||
 | 
					          ..[presence.senderId] = Status(
 | 
				
			||||||
 | 
					            presence.senderId,
 | 
				
			||||||
 | 
					            presence.presence.statusMsg,
 | 
				
			||||||
 | 
					            DateTime.now(),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> removeStatusOfUser(String userId) async {
 | 
				
			||||||
 | 
					    await client.setAccountData(
 | 
				
			||||||
 | 
					      client.userID,
 | 
				
			||||||
 | 
					      Status.namespace,
 | 
				
			||||||
 | 
					      statuses.map((k, v) => MapEntry(k, v.toJson()))..remove(userId),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void dispose() {
 | 
					  void dispose() {
 | 
				
			||||||
    onRoomKeyRequestSub?.cancel();
 | 
					    onRoomKeyRequestSub?.cancel();
 | 
				
			||||||
@ -403,6 +448,7 @@ class MatrixState extends State<Matrix> {
 | 
				
			|||||||
    onNotification?.cancel();
 | 
					    onNotification?.cancel();
 | 
				
			||||||
    onFocusSub?.cancel();
 | 
					    onFocusSub?.cancel();
 | 
				
			||||||
    onBlurSub?.cancel();
 | 
					    onBlurSub?.cancel();
 | 
				
			||||||
 | 
					    onPresence?.cancel();
 | 
				
			||||||
    super.dispose();
 | 
					    super.dispose();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,9 +5,8 @@ import 'package:fluffychat/views/archive.dart';
 | 
				
			|||||||
import 'package:fluffychat/views/chat.dart';
 | 
					import 'package:fluffychat/views/chat.dart';
 | 
				
			||||||
import 'package:fluffychat/views/chat_details.dart';
 | 
					import 'package:fluffychat/views/chat_details.dart';
 | 
				
			||||||
import 'package:fluffychat/views/chat_encryption_settings.dart';
 | 
					import 'package:fluffychat/views/chat_encryption_settings.dart';
 | 
				
			||||||
import 'package:fluffychat/views/chat_list.dart';
 | 
					import 'package:fluffychat/views/home_view.dart';
 | 
				
			||||||
import 'package:fluffychat/views/chat_permissions_settings.dart';
 | 
					import 'package:fluffychat/views/chat_permissions_settings.dart';
 | 
				
			||||||
import 'package:fluffychat/views/discover_view.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/views/empty_page.dart';
 | 
					import 'package:fluffychat/views/empty_page.dart';
 | 
				
			||||||
import 'package:fluffychat/views/homeserver_picker.dart';
 | 
					import 'package:fluffychat/views/homeserver_picker.dart';
 | 
				
			||||||
import 'package:fluffychat/views/invitation_selection.dart';
 | 
					import 'package:fluffychat/views/invitation_selection.dart';
 | 
				
			||||||
@ -16,6 +15,7 @@ import 'package:fluffychat/views/log_view.dart';
 | 
				
			|||||||
import 'package:fluffychat/views/login.dart';
 | 
					import 'package:fluffychat/views/login.dart';
 | 
				
			||||||
import 'package:fluffychat/views/new_group.dart';
 | 
					import 'package:fluffychat/views/new_group.dart';
 | 
				
			||||||
import 'package:fluffychat/views/new_private_chat.dart';
 | 
					import 'package:fluffychat/views/new_private_chat.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/views/set_status_view.dart';
 | 
				
			||||||
import 'package:fluffychat/views/settings.dart';
 | 
					import 'package:fluffychat/views/settings.dart';
 | 
				
			||||||
import 'package:fluffychat/views/settings_3pid.dart';
 | 
					import 'package:fluffychat/views/settings_3pid.dart';
 | 
				
			||||||
import 'package:fluffychat/views/settings_devices.dart';
 | 
					import 'package:fluffychat/views/settings_devices.dart';
 | 
				
			||||||
@ -64,14 +64,14 @@ class FluffyRoutes {
 | 
				
			|||||||
      switch (parts[1]) {
 | 
					      switch (parts[1]) {
 | 
				
			||||||
        case '':
 | 
					        case '':
 | 
				
			||||||
          return ViewData(
 | 
					          return ViewData(
 | 
				
			||||||
            mainView: (_) => ChatList(),
 | 
					            mainView: (_) => HomeView(),
 | 
				
			||||||
            emptyView: (_) => EmptyPage(),
 | 
					            emptyView: (_) => EmptyPage(),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        case 'rooms':
 | 
					        case 'rooms':
 | 
				
			||||||
          final roomId = parts[2];
 | 
					          final roomId = parts[2];
 | 
				
			||||||
          if (parts.length == 3) {
 | 
					          if (parts.length == 3) {
 | 
				
			||||||
            return ViewData(
 | 
					            return ViewData(
 | 
				
			||||||
              leftView: (_) => ChatList(activeChat: roomId),
 | 
					              leftView: (_) => HomeView(),
 | 
				
			||||||
              mainView: (_) => Chat(roomId),
 | 
					              mainView: (_) => Chat(roomId),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          } else if (parts.length == 4) {
 | 
					          } else if (parts.length == 4) {
 | 
				
			||||||
@ -79,44 +79,44 @@ class FluffyRoutes {
 | 
				
			|||||||
            switch (action) {
 | 
					            switch (action) {
 | 
				
			||||||
              case 'details':
 | 
					              case 'details':
 | 
				
			||||||
                return ViewData(
 | 
					                return ViewData(
 | 
				
			||||||
                  leftView: (_) => ChatList(activeChat: roomId),
 | 
					                  leftView: (_) => HomeView(),
 | 
				
			||||||
                  mainView: (_) => Chat(roomId),
 | 
					                  mainView: (_) => Chat(roomId),
 | 
				
			||||||
                  rightView: (_) => ChatDetails(roomId),
 | 
					                  rightView: (_) => ChatDetails(roomId),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              case 'encryption':
 | 
					              case 'encryption':
 | 
				
			||||||
                return ViewData(
 | 
					                return ViewData(
 | 
				
			||||||
                  leftView: (_) => ChatList(activeChat: roomId),
 | 
					                  leftView: (_) => HomeView(),
 | 
				
			||||||
                  mainView: (_) => Chat(roomId),
 | 
					                  mainView: (_) => Chat(roomId),
 | 
				
			||||||
                  rightView: (_) => ChatEncryptionSettings(roomId),
 | 
					                  rightView: (_) => ChatEncryptionSettings(roomId),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              case 'permissions':
 | 
					              case 'permissions':
 | 
				
			||||||
                return ViewData(
 | 
					                return ViewData(
 | 
				
			||||||
                  leftView: (_) => ChatList(activeChat: roomId),
 | 
					                  leftView: (_) => HomeView(),
 | 
				
			||||||
                  mainView: (_) => Chat(roomId),
 | 
					                  mainView: (_) => Chat(roomId),
 | 
				
			||||||
                  rightView: (_) => ChatPermissionsSettings(roomId),
 | 
					                  rightView: (_) => ChatPermissionsSettings(roomId),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              case 'invite':
 | 
					              case 'invite':
 | 
				
			||||||
                return ViewData(
 | 
					                return ViewData(
 | 
				
			||||||
                  leftView: (_) => ChatList(activeChat: roomId),
 | 
					                  leftView: (_) => HomeView(),
 | 
				
			||||||
                  mainView: (_) => Chat(roomId),
 | 
					                  mainView: (_) => Chat(roomId),
 | 
				
			||||||
                  rightView: (_) => InvitationSelection(roomId),
 | 
					                  rightView: (_) => InvitationSelection(roomId),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              case 'emotes':
 | 
					              case 'emotes':
 | 
				
			||||||
                return ViewData(
 | 
					                return ViewData(
 | 
				
			||||||
                  leftView: (_) => ChatList(activeChat: roomId),
 | 
					                  leftView: (_) => HomeView(),
 | 
				
			||||||
                  mainView: (_) => Chat(roomId),
 | 
					                  mainView: (_) => Chat(roomId),
 | 
				
			||||||
                  rightView: (_) => MultipleEmotesSettings(roomId),
 | 
					                  rightView: (_) => MultipleEmotesSettings(roomId),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              default:
 | 
					              default:
 | 
				
			||||||
                return ViewData(
 | 
					                return ViewData(
 | 
				
			||||||
                  leftView: (_) => ChatList(activeChat: roomId),
 | 
					                  leftView: (_) => HomeView(),
 | 
				
			||||||
                  mainView: (_) => Chat(roomId,
 | 
					                  mainView: (_) => Chat(roomId,
 | 
				
			||||||
                      scrollToEventId: action.sigil == '\$' ? action : null),
 | 
					                      scrollToEventId: action.sigil == '\$' ? action : null),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return ViewData(
 | 
					          return ViewData(
 | 
				
			||||||
            mainView: (_) => ChatList(),
 | 
					            mainView: (_) => HomeView(),
 | 
				
			||||||
            emptyView: (_) => EmptyPage(),
 | 
					            emptyView: (_) => EmptyPage(),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        case 'archive':
 | 
					        case 'archive':
 | 
				
			||||||
@ -124,26 +124,25 @@ class FluffyRoutes {
 | 
				
			|||||||
            mainView: (_) => Archive(),
 | 
					            mainView: (_) => Archive(),
 | 
				
			||||||
            emptyView: (_) => EmptyPage(),
 | 
					            emptyView: (_) => EmptyPage(),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        case 'discover':
 | 
					 | 
				
			||||||
          return ViewData(
 | 
					 | 
				
			||||||
            mainView: (_) =>
 | 
					 | 
				
			||||||
                DiscoverPage(alias: parts.length == 3 ? parts[2] : null),
 | 
					 | 
				
			||||||
            emptyView: (_) => EmptyPage(),
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        case 'logs':
 | 
					        case 'logs':
 | 
				
			||||||
          return ViewData(
 | 
					          return ViewData(
 | 
				
			||||||
            mainView: (_) => LogViewer(),
 | 
					            mainView: (_) => LogViewer(),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        case 'newgroup':
 | 
					        case 'newgroup':
 | 
				
			||||||
          return ViewData(
 | 
					          return ViewData(
 | 
				
			||||||
            leftView: (_) => ChatList(),
 | 
					            leftView: (_) => HomeView(),
 | 
				
			||||||
            mainView: (_) => NewGroup(),
 | 
					            mainView: (_) => NewGroup(),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        case 'newprivatechat':
 | 
					        case 'newprivatechat':
 | 
				
			||||||
          return ViewData(
 | 
					          return ViewData(
 | 
				
			||||||
            leftView: (_) => ChatList(),
 | 
					            leftView: (_) => HomeView(),
 | 
				
			||||||
            mainView: (_) => NewPrivateChat(),
 | 
					            mainView: (_) => NewPrivateChat(),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
 | 
					        case 'newstatus':
 | 
				
			||||||
 | 
					          return ViewData(
 | 
				
			||||||
 | 
					            leftView: (_) => HomeView(),
 | 
				
			||||||
 | 
					            mainView: (_) => SetStatusView(initialText: settings.arguments),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
        case 'settings':
 | 
					        case 'settings':
 | 
				
			||||||
          if (parts.length == 3) {
 | 
					          if (parts.length == 3) {
 | 
				
			||||||
            final action = parts[2];
 | 
					            final action = parts[2];
 | 
				
			||||||
@ -169,7 +168,9 @@ class FluffyRoutes {
 | 
				
			|||||||
              case 'ignore':
 | 
					              case 'ignore':
 | 
				
			||||||
                return ViewData(
 | 
					                return ViewData(
 | 
				
			||||||
                  leftView: (_) => Settings(),
 | 
					                  leftView: (_) => Settings(),
 | 
				
			||||||
                  mainView: (_) => SettingsIgnoreList(),
 | 
					                  mainView: (_) => SettingsIgnoreList(
 | 
				
			||||||
 | 
					                    initialUserId: settings.arguments,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
              case 'notifications':
 | 
					              case 'notifications':
 | 
				
			||||||
                return ViewData(
 | 
					                return ViewData(
 | 
				
			||||||
 | 
				
			|||||||
@ -1332,8 +1332,13 @@
 | 
				
			|||||||
      "username": {}
 | 
					      "username": {}
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "searchForAChat": "Search for a chat",
 | 
					  "ignore": "Ignore",
 | 
				
			||||||
  "@searchForAChat": {
 | 
					  "@ignore": {
 | 
				
			||||||
 | 
					    "type": "text",
 | 
				
			||||||
 | 
					    "placeholders": {}
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "search": "Search",
 | 
				
			||||||
 | 
					  "@search": {
 | 
				
			||||||
    "type": "text",
 | 
					    "type": "text",
 | 
				
			||||||
    "placeholders": {}
 | 
					    "placeholders": {}
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@ -1385,8 +1390,23 @@
 | 
				
			|||||||
      "count": {}
 | 
					      "count": {}
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "discoverGroups": "Discover groups",
 | 
					  "status": "Status",
 | 
				
			||||||
  "@discoverGroups": {
 | 
					  "@status": {
 | 
				
			||||||
 | 
					    "type": "text",
 | 
				
			||||||
 | 
					    "placeholders": {}
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "messages": "Messages",
 | 
				
			||||||
 | 
					  "@messages": {
 | 
				
			||||||
 | 
					    "type": "text",
 | 
				
			||||||
 | 
					    "placeholders": {}
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "groups": "Groups",
 | 
				
			||||||
 | 
					  "@groups": {
 | 
				
			||||||
 | 
					    "type": "text",
 | 
				
			||||||
 | 
					    "placeholders": {}
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "discover": "Discover",
 | 
				
			||||||
 | 
					  "@discover": {
 | 
				
			||||||
    "type": "text",
 | 
					    "type": "text",
 | 
				
			||||||
    "placeholders": {}
 | 
					    "placeholders": {}
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								lib/utils/status.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								lib/utils/status.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					class Status {
 | 
				
			||||||
 | 
					  static const String namespace = 'im.fluffychat.statuses';
 | 
				
			||||||
 | 
					  final String senderId;
 | 
				
			||||||
 | 
					  final String message;
 | 
				
			||||||
 | 
					  final DateTime dateTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Status(this.senderId, this.message, this.dateTime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Status.fromJson(Map<String, dynamic> json)
 | 
				
			||||||
 | 
					      : senderId = json['sender_id'],
 | 
				
			||||||
 | 
					        message = json['message'],
 | 
				
			||||||
 | 
					        dateTime = DateTime.fromMillisecondsSinceEpoch(json['date_time']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Map<String, dynamic> toJson() => <String, dynamic>{
 | 
				
			||||||
 | 
					        'sender_id': senderId,
 | 
				
			||||||
 | 
					        'message': message,
 | 
				
			||||||
 | 
					        'date_time': dateTime.millisecondsSinceEpoch,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,341 +0,0 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					 | 
				
			||||||
import 'dart:io';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
					 | 
				
			||||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
					 | 
				
			||||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/components/connection_status_header.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/components/default_app_bar_search_field.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/components/default_drawer.dart';
 | 
					 | 
				
			||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/app_config.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/foundation.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					 | 
				
			||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
					 | 
				
			||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import '../components/list_items/chat_list_item.dart';
 | 
					 | 
				
			||||||
import '../components/matrix.dart';
 | 
					 | 
				
			||||||
import '../utils/matrix_file_extension.dart';
 | 
					 | 
				
			||||||
import '../utils/url_launcher.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum SelectMode { normal, share, select }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ChatList extends StatefulWidget {
 | 
					 | 
				
			||||||
  final String activeChat;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const ChatList({this.activeChat, Key key}) : super(key: key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  _ChatListState createState() => _ChatListState();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class _ChatListState extends State<ChatList> {
 | 
					 | 
				
			||||||
  bool get searchMode => searchController.text?.isNotEmpty ?? false;
 | 
					 | 
				
			||||||
  final TextEditingController searchController = TextEditingController();
 | 
					 | 
				
			||||||
  final _selectedRoomIds = <String>{};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  final ScrollController _scrollController = ScrollController();
 | 
					 | 
				
			||||||
  bool _scrolledToTop = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _toggleSelection(String roomId) =>
 | 
					 | 
				
			||||||
      setState(() => _selectedRoomIds.contains(roomId)
 | 
					 | 
				
			||||||
          ? _selectedRoomIds.remove(roomId)
 | 
					 | 
				
			||||||
          : _selectedRoomIds.add(roomId));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> waitForFirstSync(BuildContext context) async {
 | 
					 | 
				
			||||||
    var client = Matrix.of(context).client;
 | 
					 | 
				
			||||||
    if (client.prevBatch?.isEmpty ?? true) {
 | 
					 | 
				
			||||||
      await client.onFirstSync.stream.first;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  void initState() {
 | 
					 | 
				
			||||||
    _scrollController.addListener(() async {
 | 
					 | 
				
			||||||
      if (_scrollController.position.pixels > 0 && _scrolledToTop) {
 | 
					 | 
				
			||||||
        setState(() => _scrolledToTop = false);
 | 
					 | 
				
			||||||
      } else if (_scrollController.position.pixels == 0 && !_scrolledToTop) {
 | 
					 | 
				
			||||||
        setState(() => _scrolledToTop = true);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    _initReceiveSharingIntent();
 | 
					 | 
				
			||||||
    super.initState();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  StreamSubscription _intentDataStreamSubscription;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  StreamSubscription _intentFileStreamSubscription;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _processIncomingSharedFiles(List<SharedMediaFile> files) {
 | 
					 | 
				
			||||||
    if (files?.isEmpty ?? true) return;
 | 
					 | 
				
			||||||
    AdaptivePageLayout.of(context).popUntilIsFirst();
 | 
					 | 
				
			||||||
    final file = File(files.first.path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Matrix.of(context).shareContent = {
 | 
					 | 
				
			||||||
      'msgtype': 'chat.fluffy.shared_file',
 | 
					 | 
				
			||||||
      'file': MatrixFile(
 | 
					 | 
				
			||||||
        bytes: file.readAsBytesSync(),
 | 
					 | 
				
			||||||
        name: file.path,
 | 
					 | 
				
			||||||
      ).detectFileType,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _processIncomingSharedText(String text) {
 | 
					 | 
				
			||||||
    if (text == null) return;
 | 
					 | 
				
			||||||
    AdaptivePageLayout.of(context).popUntilIsFirst();
 | 
					 | 
				
			||||||
    if (text.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
 | 
					 | 
				
			||||||
        (text.toLowerCase().startsWith(AppConfig.schemePrefix) &&
 | 
					 | 
				
			||||||
            !RegExp(r'\s').hasMatch(text))) {
 | 
					 | 
				
			||||||
      UrlLauncher(context, text).openMatrixToUrl();
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Matrix.of(context).shareContent = {
 | 
					 | 
				
			||||||
      'msgtype': 'm.text',
 | 
					 | 
				
			||||||
      'body': text,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _initReceiveSharingIntent() {
 | 
					 | 
				
			||||||
    if (!PlatformInfos.isMobile) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // For sharing images coming from outside the app while the app is in the memory
 | 
					 | 
				
			||||||
    _intentFileStreamSubscription = ReceiveSharingIntent.getMediaStream()
 | 
					 | 
				
			||||||
        .listen(_processIncomingSharedFiles, onError: print);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // For sharing images coming from outside the app while the app is closed
 | 
					 | 
				
			||||||
    ReceiveSharingIntent.getInitialMedia().then(_processIncomingSharedFiles);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // For sharing or opening urls/text coming from outside the app while the app is in the memory
 | 
					 | 
				
			||||||
    _intentDataStreamSubscription = ReceiveSharingIntent.getTextStream()
 | 
					 | 
				
			||||||
        .listen(_processIncomingSharedText, onError: print);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // For sharing or opening urls/text coming from outside the app while the app is closed
 | 
					 | 
				
			||||||
    ReceiveSharingIntent.getInitialText().then(_processIncomingSharedText);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  void dispose() {
 | 
					 | 
				
			||||||
    _intentDataStreamSubscription?.cancel();
 | 
					 | 
				
			||||||
    _intentFileStreamSubscription?.cancel();
 | 
					 | 
				
			||||||
    super.dispose();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> _toggleUnread(BuildContext context) {
 | 
					 | 
				
			||||||
    final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
 | 
					 | 
				
			||||||
    return showFutureLoadingDialog(
 | 
					 | 
				
			||||||
      context: context,
 | 
					 | 
				
			||||||
      future: () => room.setUnread(!room.isUnread),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> _toggleFavouriteRoom(BuildContext context) {
 | 
					 | 
				
			||||||
    final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
 | 
					 | 
				
			||||||
    return showFutureLoadingDialog(
 | 
					 | 
				
			||||||
      context: context,
 | 
					 | 
				
			||||||
      future: () => room.setFavourite(!room.isFavourite),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> _toggleMuted(BuildContext context) {
 | 
					 | 
				
			||||||
    final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
 | 
					 | 
				
			||||||
    return showFutureLoadingDialog(
 | 
					 | 
				
			||||||
      context: context,
 | 
					 | 
				
			||||||
      future: () => room.setPushRuleState(
 | 
					 | 
				
			||||||
          room.pushRuleState == PushRuleState.notify
 | 
					 | 
				
			||||||
              ? PushRuleState.mentions_only
 | 
					 | 
				
			||||||
              : PushRuleState.notify),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> _archiveAction(BuildContext context) async {
 | 
					 | 
				
			||||||
    final confirmed = await showOkCancelAlertDialog(
 | 
					 | 
				
			||||||
          context: context,
 | 
					 | 
				
			||||||
          title: L10n.of(context).areYouSure,
 | 
					 | 
				
			||||||
        ) ==
 | 
					 | 
				
			||||||
        OkCancelResult.ok;
 | 
					 | 
				
			||||||
    if (!confirmed) return;
 | 
					 | 
				
			||||||
    await showFutureLoadingDialog(
 | 
					 | 
				
			||||||
      context: context,
 | 
					 | 
				
			||||||
      future: () => _archiveSelectedRooms(context),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    setState(() => null);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> _archiveSelectedRooms(BuildContext context) async {
 | 
					 | 
				
			||||||
    final client = Matrix.of(context).client;
 | 
					 | 
				
			||||||
    while (_selectedRoomIds.isNotEmpty) {
 | 
					 | 
				
			||||||
      final roomId = _selectedRoomIds.first;
 | 
					 | 
				
			||||||
      await client.getRoomById(roomId).leave();
 | 
					 | 
				
			||||||
      _selectedRoomIds.remove(roomId);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					 | 
				
			||||||
    return StreamBuilder(
 | 
					 | 
				
			||||||
        stream: Matrix.of(context).onShareContentChanged.stream,
 | 
					 | 
				
			||||||
        builder: (context, snapshot) {
 | 
					 | 
				
			||||||
          final selectMode = Matrix.of(context).shareContent == null
 | 
					 | 
				
			||||||
              ? _selectedRoomIds.isEmpty
 | 
					 | 
				
			||||||
                  ? SelectMode.normal
 | 
					 | 
				
			||||||
                  : SelectMode.select
 | 
					 | 
				
			||||||
              : SelectMode.share;
 | 
					 | 
				
			||||||
          if (selectMode == SelectMode.share) {
 | 
					 | 
				
			||||||
            _selectedRoomIds.clear();
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          Room selectedRoom;
 | 
					 | 
				
			||||||
          if (_selectedRoomIds.length == 1) {
 | 
					 | 
				
			||||||
            selectedRoom =
 | 
					 | 
				
			||||||
                Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return Scaffold(
 | 
					 | 
				
			||||||
            drawer: selectMode != SelectMode.normal ? null : DefaultDrawer(),
 | 
					 | 
				
			||||||
            appBar: AppBar(
 | 
					 | 
				
			||||||
              centerTitle: false,
 | 
					 | 
				
			||||||
              elevation: _scrolledToTop ? 0 : null,
 | 
					 | 
				
			||||||
              leading: selectMode == SelectMode.share
 | 
					 | 
				
			||||||
                  ? IconButton(
 | 
					 | 
				
			||||||
                      icon: Icon(Icons.close),
 | 
					 | 
				
			||||||
                      onPressed: () => Matrix.of(context).shareContent = null,
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                  : selectMode == SelectMode.select
 | 
					 | 
				
			||||||
                      ? IconButton(
 | 
					 | 
				
			||||||
                          icon: Icon(Icons.close),
 | 
					 | 
				
			||||||
                          onPressed: () => setState(_selectedRoomIds.clear),
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                      : null,
 | 
					 | 
				
			||||||
              titleSpacing: 0,
 | 
					 | 
				
			||||||
              actions: selectMode != SelectMode.select
 | 
					 | 
				
			||||||
                  ? null
 | 
					 | 
				
			||||||
                  : [
 | 
					 | 
				
			||||||
                      if (_selectedRoomIds.length == 1)
 | 
					 | 
				
			||||||
                        IconButton(
 | 
					 | 
				
			||||||
                          tooltip: L10n.of(context).toggleUnread,
 | 
					 | 
				
			||||||
                          icon: Icon(selectedRoom.isUnread
 | 
					 | 
				
			||||||
                              ? Icons.mark_chat_read_outlined
 | 
					 | 
				
			||||||
                              : Icons.mark_chat_unread_outlined),
 | 
					 | 
				
			||||||
                          onPressed: () => _toggleUnread(context),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      if (_selectedRoomIds.length == 1)
 | 
					 | 
				
			||||||
                        IconButton(
 | 
					 | 
				
			||||||
                          tooltip: L10n.of(context).toggleFavorite,
 | 
					 | 
				
			||||||
                          icon: Icon(Icons.push_pin_outlined),
 | 
					 | 
				
			||||||
                          onPressed: () => _toggleFavouriteRoom(context),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      if (_selectedRoomIds.length == 1)
 | 
					 | 
				
			||||||
                        IconButton(
 | 
					 | 
				
			||||||
                          icon: Icon(
 | 
					 | 
				
			||||||
                              selectedRoom.pushRuleState == PushRuleState.notify
 | 
					 | 
				
			||||||
                                  ? Icons.notifications_off_outlined
 | 
					 | 
				
			||||||
                                  : Icons.notifications_outlined),
 | 
					 | 
				
			||||||
                          tooltip: L10n.of(context).toggleMuted,
 | 
					 | 
				
			||||||
                          onPressed: () => _toggleMuted(context),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      IconButton(
 | 
					 | 
				
			||||||
                        icon: Icon(Icons.archive_outlined),
 | 
					 | 
				
			||||||
                        tooltip: L10n.of(context).archive,
 | 
					 | 
				
			||||||
                        onPressed: () => _archiveAction(context),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
              title: selectMode == SelectMode.share
 | 
					 | 
				
			||||||
                  ? Text(L10n.of(context).share)
 | 
					 | 
				
			||||||
                  : selectMode == SelectMode.select
 | 
					 | 
				
			||||||
                      ? Text(_selectedRoomIds.length.toString())
 | 
					 | 
				
			||||||
                      : DefaultAppBarSearchField(
 | 
					 | 
				
			||||||
                          searchController: searchController,
 | 
					 | 
				
			||||||
                          hintText: L10n.of(context).searchForAChat,
 | 
					 | 
				
			||||||
                          onChanged: (_) => setState(() => null),
 | 
					 | 
				
			||||||
                          suffix: Icon(Icons.search_outlined),
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            floatingActionButton:
 | 
					 | 
				
			||||||
                AdaptivePageLayout.of(context).columnMode(context)
 | 
					 | 
				
			||||||
                    ? null
 | 
					 | 
				
			||||||
                    : FloatingActionButton(
 | 
					 | 
				
			||||||
                        child: Icon(Icons.add_outlined),
 | 
					 | 
				
			||||||
                        onPressed: () => AdaptivePageLayout.of(context)
 | 
					 | 
				
			||||||
                            .pushNamedAndRemoveUntilIsFirst('/newprivatechat'),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
            body: Column(
 | 
					 | 
				
			||||||
              children: [
 | 
					 | 
				
			||||||
                ConnectionStatusHeader(),
 | 
					 | 
				
			||||||
                Expanded(
 | 
					 | 
				
			||||||
                  child: StreamBuilder(
 | 
					 | 
				
			||||||
                      stream: Matrix.of(context)
 | 
					 | 
				
			||||||
                          .client
 | 
					 | 
				
			||||||
                          .onSync
 | 
					 | 
				
			||||||
                          .stream
 | 
					 | 
				
			||||||
                          .where((s) => s.hasRoomUpdate),
 | 
					 | 
				
			||||||
                      builder: (context, snapshot) {
 | 
					 | 
				
			||||||
                        return FutureBuilder<void>(
 | 
					 | 
				
			||||||
                          future: waitForFirstSync(context),
 | 
					 | 
				
			||||||
                          builder: (BuildContext context, snapshot) {
 | 
					 | 
				
			||||||
                            if (snapshot.hasData) {
 | 
					 | 
				
			||||||
                              var rooms = List<Room>.from(
 | 
					 | 
				
			||||||
                                  Matrix.of(context).client.rooms);
 | 
					 | 
				
			||||||
                              rooms.removeWhere((Room room) =>
 | 
					 | 
				
			||||||
                                  room.lastEvent == null ||
 | 
					 | 
				
			||||||
                                  (searchMode &&
 | 
					 | 
				
			||||||
                                      !room.displayname.toLowerCase().contains(
 | 
					 | 
				
			||||||
                                          searchController.text.toLowerCase() ??
 | 
					 | 
				
			||||||
                                              '')));
 | 
					 | 
				
			||||||
                              if (rooms.isEmpty && (!searchMode)) {
 | 
					 | 
				
			||||||
                                return Column(
 | 
					 | 
				
			||||||
                                  mainAxisAlignment: MainAxisAlignment.center,
 | 
					 | 
				
			||||||
                                  mainAxisSize: MainAxisSize.min,
 | 
					 | 
				
			||||||
                                  children: <Widget>[
 | 
					 | 
				
			||||||
                                    Icon(
 | 
					 | 
				
			||||||
                                      searchMode
 | 
					 | 
				
			||||||
                                          ? Icons.search_outlined
 | 
					 | 
				
			||||||
                                          : Icons.maps_ugc_outlined,
 | 
					 | 
				
			||||||
                                      size: 80,
 | 
					 | 
				
			||||||
                                      color: Colors.grey,
 | 
					 | 
				
			||||||
                                    ),
 | 
					 | 
				
			||||||
                                    Text(
 | 
					 | 
				
			||||||
                                      searchMode
 | 
					 | 
				
			||||||
                                          ? L10n.of(context).noRoomsFound
 | 
					 | 
				
			||||||
                                          : L10n.of(context).startYourFirstChat,
 | 
					 | 
				
			||||||
                                      style: TextStyle(
 | 
					 | 
				
			||||||
                                        color: Colors.grey,
 | 
					 | 
				
			||||||
                                        fontSize: 16,
 | 
					 | 
				
			||||||
                                      ),
 | 
					 | 
				
			||||||
                                    ),
 | 
					 | 
				
			||||||
                                  ],
 | 
					 | 
				
			||||||
                                );
 | 
					 | 
				
			||||||
                              }
 | 
					 | 
				
			||||||
                              final totalCount = rooms.length;
 | 
					 | 
				
			||||||
                              return ListView.builder(
 | 
					 | 
				
			||||||
                                controller: _scrollController,
 | 
					 | 
				
			||||||
                                itemCount: totalCount,
 | 
					 | 
				
			||||||
                                itemBuilder: (BuildContext context, int i) =>
 | 
					 | 
				
			||||||
                                    ChatListItem(
 | 
					 | 
				
			||||||
                                  rooms[i],
 | 
					 | 
				
			||||||
                                  selected:
 | 
					 | 
				
			||||||
                                      _selectedRoomIds.contains(rooms[i].id),
 | 
					 | 
				
			||||||
                                  onTap: selectMode == SelectMode.select
 | 
					 | 
				
			||||||
                                      ? () => _toggleSelection(rooms[i].id)
 | 
					 | 
				
			||||||
                                      : null,
 | 
					 | 
				
			||||||
                                  onLongPress: selectMode != SelectMode.share
 | 
					 | 
				
			||||||
                                      ? () => _toggleSelection(rooms[i].id)
 | 
					 | 
				
			||||||
                                      : null,
 | 
					 | 
				
			||||||
                                  activeChat: widget.activeChat == rooms[i].id,
 | 
					 | 
				
			||||||
                                ),
 | 
					 | 
				
			||||||
                              );
 | 
					 | 
				
			||||||
                            } else {
 | 
					 | 
				
			||||||
                              return Center(
 | 
					 | 
				
			||||||
                                child: CircularProgressIndicator(),
 | 
					 | 
				
			||||||
                              );
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                          },
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                      }),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ],
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,238 +0,0 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
					 | 
				
			||||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
					 | 
				
			||||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/components/avatar.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/components/default_app_bar_search_field.dart';
 | 
					 | 
				
			||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
					 | 
				
			||||||
import 'package:fluffychat/components/matrix.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					 | 
				
			||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DiscoverPage extends StatefulWidget {
 | 
					 | 
				
			||||||
  final String alias;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const DiscoverPage({Key key, this.alias}) : super(key: key);
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  _DiscoverPageState createState() => _DiscoverPageState();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class _DiscoverPageState extends State<DiscoverPage> {
 | 
					 | 
				
			||||||
  final ScrollController _scrollController = ScrollController();
 | 
					 | 
				
			||||||
  bool _scrolledToTop = true;
 | 
					 | 
				
			||||||
  Future<PublicRoomsResponse> _publicRoomsResponse;
 | 
					 | 
				
			||||||
  Timer _coolDown;
 | 
					 | 
				
			||||||
  String _server;
 | 
					 | 
				
			||||||
  String _genericSearchTerm;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _search(BuildContext context, String query) async {
 | 
					 | 
				
			||||||
    _coolDown?.cancel();
 | 
					 | 
				
			||||||
    _coolDown = Timer(
 | 
					 | 
				
			||||||
      Duration(milliseconds: 500),
 | 
					 | 
				
			||||||
      () => setState(() {
 | 
					 | 
				
			||||||
        _genericSearchTerm = query;
 | 
					 | 
				
			||||||
        _publicRoomsResponse = null;
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _setServer(BuildContext context) async {
 | 
					 | 
				
			||||||
    final newServer = await showTextInputDialog(
 | 
					 | 
				
			||||||
        title: L10n.of(context).changeTheHomeserver,
 | 
					 | 
				
			||||||
        context: context,
 | 
					 | 
				
			||||||
        textFields: [
 | 
					 | 
				
			||||||
          DialogTextField(
 | 
					 | 
				
			||||||
            hintText: Matrix.of(context).client.homeserver.toString(),
 | 
					 | 
				
			||||||
            initialText: _server,
 | 
					 | 
				
			||||||
            keyboardType: TextInputType.url,
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
    if (newServer == null) return;
 | 
					 | 
				
			||||||
    setState(() {
 | 
					 | 
				
			||||||
      _server = newServer.single;
 | 
					 | 
				
			||||||
      _publicRoomsResponse = null;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<String> _joinRoomAndWait(
 | 
					 | 
				
			||||||
    BuildContext context,
 | 
					 | 
				
			||||||
    String roomId,
 | 
					 | 
				
			||||||
    String alias,
 | 
					 | 
				
			||||||
  ) async {
 | 
					 | 
				
			||||||
    if (Matrix.of(context).client.getRoomById(roomId) != null) {
 | 
					 | 
				
			||||||
      return roomId;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    final newRoomId = await Matrix.of(context)
 | 
					 | 
				
			||||||
        .client
 | 
					 | 
				
			||||||
        .joinRoomOrAlias(alias?.isNotEmpty ?? false ? alias : roomId);
 | 
					 | 
				
			||||||
    await Matrix.of(context)
 | 
					 | 
				
			||||||
        .client
 | 
					 | 
				
			||||||
        .onRoomUpdate
 | 
					 | 
				
			||||||
        .stream
 | 
					 | 
				
			||||||
        .firstWhere((r) => r.id == newRoomId);
 | 
					 | 
				
			||||||
    return newRoomId;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void _joinGroupAction(BuildContext context, PublicRoom room) async {
 | 
					 | 
				
			||||||
    if (await showOkCancelAlertDialog(
 | 
					 | 
				
			||||||
          context: context,
 | 
					 | 
				
			||||||
          okLabel: L10n.of(context).joinRoom,
 | 
					 | 
				
			||||||
          title: '${room.name} (${room.numJoinedMembers ?? 0})',
 | 
					 | 
				
			||||||
          message: room.topic ?? L10n.of(context).noDescription,
 | 
					 | 
				
			||||||
        ) ==
 | 
					 | 
				
			||||||
        OkCancelResult.cancel) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    final success = await showFutureLoadingDialog(
 | 
					 | 
				
			||||||
      context: context,
 | 
					 | 
				
			||||||
      future: () => _joinRoomAndWait(
 | 
					 | 
				
			||||||
        context,
 | 
					 | 
				
			||||||
        room.roomId,
 | 
					 | 
				
			||||||
        room.canonicalAlias ?? room.aliases.first,
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    if (success.error == null) {
 | 
					 | 
				
			||||||
      await AdaptivePageLayout.of(context)
 | 
					 | 
				
			||||||
          .pushNamedAndRemoveUntilIsFirst('/rooms/${success.result}');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  void initState() {
 | 
					 | 
				
			||||||
    _genericSearchTerm = widget.alias;
 | 
					 | 
				
			||||||
    _scrollController.addListener(() async {
 | 
					 | 
				
			||||||
      if (_scrollController.position.pixels > 0 && _scrolledToTop) {
 | 
					 | 
				
			||||||
        setState(() => _scrolledToTop = false);
 | 
					 | 
				
			||||||
      } else if (_scrollController.position.pixels == 0 && !_scrolledToTop) {
 | 
					 | 
				
			||||||
        setState(() => _scrolledToTop = true);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    super.initState();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					 | 
				
			||||||
    final server = _genericSearchTerm?.isValidMatrixId ?? false
 | 
					 | 
				
			||||||
        ? _genericSearchTerm.domain
 | 
					 | 
				
			||||||
        : _server;
 | 
					 | 
				
			||||||
    _publicRoomsResponse ??= Matrix.of(context)
 | 
					 | 
				
			||||||
        .client
 | 
					 | 
				
			||||||
        .searchPublicRooms(
 | 
					 | 
				
			||||||
          server: server,
 | 
					 | 
				
			||||||
          genericSearchTerm: _genericSearchTerm,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .catchError((error) {
 | 
					 | 
				
			||||||
      if (widget.alias == null) {
 | 
					 | 
				
			||||||
        throw error;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return PublicRoomsResponse.fromJson({
 | 
					 | 
				
			||||||
        'chunk': [],
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }).then((PublicRoomsResponse res) {
 | 
					 | 
				
			||||||
      if (widget.alias != null &&
 | 
					 | 
				
			||||||
          !res.chunk.any((room) =>
 | 
					 | 
				
			||||||
              room.aliases.contains(widget.alias) ||
 | 
					 | 
				
			||||||
              room.canonicalAlias == widget.alias)) {
 | 
					 | 
				
			||||||
        // we have to tack on the original alias
 | 
					 | 
				
			||||||
        res.chunk.add(PublicRoom.fromJson(<String, dynamic>{
 | 
					 | 
				
			||||||
          'aliases': [widget.alias],
 | 
					 | 
				
			||||||
          'name': widget.alias,
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return res;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return Scaffold(
 | 
					 | 
				
			||||||
      appBar: AppBar(
 | 
					 | 
				
			||||||
        leading: BackButton(),
 | 
					 | 
				
			||||||
        titleSpacing: 0,
 | 
					 | 
				
			||||||
        elevation: _scrolledToTop ? 0 : null,
 | 
					 | 
				
			||||||
        title: DefaultAppBarSearchField(
 | 
					 | 
				
			||||||
          onChanged: (text) => _search(context, text),
 | 
					 | 
				
			||||||
          hintText: L10n.of(context).searchForAChat,
 | 
					 | 
				
			||||||
          suffix: IconButton(
 | 
					 | 
				
			||||||
            icon: Icon(Icons.edit_outlined),
 | 
					 | 
				
			||||||
            onPressed: () => _setServer(context),
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
      body: FutureBuilder<PublicRoomsResponse>(
 | 
					 | 
				
			||||||
        future: _publicRoomsResponse,
 | 
					 | 
				
			||||||
        builder: (BuildContext context,
 | 
					 | 
				
			||||||
            AsyncSnapshot<PublicRoomsResponse> snapshot) {
 | 
					 | 
				
			||||||
          if (snapshot.hasError) {
 | 
					 | 
				
			||||||
            return Center(child: Text(snapshot.error.toString()));
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          if (snapshot.connectionState != ConnectionState.done) {
 | 
					 | 
				
			||||||
            return Center(child: CircularProgressIndicator());
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          final publicRoomsResponse = snapshot.data;
 | 
					 | 
				
			||||||
          if (publicRoomsResponse.chunk.isEmpty) {
 | 
					 | 
				
			||||||
            return Center(
 | 
					 | 
				
			||||||
              child: Text(
 | 
					 | 
				
			||||||
                'No public groups found...',
 | 
					 | 
				
			||||||
                textAlign: TextAlign.center,
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return GridView.builder(
 | 
					 | 
				
			||||||
            padding: EdgeInsets.all(12),
 | 
					 | 
				
			||||||
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 | 
					 | 
				
			||||||
              crossAxisCount: 2,
 | 
					 | 
				
			||||||
              childAspectRatio: 1,
 | 
					 | 
				
			||||||
              crossAxisSpacing: 16,
 | 
					 | 
				
			||||||
              mainAxisSpacing: 16,
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            controller: _scrollController,
 | 
					 | 
				
			||||||
            itemCount: publicRoomsResponse.chunk.length,
 | 
					 | 
				
			||||||
            itemBuilder: (BuildContext context, int i) => Material(
 | 
					 | 
				
			||||||
              elevation: 2,
 | 
					 | 
				
			||||||
              borderRadius: BorderRadius.circular(16),
 | 
					 | 
				
			||||||
              child: InkWell(
 | 
					 | 
				
			||||||
                onTap: () => _joinGroupAction(
 | 
					 | 
				
			||||||
                  context,
 | 
					 | 
				
			||||||
                  publicRoomsResponse.chunk[i],
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                borderRadius: BorderRadius.circular(16),
 | 
					 | 
				
			||||||
                child: Padding(
 | 
					 | 
				
			||||||
                  padding: const EdgeInsets.all(8.0),
 | 
					 | 
				
			||||||
                  child: Column(
 | 
					 | 
				
			||||||
                    mainAxisSize: MainAxisSize.min,
 | 
					 | 
				
			||||||
                    children: [
 | 
					 | 
				
			||||||
                      Avatar(
 | 
					 | 
				
			||||||
                          Uri.parse(
 | 
					 | 
				
			||||||
                              publicRoomsResponse.chunk[i].avatarUrl ?? ''),
 | 
					 | 
				
			||||||
                          publicRoomsResponse.chunk[i].name),
 | 
					 | 
				
			||||||
                      Text(
 | 
					 | 
				
			||||||
                        publicRoomsResponse.chunk[i].name,
 | 
					 | 
				
			||||||
                        style: TextStyle(
 | 
					 | 
				
			||||||
                          fontSize: 16,
 | 
					 | 
				
			||||||
                          fontWeight: FontWeight.bold,
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                        maxLines: 1,
 | 
					 | 
				
			||||||
                        textAlign: TextAlign.center,
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      Text(
 | 
					 | 
				
			||||||
                        L10n.of(context).countParticipants(
 | 
					 | 
				
			||||||
                            publicRoomsResponse.chunk[i].numJoinedMembers ?? 0),
 | 
					 | 
				
			||||||
                        style: TextStyle(fontSize: 10.5),
 | 
					 | 
				
			||||||
                        maxLines: 1,
 | 
					 | 
				
			||||||
                        textAlign: TextAlign.center,
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      Text(
 | 
					 | 
				
			||||||
                        publicRoomsResponse.chunk[i].topic ??
 | 
					 | 
				
			||||||
                            L10n.of(context).noDescription,
 | 
					 | 
				
			||||||
                        maxLines: 4,
 | 
					 | 
				
			||||||
                        textAlign: TextAlign.center,
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										235
									
								
								lib/views/home_view.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								lib/views/home_view.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,235 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					import 'dart:io';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
				
			||||||
 | 
					import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
				
			||||||
 | 
					import 'package:famedlysdk/famedlysdk.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/views/home_view_parts/discover.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/views/share_view.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/cupertino.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/app_config.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/utils/platform_infos.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:receive_sharing_intent/receive_sharing_intent.dart';
 | 
				
			||||||
 | 
					import '../components/matrix.dart';
 | 
				
			||||||
 | 
					import '../utils/matrix_file_extension.dart';
 | 
				
			||||||
 | 
					import '../utils/url_launcher.dart';
 | 
				
			||||||
 | 
					import 'home_view_parts/chat_list.dart';
 | 
				
			||||||
 | 
					import 'home_view_parts/status_list.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum SelectMode { normal, share, select }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HomeView extends StatefulWidget {
 | 
				
			||||||
 | 
					  final String activeChat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const HomeView({this.activeChat, Key key}) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  _HomeViewState createState() => _HomeViewState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _HomeViewState extends State<HomeView> {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    _initReceiveSharingIntent();
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int currentIndex = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  StreamSubscription _intentDataStreamSubscription;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  StreamSubscription _intentFileStreamSubscription;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  StreamSubscription _onShareContentChanged;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AppBar appBar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onShare(Map<String, dynamic> content) {
 | 
				
			||||||
 | 
					    if (content != null) {
 | 
				
			||||||
 | 
					      WidgetsBinding.instance.addPostFrameCallback(
 | 
				
			||||||
 | 
					        (_) => Navigator.of(context).push(
 | 
				
			||||||
 | 
					          MaterialPageRoute(
 | 
				
			||||||
 | 
					            builder: (_) => ShareView(),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _processIncomingSharedFiles(List<SharedMediaFile> files) {
 | 
				
			||||||
 | 
					    if (files?.isEmpty ?? true) return;
 | 
				
			||||||
 | 
					    AdaptivePageLayout.of(context).popUntilIsFirst();
 | 
				
			||||||
 | 
					    final file = File(files.first.path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Matrix.of(context).shareContent = {
 | 
				
			||||||
 | 
					      'msgtype': 'chat.fluffy.shared_file',
 | 
				
			||||||
 | 
					      'file': MatrixFile(
 | 
				
			||||||
 | 
					        bytes: file.readAsBytesSync(),
 | 
				
			||||||
 | 
					        name: file.path,
 | 
				
			||||||
 | 
					      ).detectFileType,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _processIncomingSharedText(String text) {
 | 
				
			||||||
 | 
					    if (text == null) return;
 | 
				
			||||||
 | 
					    AdaptivePageLayout.of(context).popUntilIsFirst();
 | 
				
			||||||
 | 
					    if (text.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
 | 
				
			||||||
 | 
					        (text.toLowerCase().startsWith(AppConfig.schemePrefix) &&
 | 
				
			||||||
 | 
					            !RegExp(r'\s').hasMatch(text))) {
 | 
				
			||||||
 | 
					      UrlLauncher(context, text).openMatrixToUrl();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Matrix.of(context).shareContent = {
 | 
				
			||||||
 | 
					      'msgtype': 'm.text',
 | 
				
			||||||
 | 
					      'body': text,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _initReceiveSharingIntent() {
 | 
				
			||||||
 | 
					    if (!PlatformInfos.isMobile) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // For sharing images coming from outside the app while the app is in the memory
 | 
				
			||||||
 | 
					    _intentFileStreamSubscription = ReceiveSharingIntent.getMediaStream()
 | 
				
			||||||
 | 
					        .listen(_processIncomingSharedFiles, onError: print);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // For sharing images coming from outside the app while the app is closed
 | 
				
			||||||
 | 
					    ReceiveSharingIntent.getInitialMedia().then(_processIncomingSharedFiles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // For sharing or opening urls/text coming from outside the app while the app is in the memory
 | 
				
			||||||
 | 
					    _intentDataStreamSubscription = ReceiveSharingIntent.getTextStream()
 | 
				
			||||||
 | 
					        .listen(_processIncomingSharedText, onError: print);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // For sharing or opening urls/text coming from outside the app while the app is closed
 | 
				
			||||||
 | 
					    ReceiveSharingIntent.getInitialText().then(_processIncomingSharedText);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void dispose() {
 | 
				
			||||||
 | 
					    _intentDataStreamSubscription?.cancel();
 | 
				
			||||||
 | 
					    _intentFileStreamSubscription?.cancel();
 | 
				
			||||||
 | 
					    super.dispose();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String _server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _setServer(BuildContext context) async {
 | 
				
			||||||
 | 
					    final newServer = await showTextInputDialog(
 | 
				
			||||||
 | 
					        title: L10n.of(context).changeTheHomeserver,
 | 
				
			||||||
 | 
					        context: context,
 | 
				
			||||||
 | 
					        textFields: [
 | 
				
			||||||
 | 
					          DialogTextField(
 | 
				
			||||||
 | 
					            prefixText: 'https://',
 | 
				
			||||||
 | 
					            hintText: Matrix.of(context).client.homeserver.host,
 | 
				
			||||||
 | 
					            initialText: _server,
 | 
				
			||||||
 | 
					            keyboardType: TextInputType.url,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    if (newServer == null) return;
 | 
				
			||||||
 | 
					    setState(() {
 | 
				
			||||||
 | 
					      _server = newServer.single;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onFabTab() {
 | 
				
			||||||
 | 
					    switch (currentIndex) {
 | 
				
			||||||
 | 
					      case 0:
 | 
				
			||||||
 | 
					        AdaptivePageLayout.of(context)
 | 
				
			||||||
 | 
					            .pushNamedAndRemoveUntilIsFirst('/newstatus');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 1:
 | 
				
			||||||
 | 
					        AdaptivePageLayout.of(context)
 | 
				
			||||||
 | 
					            .pushNamedAndRemoveUntilIsFirst('/newprivatechat');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 2:
 | 
				
			||||||
 | 
					        AdaptivePageLayout.of(context)
 | 
				
			||||||
 | 
					            .pushNamedAndRemoveUntilIsFirst('/newgroup');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 3:
 | 
				
			||||||
 | 
					        _setServer(context);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    _onShareContentChanged ??=
 | 
				
			||||||
 | 
					        Matrix.of(context).onShareContentChanged.stream.listen(_onShare);
 | 
				
			||||||
 | 
					    Widget body;
 | 
				
			||||||
 | 
					    IconData fabIcon;
 | 
				
			||||||
 | 
					    switch (currentIndex) {
 | 
				
			||||||
 | 
					      case 0:
 | 
				
			||||||
 | 
					        body = StatusList();
 | 
				
			||||||
 | 
					        fabIcon = Icons.edit_outlined;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 1:
 | 
				
			||||||
 | 
					        body = ChatList(
 | 
				
			||||||
 | 
					          type: ChatListType.messages,
 | 
				
			||||||
 | 
					          onCustomAppBar: (appBar) => setState(() => this.appBar = appBar),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        fabIcon = Icons.add_outlined;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 2:
 | 
				
			||||||
 | 
					        body = ChatList(
 | 
				
			||||||
 | 
					          type: ChatListType.groups,
 | 
				
			||||||
 | 
					          onCustomAppBar: (appBar) => setState(() => this.appBar = appBar),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        fabIcon = Icons.group_add_outlined;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 3:
 | 
				
			||||||
 | 
					        body = Discover(server: _server);
 | 
				
			||||||
 | 
					        fabIcon = Icons.domain_outlined;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      appBar: appBar ??
 | 
				
			||||||
 | 
					          AppBar(
 | 
				
			||||||
 | 
					              centerTitle: false,
 | 
				
			||||||
 | 
					              actions: [
 | 
				
			||||||
 | 
					                IconButton(
 | 
				
			||||||
 | 
					                  icon: Icon(Icons.account_circle_outlined),
 | 
				
			||||||
 | 
					                  onPressed: () =>
 | 
				
			||||||
 | 
					                      AdaptivePageLayout.of(context).pushNamed('/settings'),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					              title: Text(AppConfig.applicationName)),
 | 
				
			||||||
 | 
					      body: body,
 | 
				
			||||||
 | 
					      floatingActionButton: FloatingActionButton(
 | 
				
			||||||
 | 
					        child: Icon(fabIcon),
 | 
				
			||||||
 | 
					        onPressed: _onFabTab,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
 | 
				
			||||||
 | 
					      bottomNavigationBar: BottomNavigationBar(
 | 
				
			||||||
 | 
					        unselectedItemColor: Colors.black,
 | 
				
			||||||
 | 
					        currentIndex: currentIndex,
 | 
				
			||||||
 | 
					        showSelectedLabels: true,
 | 
				
			||||||
 | 
					        showUnselectedLabels: false,
 | 
				
			||||||
 | 
					        type: BottomNavigationBarType.fixed,
 | 
				
			||||||
 | 
					        elevation: 20,
 | 
				
			||||||
 | 
					        backgroundColor: Theme.of(context).scaffoldBackgroundColor,
 | 
				
			||||||
 | 
					        onTap: (i) => setState(() => currentIndex = i),
 | 
				
			||||||
 | 
					        items: [
 | 
				
			||||||
 | 
					          BottomNavigationBarItem(
 | 
				
			||||||
 | 
					            label: L10n.of(context).status,
 | 
				
			||||||
 | 
					            icon: Icon(Icons.home_outlined),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          BottomNavigationBarItem(
 | 
				
			||||||
 | 
					            label: L10n.of(context).messages,
 | 
				
			||||||
 | 
					            icon: Icon(CupertinoIcons.chat_bubble_2),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          BottomNavigationBarItem(
 | 
				
			||||||
 | 
					            label: L10n.of(context).groups,
 | 
				
			||||||
 | 
					            icon: Icon(Icons.people_outline),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          BottomNavigationBarItem(
 | 
				
			||||||
 | 
					            label: L10n.of(context).discover,
 | 
				
			||||||
 | 
					            icon: Icon(CupertinoIcons.search_circle),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										245
									
								
								lib/views/home_view_parts/chat_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								lib/views/home_view_parts/chat_list.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,245 @@
 | 
				
			|||||||
 | 
					import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
				
			||||||
 | 
					import 'package:famedlysdk/famedlysdk.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/connection_status_header.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/default_app_bar_search_field.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/list_items/chat_list_item.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/matrix.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
				
			||||||
 | 
					import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ChatListType { messages, groups, all }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum SelectMode { normal, select }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChatList extends StatefulWidget {
 | 
				
			||||||
 | 
					  final ChatListType type;
 | 
				
			||||||
 | 
					  final void Function(AppBar appBar) onCustomAppBar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const ChatList({
 | 
				
			||||||
 | 
					    Key key,
 | 
				
			||||||
 | 
					    @required this.type,
 | 
				
			||||||
 | 
					    this.onCustomAppBar,
 | 
				
			||||||
 | 
					  }) : super(key: key);
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  _ChatListState createState() => _ChatListState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _ChatListState extends State<ChatList> {
 | 
				
			||||||
 | 
					  bool get searchMode => searchController.text?.isNotEmpty ?? false;
 | 
				
			||||||
 | 
					  final TextEditingController searchController = TextEditingController();
 | 
				
			||||||
 | 
					  final _selectedRoomIds = <String>{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _toggleSelection(String roomId) {
 | 
				
			||||||
 | 
					    setState(() => _selectedRoomIds.contains(roomId)
 | 
				
			||||||
 | 
					        ? _selectedRoomIds.remove(roomId)
 | 
				
			||||||
 | 
					        : _selectedRoomIds.add(roomId));
 | 
				
			||||||
 | 
					    widget.onCustomAppBar(
 | 
				
			||||||
 | 
					      _selectedRoomIds.isEmpty
 | 
				
			||||||
 | 
					          ? null
 | 
				
			||||||
 | 
					          : AppBar(
 | 
				
			||||||
 | 
					              centerTitle: false,
 | 
				
			||||||
 | 
					              leading: IconButton(
 | 
				
			||||||
 | 
					                icon: Icon(Icons.close_outlined),
 | 
				
			||||||
 | 
					                onPressed: () {
 | 
				
			||||||
 | 
					                  _selectedRoomIds.clear();
 | 
				
			||||||
 | 
					                  widget.onCustomAppBar(null);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              title: Text(
 | 
				
			||||||
 | 
					                L10n.of(context)
 | 
				
			||||||
 | 
					                    .numberSelected(_selectedRoomIds.length.toString()),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              actions: [
 | 
				
			||||||
 | 
					                if (_selectedRoomIds.length == 1)
 | 
				
			||||||
 | 
					                  IconButton(
 | 
				
			||||||
 | 
					                    tooltip: L10n.of(context).toggleUnread,
 | 
				
			||||||
 | 
					                    icon: Icon(Matrix.of(context)
 | 
				
			||||||
 | 
					                            .client
 | 
				
			||||||
 | 
					                            .getRoomById(_selectedRoomIds.single)
 | 
				
			||||||
 | 
					                            .isUnread
 | 
				
			||||||
 | 
					                        ? Icons.mark_chat_read_outlined
 | 
				
			||||||
 | 
					                        : Icons.mark_chat_unread_outlined),
 | 
				
			||||||
 | 
					                    onPressed: () => _toggleUnread(context),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                if (_selectedRoomIds.length == 1)
 | 
				
			||||||
 | 
					                  IconButton(
 | 
				
			||||||
 | 
					                    tooltip: L10n.of(context).toggleFavorite,
 | 
				
			||||||
 | 
					                    icon: Icon(Icons.push_pin_outlined),
 | 
				
			||||||
 | 
					                    onPressed: () => _toggleFavouriteRoom(context),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                if (_selectedRoomIds.length == 1)
 | 
				
			||||||
 | 
					                  IconButton(
 | 
				
			||||||
 | 
					                    icon: Icon(Matrix.of(context)
 | 
				
			||||||
 | 
					                                .client
 | 
				
			||||||
 | 
					                                .getRoomById(_selectedRoomIds.single)
 | 
				
			||||||
 | 
					                                .pushRuleState ==
 | 
				
			||||||
 | 
					                            PushRuleState.notify
 | 
				
			||||||
 | 
					                        ? Icons.notifications_off_outlined
 | 
				
			||||||
 | 
					                        : Icons.notifications_outlined),
 | 
				
			||||||
 | 
					                    tooltip: L10n.of(context).toggleMuted,
 | 
				
			||||||
 | 
					                    onPressed: () => _toggleMuted(context),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                IconButton(
 | 
				
			||||||
 | 
					                  icon: Icon(Icons.archive_outlined),
 | 
				
			||||||
 | 
					                  tooltip: L10n.of(context).archive,
 | 
				
			||||||
 | 
					                  onPressed: () => _archiveAction(context),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _toggleUnread(BuildContext context) {
 | 
				
			||||||
 | 
					    final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
 | 
				
			||||||
 | 
					    return showFutureLoadingDialog(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      future: () => room.setUnread(!room.isUnread),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _toggleFavouriteRoom(BuildContext context) {
 | 
				
			||||||
 | 
					    final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
 | 
				
			||||||
 | 
					    return showFutureLoadingDialog(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      future: () => room.setFavourite(!room.isFavourite),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _toggleMuted(BuildContext context) {
 | 
				
			||||||
 | 
					    final room = Matrix.of(context).client.getRoomById(_selectedRoomIds.single);
 | 
				
			||||||
 | 
					    return showFutureLoadingDialog(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      future: () => room.setPushRuleState(
 | 
				
			||||||
 | 
					          room.pushRuleState == PushRuleState.notify
 | 
				
			||||||
 | 
					              ? PushRuleState.mentions_only
 | 
				
			||||||
 | 
					              : PushRuleState.notify),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _archiveAction(BuildContext context) async {
 | 
				
			||||||
 | 
					    final confirmed = await showOkCancelAlertDialog(
 | 
				
			||||||
 | 
					          context: context,
 | 
				
			||||||
 | 
					          title: L10n.of(context).areYouSure,
 | 
				
			||||||
 | 
					        ) ==
 | 
				
			||||||
 | 
					        OkCancelResult.ok;
 | 
				
			||||||
 | 
					    if (!confirmed) return;
 | 
				
			||||||
 | 
					    await showFutureLoadingDialog(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      future: () => _archiveSelectedRooms(context),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    setState(() => null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> _archiveSelectedRooms(BuildContext context) async {
 | 
				
			||||||
 | 
					    final client = Matrix.of(context).client;
 | 
				
			||||||
 | 
					    while (_selectedRoomIds.isNotEmpty) {
 | 
				
			||||||
 | 
					      final roomId = _selectedRoomIds.first;
 | 
				
			||||||
 | 
					      await client.getRoomById(roomId).leave();
 | 
				
			||||||
 | 
					      _toggleSelection(roomId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> waitForFirstSync(BuildContext context) async {
 | 
				
			||||||
 | 
					    var client = Matrix.of(context).client;
 | 
				
			||||||
 | 
					    if (client.prevBatch?.isEmpty ?? true) {
 | 
				
			||||||
 | 
					      await client.onFirstSync.stream.first;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final selectMode =
 | 
				
			||||||
 | 
					        _selectedRoomIds.isEmpty ? SelectMode.normal : SelectMode.select;
 | 
				
			||||||
 | 
					    return Column(children: [
 | 
				
			||||||
 | 
					      ConnectionStatusHeader(),
 | 
				
			||||||
 | 
					      Expanded(
 | 
				
			||||||
 | 
					        child: StreamBuilder(
 | 
				
			||||||
 | 
					            stream: Matrix.of(context)
 | 
				
			||||||
 | 
					                .client
 | 
				
			||||||
 | 
					                .onSync
 | 
				
			||||||
 | 
					                .stream
 | 
				
			||||||
 | 
					                .where((s) => s.hasRoomUpdate),
 | 
				
			||||||
 | 
					            builder: (context, snapshot) {
 | 
				
			||||||
 | 
					              return FutureBuilder<void>(
 | 
				
			||||||
 | 
					                future: waitForFirstSync(context),
 | 
				
			||||||
 | 
					                builder: (BuildContext context, snapshot) {
 | 
				
			||||||
 | 
					                  if (snapshot.hasData) {
 | 
				
			||||||
 | 
					                    var rooms =
 | 
				
			||||||
 | 
					                        List<Room>.from(Matrix.of(context).client.rooms);
 | 
				
			||||||
 | 
					                    rooms.removeWhere((room) =>
 | 
				
			||||||
 | 
					                        room.lastEvent == null ||
 | 
				
			||||||
 | 
					                        (searchMode &&
 | 
				
			||||||
 | 
					                            !room.displayname.toLowerCase().contains(
 | 
				
			||||||
 | 
					                                searchController.text.toLowerCase() ?? '')));
 | 
				
			||||||
 | 
					                    if (widget.type == ChatListType.messages) {
 | 
				
			||||||
 | 
					                      rooms.removeWhere((room) => !room.isDirectChat);
 | 
				
			||||||
 | 
					                    } else if (widget.type == ChatListType.groups) {
 | 
				
			||||||
 | 
					                      rooms.removeWhere((room) => room.isDirectChat);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (rooms.isEmpty && (!searchMode)) {
 | 
				
			||||||
 | 
					                      return Column(
 | 
				
			||||||
 | 
					                        mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
 | 
					                        mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					                        children: <Widget>[
 | 
				
			||||||
 | 
					                          Icon(
 | 
				
			||||||
 | 
					                            searchMode
 | 
				
			||||||
 | 
					                                ? Icons.search_outlined
 | 
				
			||||||
 | 
					                                : Icons.maps_ugc_outlined,
 | 
				
			||||||
 | 
					                            size: 80,
 | 
				
			||||||
 | 
					                            color: Colors.grey,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          Text(
 | 
				
			||||||
 | 
					                            searchMode
 | 
				
			||||||
 | 
					                                ? L10n.of(context).noRoomsFound
 | 
				
			||||||
 | 
					                                : L10n.of(context).startYourFirstChat,
 | 
				
			||||||
 | 
					                            style: TextStyle(
 | 
				
			||||||
 | 
					                              color: Colors.grey,
 | 
				
			||||||
 | 
					                              fontSize: 16,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                      );
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    final totalCount = rooms.length;
 | 
				
			||||||
 | 
					                    return ListView.builder(
 | 
				
			||||||
 | 
					                      itemCount: totalCount + 1,
 | 
				
			||||||
 | 
					                      itemBuilder: (BuildContext context, int i) => i == 0
 | 
				
			||||||
 | 
					                          ? Padding(
 | 
				
			||||||
 | 
					                              padding: EdgeInsets.all(12),
 | 
				
			||||||
 | 
					                              child: DefaultAppBarSearchField(
 | 
				
			||||||
 | 
					                                hintText: L10n.of(context).search,
 | 
				
			||||||
 | 
					                                prefixIcon: Icon(Icons.search_outlined),
 | 
				
			||||||
 | 
					                                searchController: searchController,
 | 
				
			||||||
 | 
					                                onChanged: (_) => setState(() => null),
 | 
				
			||||||
 | 
					                                padding: EdgeInsets.zero,
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                          : ChatListItem(
 | 
				
			||||||
 | 
					                              rooms[i - 1],
 | 
				
			||||||
 | 
					                              selected:
 | 
				
			||||||
 | 
					                                  _selectedRoomIds.contains(rooms[i - 1].id),
 | 
				
			||||||
 | 
					                              onTap: selectMode == SelectMode.select &&
 | 
				
			||||||
 | 
					                                      widget.onCustomAppBar != null
 | 
				
			||||||
 | 
					                                  ? () => _toggleSelection(rooms[i - 1].id)
 | 
				
			||||||
 | 
					                                  : null,
 | 
				
			||||||
 | 
					                              onLongPress: widget.onCustomAppBar != null
 | 
				
			||||||
 | 
					                                  ? () => _toggleSelection(rooms[i - 1].id)
 | 
				
			||||||
 | 
					                                  : null,
 | 
				
			||||||
 | 
					                              activeChat: Matrix.of(context).activeRoomId ==
 | 
				
			||||||
 | 
					                                  rooms[i - 1].id,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                  } else {
 | 
				
			||||||
 | 
					                    return Center(
 | 
				
			||||||
 | 
					                      child: CircularProgressIndicator(),
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										215
									
								
								lib/views/home_view_parts/discover.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								lib/views/home_view_parts/discover.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,215 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
				
			||||||
 | 
					import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
				
			||||||
 | 
					import 'package:famedlysdk/famedlysdk.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/avatar.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/default_app_bar_search_field.dart';
 | 
				
			||||||
 | 
					import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/matrix.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Discover extends StatefulWidget {
 | 
				
			||||||
 | 
					  final String alias;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final String server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const Discover({
 | 
				
			||||||
 | 
					    Key key,
 | 
				
			||||||
 | 
					    this.alias,
 | 
				
			||||||
 | 
					    this.server,
 | 
				
			||||||
 | 
					  }) : super(key: key);
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  _DiscoverState createState() => _DiscoverState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _DiscoverState extends State<Discover> {
 | 
				
			||||||
 | 
					  Future<PublicRoomsResponse> _publicRoomsResponse;
 | 
				
			||||||
 | 
					  Timer _coolDown;
 | 
				
			||||||
 | 
					  String _genericSearchTerm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _search(BuildContext context, String query) async {
 | 
				
			||||||
 | 
					    _coolDown?.cancel();
 | 
				
			||||||
 | 
					    _coolDown = Timer(
 | 
				
			||||||
 | 
					      Duration(milliseconds: 500),
 | 
				
			||||||
 | 
					      () => setState(() {
 | 
				
			||||||
 | 
					        _genericSearchTerm = query;
 | 
				
			||||||
 | 
					        _publicRoomsResponse = null;
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<String> _joinRoomAndWait(
 | 
				
			||||||
 | 
					    BuildContext context,
 | 
				
			||||||
 | 
					    String roomId,
 | 
				
			||||||
 | 
					    String alias,
 | 
				
			||||||
 | 
					  ) async {
 | 
				
			||||||
 | 
					    if (Matrix.of(context).client.getRoomById(roomId) != null) {
 | 
				
			||||||
 | 
					      return roomId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    final newRoomId = await Matrix.of(context)
 | 
				
			||||||
 | 
					        .client
 | 
				
			||||||
 | 
					        .joinRoomOrAlias(alias?.isNotEmpty ?? false ? alias : roomId);
 | 
				
			||||||
 | 
					    await Matrix.of(context)
 | 
				
			||||||
 | 
					        .client
 | 
				
			||||||
 | 
					        .onRoomUpdate
 | 
				
			||||||
 | 
					        .stream
 | 
				
			||||||
 | 
					        .firstWhere((r) => r.id == newRoomId);
 | 
				
			||||||
 | 
					    return newRoomId;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _joinGroupAction(BuildContext context, PublicRoom room) async {
 | 
				
			||||||
 | 
					    if (await showOkCancelAlertDialog(
 | 
				
			||||||
 | 
					          context: context,
 | 
				
			||||||
 | 
					          okLabel: L10n.of(context).joinRoom,
 | 
				
			||||||
 | 
					          title: '${room.name} (${room.numJoinedMembers ?? 0})',
 | 
				
			||||||
 | 
					          message: room.topic ?? L10n.of(context).noDescription,
 | 
				
			||||||
 | 
					        ) ==
 | 
				
			||||||
 | 
					        OkCancelResult.cancel) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    final success = await showFutureLoadingDialog(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      future: () => _joinRoomAndWait(
 | 
				
			||||||
 | 
					        context,
 | 
				
			||||||
 | 
					        room.roomId,
 | 
				
			||||||
 | 
					        room.canonicalAlias ?? room.aliases.first,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (success.error == null) {
 | 
				
			||||||
 | 
					      await AdaptivePageLayout.of(context)
 | 
				
			||||||
 | 
					          .pushNamedAndRemoveUntilIsFirst('/rooms/${success.result}');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    _genericSearchTerm = widget.alias;
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final server = _genericSearchTerm?.isValidMatrixId ?? false
 | 
				
			||||||
 | 
					        ? _genericSearchTerm.domain
 | 
				
			||||||
 | 
					        : widget.server;
 | 
				
			||||||
 | 
					    _publicRoomsResponse ??= Matrix.of(context)
 | 
				
			||||||
 | 
					        .client
 | 
				
			||||||
 | 
					        .searchPublicRooms(
 | 
				
			||||||
 | 
					          server: server,
 | 
				
			||||||
 | 
					          genericSearchTerm: _genericSearchTerm,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .catchError((error) {
 | 
				
			||||||
 | 
					      if (widget.alias == null) {
 | 
				
			||||||
 | 
					        throw error;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return PublicRoomsResponse.fromJson({
 | 
				
			||||||
 | 
					        'chunk': [],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }).then((PublicRoomsResponse res) {
 | 
				
			||||||
 | 
					      if (widget.alias != null &&
 | 
				
			||||||
 | 
					          !res.chunk.any((room) =>
 | 
				
			||||||
 | 
					              room.aliases.contains(widget.alias) ||
 | 
				
			||||||
 | 
					              room.canonicalAlias == widget.alias)) {
 | 
				
			||||||
 | 
					        // we have to tack on the original alias
 | 
				
			||||||
 | 
					        res.chunk.add(PublicRoom.fromJson(<String, dynamic>{
 | 
				
			||||||
 | 
					          'aliases': [widget.alias],
 | 
				
			||||||
 | 
					          'name': widget.alias,
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return res;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return ListView(
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        Padding(
 | 
				
			||||||
 | 
					          padding: EdgeInsets.all(12),
 | 
				
			||||||
 | 
					          child: DefaultAppBarSearchField(
 | 
				
			||||||
 | 
					            hintText: L10n.of(context).search,
 | 
				
			||||||
 | 
					            prefixIcon: Icon(Icons.search_outlined),
 | 
				
			||||||
 | 
					            onChanged: (t) => _search(context, t),
 | 
				
			||||||
 | 
					            padding: EdgeInsets.zero,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        FutureBuilder<PublicRoomsResponse>(
 | 
				
			||||||
 | 
					            future: _publicRoomsResponse,
 | 
				
			||||||
 | 
					            builder: (BuildContext context,
 | 
				
			||||||
 | 
					                AsyncSnapshot<PublicRoomsResponse> snapshot) {
 | 
				
			||||||
 | 
					              if (snapshot.hasError) {
 | 
				
			||||||
 | 
					                return Center(child: Text(snapshot.error.toString()));
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              if (snapshot.connectionState != ConnectionState.done) {
 | 
				
			||||||
 | 
					                return Center(child: CircularProgressIndicator());
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              final publicRoomsResponse = snapshot.data;
 | 
				
			||||||
 | 
					              if (publicRoomsResponse.chunk.isEmpty) {
 | 
				
			||||||
 | 
					                return Center(
 | 
				
			||||||
 | 
					                  child: Text(
 | 
				
			||||||
 | 
					                    'No public groups found...',
 | 
				
			||||||
 | 
					                    textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              return GridView.builder(
 | 
				
			||||||
 | 
					                shrinkWrap: true,
 | 
				
			||||||
 | 
					                padding: EdgeInsets.all(12),
 | 
				
			||||||
 | 
					                physics: NeverScrollableScrollPhysics(),
 | 
				
			||||||
 | 
					                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 | 
				
			||||||
 | 
					                  crossAxisCount: 2,
 | 
				
			||||||
 | 
					                  childAspectRatio: 1,
 | 
				
			||||||
 | 
					                  crossAxisSpacing: 16,
 | 
				
			||||||
 | 
					                  mainAxisSpacing: 16,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                itemCount: publicRoomsResponse.chunk.length,
 | 
				
			||||||
 | 
					                itemBuilder: (BuildContext context, int i) => Material(
 | 
				
			||||||
 | 
					                  elevation: 2,
 | 
				
			||||||
 | 
					                  borderRadius: BorderRadius.circular(16),
 | 
				
			||||||
 | 
					                  child: InkWell(
 | 
				
			||||||
 | 
					                    onTap: () => _joinGroupAction(
 | 
				
			||||||
 | 
					                      context,
 | 
				
			||||||
 | 
					                      publicRoomsResponse.chunk[i],
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    borderRadius: BorderRadius.circular(16),
 | 
				
			||||||
 | 
					                    child: Padding(
 | 
				
			||||||
 | 
					                      padding: const EdgeInsets.all(8.0),
 | 
				
			||||||
 | 
					                      child: Column(
 | 
				
			||||||
 | 
					                        mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					                        children: [
 | 
				
			||||||
 | 
					                          Avatar(
 | 
				
			||||||
 | 
					                              Uri.parse(
 | 
				
			||||||
 | 
					                                  publicRoomsResponse.chunk[i].avatarUrl ?? ''),
 | 
				
			||||||
 | 
					                              publicRoomsResponse.chunk[i].name),
 | 
				
			||||||
 | 
					                          Text(
 | 
				
			||||||
 | 
					                            publicRoomsResponse.chunk[i].name,
 | 
				
			||||||
 | 
					                            style: TextStyle(
 | 
				
			||||||
 | 
					                              fontSize: 16,
 | 
				
			||||||
 | 
					                              fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            maxLines: 1,
 | 
				
			||||||
 | 
					                            textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          Text(
 | 
				
			||||||
 | 
					                            L10n.of(context).countParticipants(
 | 
				
			||||||
 | 
					                                publicRoomsResponse.chunk[i].numJoinedMembers ??
 | 
				
			||||||
 | 
					                                    0),
 | 
				
			||||||
 | 
					                            style: TextStyle(fontSize: 10.5),
 | 
				
			||||||
 | 
					                            maxLines: 1,
 | 
				
			||||||
 | 
					                            textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          Text(
 | 
				
			||||||
 | 
					                            publicRoomsResponse.chunk[i].topic ??
 | 
				
			||||||
 | 
					                                L10n.of(context).noDescription,
 | 
				
			||||||
 | 
					                            maxLines: 4,
 | 
				
			||||||
 | 
					                            textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										66
									
								
								lib/views/home_view_parts/status_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								lib/views/home_view_parts/status_list.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					import 'package:fluffychat/components/list_items/status_list_tile.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/matrix.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/utils/status.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StatusList extends StatefulWidget {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  _StatusListState createState() => _StatusListState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _StatusListState extends State<StatusList> {
 | 
				
			||||||
 | 
					  bool _onlyContacts = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return ListView(children: [
 | 
				
			||||||
 | 
					      Row(
 | 
				
			||||||
 | 
					        mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          RaisedButton(
 | 
				
			||||||
 | 
					            elevation: _onlyContacts ? 7 : null,
 | 
				
			||||||
 | 
					            color: !_onlyContacts ? null : Theme.of(context).primaryColor,
 | 
				
			||||||
 | 
					            child: Text(
 | 
				
			||||||
 | 
					              'Contacts',
 | 
				
			||||||
 | 
					              style: TextStyle(color: _onlyContacts ? Colors.white : null),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            onPressed: () => setState(() => _onlyContacts = true),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          RaisedButton(
 | 
				
			||||||
 | 
					            elevation: !_onlyContacts ? 7 : null,
 | 
				
			||||||
 | 
					            color: _onlyContacts ? null : Theme.of(context).primaryColor,
 | 
				
			||||||
 | 
					            child: Text(
 | 
				
			||||||
 | 
					              'All',
 | 
				
			||||||
 | 
					              style: TextStyle(color: !_onlyContacts ? Colors.white : null),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            onPressed: () => setState(() => _onlyContacts = false),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      Divider(height: 1),
 | 
				
			||||||
 | 
					      StreamBuilder<Object>(
 | 
				
			||||||
 | 
					          stream: Matrix.of(context)
 | 
				
			||||||
 | 
					              .client
 | 
				
			||||||
 | 
					              .onAccountData
 | 
				
			||||||
 | 
					              .stream
 | 
				
			||||||
 | 
					              .where((a) => a.type == Status.namespace),
 | 
				
			||||||
 | 
					          builder: (context, snapshot) {
 | 
				
			||||||
 | 
					            final statuses = Matrix.of(context).statuses.values.toList()
 | 
				
			||||||
 | 
					              ..sort((a, b) => b.dateTime.compareTo(a.dateTime));
 | 
				
			||||||
 | 
					            if (_onlyContacts) {
 | 
				
			||||||
 | 
					              final client = Matrix.of(context).client;
 | 
				
			||||||
 | 
					              statuses.removeWhere(
 | 
				
			||||||
 | 
					                  (p) => client.getDirectChatFromUserId(p.senderId) == null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return ListView.separated(
 | 
				
			||||||
 | 
					              physics: NeverScrollableScrollPhysics(),
 | 
				
			||||||
 | 
					              shrinkWrap: true,
 | 
				
			||||||
 | 
					              padding: EdgeInsets.only(bottom: 24),
 | 
				
			||||||
 | 
					              separatorBuilder: (_, __) => Divider(height: 1),
 | 
				
			||||||
 | 
					              itemCount: statuses.length,
 | 
				
			||||||
 | 
					              itemBuilder: (context, i) => StatusListTile(status: statuses[i]),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										166
									
								
								lib/views/set_status_view.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								lib/views/set_status_view.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,166 @@
 | 
				
			|||||||
 | 
					import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
				
			||||||
 | 
					import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
				
			||||||
 | 
					import 'package:famedlysdk/famedlysdk.dart';
 | 
				
			||||||
 | 
					import 'package:file_picker_cross/file_picker_cross.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/matrix.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/utils/platform_infos.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
				
			||||||
 | 
					import 'package:image_picker/image_picker.dart';
 | 
				
			||||||
 | 
					import '../utils/string_color.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SetStatusView extends StatefulWidget {
 | 
				
			||||||
 | 
					  final String initialText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const SetStatusView({Key key, this.initialText}) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  _SetStatusViewState createState() => _SetStatusViewState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SetStatusViewState extends State<SetStatusView> {
 | 
				
			||||||
 | 
					  Color _color;
 | 
				
			||||||
 | 
					  final TextEditingController _controller = TextEditingController();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					    _controller.text = widget.initialText;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _setStatusAction(BuildContext context, [String message]) async {
 | 
				
			||||||
 | 
					    final result = await showFutureLoadingDialog(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      future: () => Matrix.of(context).client.sendPresence(
 | 
				
			||||||
 | 
					            Matrix.of(context).client.userID,
 | 
				
			||||||
 | 
					            PresenceType.online,
 | 
				
			||||||
 | 
					            statusMsg: message ?? _controller.text,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (result.error == null) AdaptivePageLayout.of(context).pop();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _setCameraImageStatusAction(BuildContext context) async {
 | 
				
			||||||
 | 
					    MatrixFile file;
 | 
				
			||||||
 | 
					    if (PlatformInfos.isMobile) {
 | 
				
			||||||
 | 
					      final result = await ImagePicker().getImage(
 | 
				
			||||||
 | 
					          source: ImageSource.camera,
 | 
				
			||||||
 | 
					          imageQuality: 50,
 | 
				
			||||||
 | 
					          maxWidth: 1600,
 | 
				
			||||||
 | 
					          maxHeight: 1600);
 | 
				
			||||||
 | 
					      if (result == null) return;
 | 
				
			||||||
 | 
					      file = MatrixFile(
 | 
				
			||||||
 | 
					        bytes: await result.readAsBytes(),
 | 
				
			||||||
 | 
					        name: result.path,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    final uploadResp = await showFutureLoadingDialog(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      future: () => Matrix.of(context).client.upload(file.bytes, file.name),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (uploadResp.error == null) {
 | 
				
			||||||
 | 
					      return _setStatusAction(context, uploadResp.result);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _setImageStatusAction(BuildContext context) async {
 | 
				
			||||||
 | 
					    MatrixFile file;
 | 
				
			||||||
 | 
					    if (PlatformInfos.isMobile) {
 | 
				
			||||||
 | 
					      final result = await ImagePicker().getImage(
 | 
				
			||||||
 | 
					          source: ImageSource.gallery,
 | 
				
			||||||
 | 
					          imageQuality: 50,
 | 
				
			||||||
 | 
					          maxWidth: 1600,
 | 
				
			||||||
 | 
					          maxHeight: 1600);
 | 
				
			||||||
 | 
					      if (result == null) return;
 | 
				
			||||||
 | 
					      file = MatrixFile(
 | 
				
			||||||
 | 
					        bytes: await result.readAsBytes(),
 | 
				
			||||||
 | 
					        name: result.path,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      final result =
 | 
				
			||||||
 | 
					          await FilePickerCross.importFromStorage(type: FileTypeCross.image);
 | 
				
			||||||
 | 
					      if (result == null) return;
 | 
				
			||||||
 | 
					      file = MatrixFile(
 | 
				
			||||||
 | 
					        bytes: result.toUint8List(),
 | 
				
			||||||
 | 
					        name: result.fileName,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    final uploadResp = await showFutureLoadingDialog(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      future: () => Matrix.of(context).client.upload(file.bytes, file.name),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (uploadResp.error == null) {
 | 
				
			||||||
 | 
					      return _setStatusAction(context, uploadResp.result);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    _color ??= Theme.of(context).primaryColor;
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      extendBodyBehindAppBar: true,
 | 
				
			||||||
 | 
					      appBar: AppBar(
 | 
				
			||||||
 | 
					        backgroundColor:
 | 
				
			||||||
 | 
					            Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5),
 | 
				
			||||||
 | 
					        title: Text(L10n.of(context).statusExampleMessage),
 | 
				
			||||||
 | 
					        actions: [
 | 
				
			||||||
 | 
					          IconButton(
 | 
				
			||||||
 | 
					            icon: Icon(Icons.info_outlined),
 | 
				
			||||||
 | 
					            onPressed: () => showOkAlertDialog(
 | 
				
			||||||
 | 
					              context: context,
 | 
				
			||||||
 | 
					              title: L10n.of(context).setStatus,
 | 
				
			||||||
 | 
					              message:
 | 
				
			||||||
 | 
					                  'Show your status to all users you share a room. Every status will replace the previous one. Be aware that statuses are public and therefore not end-to-end encrypted.',
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      body: AnimatedContainer(
 | 
				
			||||||
 | 
					        duration: Duration(seconds: 2),
 | 
				
			||||||
 | 
					        alignment: Alignment.center,
 | 
				
			||||||
 | 
					        color: _color,
 | 
				
			||||||
 | 
					        child: SingleChildScrollView(
 | 
				
			||||||
 | 
					          child: TextField(
 | 
				
			||||||
 | 
					            minLines: 1,
 | 
				
			||||||
 | 
					            maxLines: 10,
 | 
				
			||||||
 | 
					            autofocus: true,
 | 
				
			||||||
 | 
					            textAlign: TextAlign.center,
 | 
				
			||||||
 | 
					            controller: _controller,
 | 
				
			||||||
 | 
					            onChanged: (s) => setState(() => _color = s.color),
 | 
				
			||||||
 | 
					            style: TextStyle(fontSize: 40, color: Colors.white),
 | 
				
			||||||
 | 
					            decoration: InputDecoration(
 | 
				
			||||||
 | 
					              border: InputBorder.none,
 | 
				
			||||||
 | 
					              filled: false,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      floatingActionButton: Column(
 | 
				
			||||||
 | 
					        mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          if (PlatformInfos.isMobile) ...{
 | 
				
			||||||
 | 
					            FloatingActionButton(
 | 
				
			||||||
 | 
					              backgroundColor: Colors.white,
 | 
				
			||||||
 | 
					              foregroundColor: Theme.of(context).primaryColor,
 | 
				
			||||||
 | 
					              child: Icon(Icons.camera_alt_outlined),
 | 
				
			||||||
 | 
					              onPressed: () => _setCameraImageStatusAction(context),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            SizedBox(height: 12),
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          FloatingActionButton(
 | 
				
			||||||
 | 
					            backgroundColor: Colors.white,
 | 
				
			||||||
 | 
					            foregroundColor: Theme.of(context).primaryColor,
 | 
				
			||||||
 | 
					            child: Icon(Icons.image_outlined),
 | 
				
			||||||
 | 
					            onPressed: () => _setImageStatusAction(context),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          SizedBox(height: 12),
 | 
				
			||||||
 | 
					          FloatingActionButton(
 | 
				
			||||||
 | 
					            child: Icon(Icons.send_outlined),
 | 
				
			||||||
 | 
					            onPressed: () => _setStatusAction(context),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -6,9 +6,26 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import '../components/matrix.dart';
 | 
					import '../components/matrix.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SettingsIgnoreList extends StatelessWidget {
 | 
					class SettingsIgnoreList extends StatefulWidget {
 | 
				
			||||||
 | 
					  final String initialUserId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  SettingsIgnoreList({Key key, this.initialUserId}) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  _SettingsIgnoreListState createState() => _SettingsIgnoreListState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SettingsIgnoreListState extends State<SettingsIgnoreList> {
 | 
				
			||||||
  final TextEditingController _controller = TextEditingController();
 | 
					  final TextEditingController _controller = TextEditingController();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					    if (widget.initialUserId != null) {
 | 
				
			||||||
 | 
					      _controller.text = widget.initialUserId.replaceAll('@', '');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _ignoreUser(BuildContext context) {
 | 
					  void _ignoreUser(BuildContext context) {
 | 
				
			||||||
    if (_controller.text.isEmpty) return;
 | 
					    if (_controller.text.isEmpty) return;
 | 
				
			||||||
    final userId = '@${_controller.text}';
 | 
					    final userId = '@${_controller.text}';
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								lib/views/share_view.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/views/share_view.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
				
			||||||
 | 
					import 'package:fluffychat/components/matrix.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'home_view_parts/chat_list.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ShareView extends StatelessWidget {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      appBar: AppBar(
 | 
				
			||||||
 | 
					        leading: IconButton(
 | 
				
			||||||
 | 
					          icon: Icon(Icons.close_outlined),
 | 
				
			||||||
 | 
					          onPressed: () {
 | 
				
			||||||
 | 
					            Matrix.of(context).shareContent = null;
 | 
				
			||||||
 | 
					            AdaptivePageLayout.of(context).pop();
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        title: Text(L10n.of(context).share),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      body: ChatList(
 | 
				
			||||||
 | 
					        type: ChatListType.all,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -176,6 +176,13 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.16.2"
 | 
					    version: "0.16.2"
 | 
				
			||||||
 | 
					  cupertino_icons:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: cupertino_icons
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "1.0.0"
 | 
				
			||||||
  dapackages:
 | 
					  dapackages:
 | 
				
			||||||
    dependency: "direct dev"
 | 
					    dependency: "direct dev"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,7 @@ dependencies:
 | 
				
			|||||||
      url: https://github.com/UnifiedPush/flutter-connector.git
 | 
					      url: https://github.com/UnifiedPush/flutter-connector.git
 | 
				
			||||||
      ref: main
 | 
					      ref: main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cupertino_icons: any
 | 
				
			||||||
  localstorage: ^3.0.6+9
 | 
					  localstorage: ^3.0.6+9
 | 
				
			||||||
  file_picker_cross: 4.2.2
 | 
					  file_picker_cross: 4.2.2
 | 
				
			||||||
  image_picker: ^0.6.7+21
 | 
					  image_picker: ^0.6.7+21
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user