mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	change: Implement contact list instead of status
This commit is contained in:
		
							parent
							
								
									6960618f55
								
							
						
					
					
						commit
						b9be33c16b
					
				
							
								
								
									
										74
									
								
								lib/components/list_items/contact_list_tile.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								lib/components/list_items/contact_list_tile.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,74 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/components/avatar.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import '../../utils/presence_extension.dart';
 | 
			
		||||
import '../matrix.dart';
 | 
			
		||||
 | 
			
		||||
class ContactListTile extends StatelessWidget {
 | 
			
		||||
  final Presence contact;
 | 
			
		||||
 | 
			
		||||
  const ContactListTile({Key key, @required this.contact}) : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    var statusMsg = contact.presence?.statusMsg?.isNotEmpty ?? false
 | 
			
		||||
        ? contact.presence.statusMsg
 | 
			
		||||
        : null;
 | 
			
		||||
    if (contact.senderId == '@jana:janian.de') {
 | 
			
		||||
      statusMsg = 'Hallo Welt';
 | 
			
		||||
    }
 | 
			
		||||
    return FutureBuilder<Profile>(
 | 
			
		||||
        future:
 | 
			
		||||
            Matrix.of(context).client.getProfileFromUserId(contact.senderId),
 | 
			
		||||
        builder: (context, snapshot) {
 | 
			
		||||
          final displayname =
 | 
			
		||||
              snapshot.data?.displayname ?? contact.senderId.localpart;
 | 
			
		||||
          final avatarUrl = snapshot.data?.avatarUrl;
 | 
			
		||||
          return ListTile(
 | 
			
		||||
              leading: Avatar(avatarUrl, displayname),
 | 
			
		||||
              title: Row(
 | 
			
		||||
                children: [
 | 
			
		||||
                  Icon(Icons.circle, color: contact.color, size: 10),
 | 
			
		||||
                  SizedBox(width: 4),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: Text(
 | 
			
		||||
                      displayname,
 | 
			
		||||
                      overflow: TextOverflow.ellipsis,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
              subtitle: statusMsg == null
 | 
			
		||||
                  ? Text(contact.getLocalizedLastActiveAgo(context))
 | 
			
		||||
                  : Row(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Icon(Icons.edit_outlined,
 | 
			
		||||
                            color: Theme.of(context).accentColor, size: 12),
 | 
			
		||||
                        SizedBox(width: 2),
 | 
			
		||||
                        Expanded(
 | 
			
		||||
                          child: Text(
 | 
			
		||||
                            statusMsg,
 | 
			
		||||
                            overflow: TextOverflow.ellipsis,
 | 
			
		||||
                            style: TextStyle(
 | 
			
		||||
                              color:
 | 
			
		||||
                                  Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
              onTap: () async {
 | 
			
		||||
                if (contact.senderId == Matrix.of(context).client.userID) {
 | 
			
		||||
                  return;
 | 
			
		||||
                }
 | 
			
		||||
                final roomId = await User(contact.senderId,
 | 
			
		||||
                        room: Room(id: '', client: Matrix.of(context).client))
 | 
			
		||||
                    .startDirectChat();
 | 
			
		||||
                await AdaptivePageLayout.of(context)
 | 
			
		||||
                    .pushNamedAndRemoveUntilIsFirst('/rooms/${roomId}');
 | 
			
		||||
              });
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,136 +0,0 @@
 | 
			
		||||
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: Text(
 | 
			
		||||
                  displayname,
 | 
			
		||||
                  overflow: TextOverflow.ellipsis,
 | 
			
		||||
                  style: TextStyle(fontWeight: FontWeight.bold),
 | 
			
		||||
                ),
 | 
			
		||||
                subtitle: Text(status.dateTime.localizedTime(context),
 | 
			
		||||
                    style: TextStyle(fontSize: 14)),
 | 
			
		||||
                trailing: Matrix.of(context).client.userID == status.senderId
 | 
			
		||||
                    ? null
 | 
			
		||||
                    : 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:
 | 
			
		||||
                          Matrix.of(context).client.userID == status.senderId
 | 
			
		||||
                              ? null
 | 
			
		||||
                              : () 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,7 +10,6 @@ import 'package:fluffychat/utils/firebase_controller.dart';
 | 
			
		||||
import 'package:fluffychat/utils/matrix_locals.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'package:fluffychat/utils/sentry_controller.dart';
 | 
			
		||||
import 'package:fluffychat/utils/status.dart';
 | 
			
		||||
import 'package:flushbar/flushbar.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -127,7 +126,6 @@ class MatrixState extends State<Matrix> {
 | 
			
		||||
  StreamSubscription onKeyVerificationRequestSub;
 | 
			
		||||
  StreamSubscription onJitsiCallSub;
 | 
			
		||||
  StreamSubscription onNotification;
 | 
			
		||||
  StreamSubscription<Presence> onPresence;
 | 
			
		||||
  StreamSubscription<LoginState> onLoginStateChanged;
 | 
			
		||||
  StreamSubscription<UiaRequest> onUiaRequest;
 | 
			
		||||
  StreamSubscription<html.Event> onFocusSub;
 | 
			
		||||
@ -290,10 +288,6 @@ class MatrixState extends State<Matrix> {
 | 
			
		||||
    LoadingDialog.defaultBackLabel = L10n.of(context).close;
 | 
			
		||||
    LoadingDialog.defaultOnError = (Object e) => e.toLocalizedString(context);
 | 
			
		||||
 | 
			
		||||
    onPresence ??= client.onPresence.stream
 | 
			
		||||
        .where((p) => p.presence?.statusMsg != null)
 | 
			
		||||
        .listen(_onPresence);
 | 
			
		||||
 | 
			
		||||
    onRoomKeyRequestSub ??=
 | 
			
		||||
        client.onRoomKeyRequest.stream.listen((RoomKeyRequest request) async {
 | 
			
		||||
      final room = request.room;
 | 
			
		||||
@ -401,45 +395,6 @@ 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
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    onRoomKeyRequestSub?.cancel();
 | 
			
		||||
@ -448,7 +403,6 @@ class MatrixState extends State<Matrix> {
 | 
			
		||||
    onNotification?.cancel();
 | 
			
		||||
    onFocusSub?.cancel();
 | 
			
		||||
    onBlurSub?.cancel();
 | 
			
		||||
    onPresence?.cancel();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,34 @@
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
 | 
			
		||||
extension ClientPresenceExtension on Client {
 | 
			
		||||
  List<Presence> get statuses => presences.values
 | 
			
		||||
      .where((p) => p.presence.statusMsg?.isNotEmpty ?? false)
 | 
			
		||||
      .toList();
 | 
			
		||||
  List<Presence> get contactList {
 | 
			
		||||
    final directChatsMxid = rooms
 | 
			
		||||
        .where((r) => r.isDirectChat)
 | 
			
		||||
        .map((r) => r.directChatMatrixID)
 | 
			
		||||
        .toSet();
 | 
			
		||||
    final contactList = directChatsMxid
 | 
			
		||||
        .map(
 | 
			
		||||
          (mxid) =>
 | 
			
		||||
              presences[mxid] ??
 | 
			
		||||
              Presence.fromJson(
 | 
			
		||||
                {
 | 
			
		||||
                  'sender': mxid,
 | 
			
		||||
                  'type': 'm.presence',
 | 
			
		||||
                  'content': {'presence': 'online'},
 | 
			
		||||
                },
 | 
			
		||||
              ),
 | 
			
		||||
        )
 | 
			
		||||
        .toList();
 | 
			
		||||
    contactList.addAll(
 | 
			
		||||
      presences.values
 | 
			
		||||
          .where((p) =>
 | 
			
		||||
              !directChatsMxid.contains(p.senderId) &&
 | 
			
		||||
              (p.presence?.statusMsg?.isNotEmpty ?? false))
 | 
			
		||||
          .toList(),
 | 
			
		||||
    );
 | 
			
		||||
    contactList.sort((a, b) => (a.presence.lastActiveAgo?.toDouble() ??
 | 
			
		||||
            double.infinity)
 | 
			
		||||
        .compareTo((b.presence.lastActiveAgo?.toDouble() ?? double.infinity)));
 | 
			
		||||
    return contactList;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -37,4 +37,16 @@ extension PresenceExtension on Presence {
 | 
			
		||||
    }
 | 
			
		||||
    return presence.presence.getLocalized(context);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Color get color {
 | 
			
		||||
    switch (presence?.presence ?? PresenceType.offline) {
 | 
			
		||||
      case PresenceType.online:
 | 
			
		||||
        return Colors.green;
 | 
			
		||||
      case PresenceType.offline:
 | 
			
		||||
        return Colors.red;
 | 
			
		||||
      case PresenceType.unavailable:
 | 
			
		||||
      default:
 | 
			
		||||
        return Colors.grey;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,19 +0,0 @@
 | 
			
		||||
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,6 +1,7 @@
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/app_config.dart';
 | 
			
		||||
import 'package:fluffychat/components/avatar.dart';
 | 
			
		||||
import 'package:fluffychat/components/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/utils/fluffy_share.dart';
 | 
			
		||||
import 'package:flushbar/flushbar_helper.dart';
 | 
			
		||||
@ -238,6 +239,7 @@ class _ChatDetailsState extends State<ChatDetails> {
 | 
			
		||||
                                        .scaffoldBackgroundColor,
 | 
			
		||||
                                    foregroundColor: Colors.grey,
 | 
			
		||||
                                    child: Icon(Icons.edit_outlined),
 | 
			
		||||
                                    radius: Avatar.defaultSize / 2,
 | 
			
		||||
                                  )
 | 
			
		||||
                                : null,
 | 
			
		||||
                            title: Text('${L10n.of(context).groupDescription}:',
 | 
			
		||||
 | 
			
		||||
@ -12,13 +12,14 @@ 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:future_loading_dialog/future_loading_dialog.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/settings.dart';
 | 
			
		||||
import 'home_view_parts/status_list.dart';
 | 
			
		||||
import 'home_view_parts/contact_list.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
enum SelectMode { normal, share, select }
 | 
			
		||||
@ -143,11 +144,30 @@ class _HomeViewState extends State<HomeView> with TickerProviderStateMixin {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _setStatus() async {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: L10n.of(context).setStatus,
 | 
			
		||||
        textFields: [
 | 
			
		||||
          DialogTextField(
 | 
			
		||||
            hintText: L10n.of(context).statusExampleMessage,
 | 
			
		||||
          ),
 | 
			
		||||
        ]);
 | 
			
		||||
    if (input == null) return;
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => Matrix.of(context).client.sendPresence(
 | 
			
		||||
            Matrix.of(context).client.userID,
 | 
			
		||||
            PresenceType.online,
 | 
			
		||||
            statusMsg: input.single,
 | 
			
		||||
          ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _onFabTab() {
 | 
			
		||||
    switch (currentIndex) {
 | 
			
		||||
      case 0:
 | 
			
		||||
        AdaptivePageLayout.of(context)
 | 
			
		||||
            .pushNamedAndRemoveUntilIsFirst('/newstatus');
 | 
			
		||||
        _setStatus();
 | 
			
		||||
        break;
 | 
			
		||||
      case 1:
 | 
			
		||||
        AdaptivePageLayout.of(context)
 | 
			
		||||
@ -212,7 +232,7 @@ class _HomeViewState extends State<HomeView> with TickerProviderStateMixin {
 | 
			
		||||
      body: TabBarView(
 | 
			
		||||
        controller: _pageController,
 | 
			
		||||
        children: [
 | 
			
		||||
          StatusList(key: Key('StatusList')),
 | 
			
		||||
          ContactList(),
 | 
			
		||||
          ChatList(
 | 
			
		||||
            onCustomAppBar: (appBar) => setState(() => this.appBar = appBar),
 | 
			
		||||
          ),
 | 
			
		||||
@ -246,8 +266,8 @@ class _HomeViewState extends State<HomeView> with TickerProviderStateMixin {
 | 
			
		||||
        },
 | 
			
		||||
        items: [
 | 
			
		||||
          BottomNavigationBarItem(
 | 
			
		||||
            label: L10n.of(context).status,
 | 
			
		||||
            icon: Icon(Icons.home_outlined),
 | 
			
		||||
            label: L10n.of(context).contacts,
 | 
			
		||||
            icon: Icon(Icons.people_outlined),
 | 
			
		||||
          ),
 | 
			
		||||
          BottomNavigationBarItem(
 | 
			
		||||
            label: L10n.of(context).messages,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										72
									
								
								lib/views/home_view_parts/contact_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								lib/views/home_view_parts/contact_list.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,72 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/components/avatar.dart';
 | 
			
		||||
import 'package:fluffychat/components/default_app_bar_search_field.dart';
 | 
			
		||||
import 'package:fluffychat/components/list_items/contact_list_tile.dart';
 | 
			
		||||
import 'package:fluffychat/components/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import '../../utils/client_presence_extension.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class ContactList extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  _ContactListState createState() => _ContactListState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ContactListState extends State<ContactList> {
 | 
			
		||||
  String _searchQuery = '';
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return ListView(children: [
 | 
			
		||||
      Padding(
 | 
			
		||||
        padding: EdgeInsets.all(12),
 | 
			
		||||
        child: DefaultAppBarSearchField(
 | 
			
		||||
          hintText: L10n.of(context).search,
 | 
			
		||||
          prefixIcon: Icon(Icons.search_outlined),
 | 
			
		||||
          onChanged: (t) => setState(() => _searchQuery = t),
 | 
			
		||||
          padding: EdgeInsets.zero,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      ListTile(
 | 
			
		||||
        leading: CircleAvatar(
 | 
			
		||||
          backgroundColor: Theme.of(context).primaryColor,
 | 
			
		||||
          foregroundColor: Colors.white,
 | 
			
		||||
          child: Icon(Icons.add_outlined),
 | 
			
		||||
          radius: Avatar.defaultSize / 2,
 | 
			
		||||
        ),
 | 
			
		||||
        title: Text('Add new contact'),
 | 
			
		||||
        onTap: () =>
 | 
			
		||||
            AdaptivePageLayout.of(context).pushNamed('/newprivatechat'),
 | 
			
		||||
      ),
 | 
			
		||||
      Divider(height: 1),
 | 
			
		||||
      StreamBuilder<Object>(
 | 
			
		||||
          stream: Matrix.of(context).client.onSync.stream,
 | 
			
		||||
          builder: (context, snapshot) {
 | 
			
		||||
            final contactList = Matrix.of(context)
 | 
			
		||||
                .client
 | 
			
		||||
                .contactList
 | 
			
		||||
                .where((p) => p.senderId
 | 
			
		||||
                    .toLowerCase()
 | 
			
		||||
                    .contains(_searchQuery.toLowerCase()))
 | 
			
		||||
                .toList();
 | 
			
		||||
            if (contactList.isEmpty) {
 | 
			
		||||
              return Container(
 | 
			
		||||
                padding: EdgeInsets.all(16),
 | 
			
		||||
                alignment: Alignment.center,
 | 
			
		||||
                child: Text(
 | 
			
		||||
                  'No contacts found...',
 | 
			
		||||
                  textAlign: TextAlign.center,
 | 
			
		||||
                ),
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
            return ListView.builder(
 | 
			
		||||
              physics: NeverScrollableScrollPhysics(),
 | 
			
		||||
              shrinkWrap: true,
 | 
			
		||||
              padding: EdgeInsets.only(bottom: 24),
 | 
			
		||||
              itemCount: contactList.length,
 | 
			
		||||
              itemBuilder: (context, i) =>
 | 
			
		||||
                  ContactListTile(contact: contactList[i]),
 | 
			
		||||
            );
 | 
			
		||||
          }),
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,78 +0,0 @@
 | 
			
		||||
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';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class StatusList extends StatefulWidget {
 | 
			
		||||
  const StatusList({Key key}) : super(key: key);
 | 
			
		||||
  @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(
 | 
			
		||||
              L10n.of(context).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(
 | 
			
		||||
              L10n.of(context).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);
 | 
			
		||||
            }
 | 
			
		||||
            if (statuses.isEmpty) {
 | 
			
		||||
              return Container(
 | 
			
		||||
                padding: EdgeInsets.all(16),
 | 
			
		||||
                alignment: Alignment.center,
 | 
			
		||||
                child: Text(
 | 
			
		||||
                  L10n.of(context).noStatusesFound,
 | 
			
		||||
                  textAlign: TextAlign.center,
 | 
			
		||||
                ),
 | 
			
		||||
              );
 | 
			
		||||
            }
 | 
			
		||||
            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]),
 | 
			
		||||
            );
 | 
			
		||||
          }),
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user