mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-03 22:07:23 +01:00 
			
		
		
		
	feat: New experimental design
This commit is contained in:
		
							parent
							
								
									9b1d7ecef8
								
							
						
					
					
						commit
						94aa9a39c6
					
				
							
								
								
									
										65
									
								
								lib/components/default_bottom_navigation_bar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								lib/components/default_bottom_navigation_bar.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class DefaultBottomNavigationBar extends StatelessWidget {
 | 
			
		||||
  final int currentIndex;
 | 
			
		||||
 | 
			
		||||
  const DefaultBottomNavigationBar({Key key, this.currentIndex = 1})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return BottomNavigationBar(
 | 
			
		||||
      onTap: (i) {
 | 
			
		||||
        if (i == currentIndex) return;
 | 
			
		||||
        switch (i) {
 | 
			
		||||
          case 0:
 | 
			
		||||
            AdaptivePageLayout.of(context)
 | 
			
		||||
                .pushNamedAndRemoveUntilIsFirst('/contacts');
 | 
			
		||||
            break;
 | 
			
		||||
          case 1:
 | 
			
		||||
            AdaptivePageLayout.of(context).pushNamedAndRemoveAllOthers('/');
 | 
			
		||||
            break;
 | 
			
		||||
          case 2:
 | 
			
		||||
            AdaptivePageLayout.of(context)
 | 
			
		||||
                .pushNamedAndRemoveUntilIsFirst('/discover');
 | 
			
		||||
            break;
 | 
			
		||||
          case 3:
 | 
			
		||||
            AdaptivePageLayout.of(context)
 | 
			
		||||
                .pushNamedAndRemoveUntilIsFirst('/settings');
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      backgroundColor: Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
      selectedItemColor: Theme.of(context).accentColor,
 | 
			
		||||
      currentIndex: currentIndex,
 | 
			
		||||
      //unselectedItemColor: Theme.of(context).textTheme.bodyText1.color,
 | 
			
		||||
      type: BottomNavigationBarType.fixed,
 | 
			
		||||
      showUnselectedLabels: false,
 | 
			
		||||
      items: [
 | 
			
		||||
        BottomNavigationBarItem(
 | 
			
		||||
          icon: Icon(currentIndex == 0 ? Icons.people : Icons.people_outlined),
 | 
			
		||||
          label: L10n.of(context).contacts,
 | 
			
		||||
        ),
 | 
			
		||||
        BottomNavigationBarItem(
 | 
			
		||||
          icon: Icon(currentIndex == 1
 | 
			
		||||
              ? CupertinoIcons.chat_bubble_2_fill
 | 
			
		||||
              : CupertinoIcons.chat_bubble_2),
 | 
			
		||||
          label: L10n.of(context).messages,
 | 
			
		||||
        ),
 | 
			
		||||
        BottomNavigationBarItem(
 | 
			
		||||
          icon: Icon(currentIndex == 2
 | 
			
		||||
              ? CupertinoIcons.compass_fill
 | 
			
		||||
              : CupertinoIcons.compass),
 | 
			
		||||
          label: L10n.of(context).discover,
 | 
			
		||||
        ),
 | 
			
		||||
        BottomNavigationBarItem(
 | 
			
		||||
          icon: Icon(
 | 
			
		||||
              currentIndex == 3 ? Icons.settings : Icons.settings_outlined),
 | 
			
		||||
          label: L10n.of(context).settings,
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,162 +0,0 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/components/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import '../utils/client_presence_extension.dart';
 | 
			
		||||
import '../utils/presence_extension.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'avatar.dart';
 | 
			
		||||
 | 
			
		||||
class HorizontalStoriesList extends StatefulWidget {
 | 
			
		||||
  final String searchQuery;
 | 
			
		||||
 | 
			
		||||
  const HorizontalStoriesList({Key key, this.searchQuery = ''})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  _HorizontalStoriesListState createState() => _HorizontalStoriesListState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _HorizontalStoriesListState extends State<HorizontalStoriesList> {
 | 
			
		||||
  StreamSubscription _onSync;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _onSync?.cancel();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  DateTime _lastSetState = DateTime.now();
 | 
			
		||||
  Timer _coolDown;
 | 
			
		||||
 | 
			
		||||
  void _updateView() {
 | 
			
		||||
    _lastSetState = DateTime.now();
 | 
			
		||||
    setState(() => null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static const double height = 68.0;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    _onSync ??= Matrix.of(context).client.onSync.stream.listen((_) {
 | 
			
		||||
      if (DateTime.now().millisecondsSinceEpoch -
 | 
			
		||||
              _lastSetState.millisecondsSinceEpoch <
 | 
			
		||||
          1000) {
 | 
			
		||||
        _coolDown?.cancel();
 | 
			
		||||
        _coolDown = Timer(Duration(seconds: 1), _updateView);
 | 
			
		||||
      } else {
 | 
			
		||||
        _updateView();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    final contactList = Matrix.of(context)
 | 
			
		||||
        .client
 | 
			
		||||
        .contactList
 | 
			
		||||
        .where((p) =>
 | 
			
		||||
            p.senderId.toLowerCase().contains(widget.searchQuery.toLowerCase()))
 | 
			
		||||
        .toList();
 | 
			
		||||
    return AnimatedContainer(
 | 
			
		||||
      height: contactList.isEmpty ? 0 : height,
 | 
			
		||||
      duration: Duration(milliseconds: 300),
 | 
			
		||||
      child: contactList.isEmpty
 | 
			
		||||
          ? null
 | 
			
		||||
          : ListView.builder(
 | 
			
		||||
              scrollDirection: Axis.horizontal,
 | 
			
		||||
              itemCount: contactList.length,
 | 
			
		||||
              itemBuilder: (context, i) =>
 | 
			
		||||
                  _StoriesListTile(story: contactList[i]),
 | 
			
		||||
            ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _StoriesListTile extends StatelessWidget {
 | 
			
		||||
  final Presence story;
 | 
			
		||||
 | 
			
		||||
  const _StoriesListTile({
 | 
			
		||||
    Key key,
 | 
			
		||||
    @required this.story,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final hasStatusMessage = story.presence.statusMsg?.isNotEmpty ?? false;
 | 
			
		||||
    return FutureBuilder<Profile>(
 | 
			
		||||
        future: Matrix.of(context).client.getProfileFromUserId(story.senderId),
 | 
			
		||||
        builder: (context, snapshot) {
 | 
			
		||||
          final displayname =
 | 
			
		||||
              snapshot.data?.displayname ?? story.senderId.localpart;
 | 
			
		||||
          final avatarUrl = snapshot.data?.avatarUrl;
 | 
			
		||||
          return Container(
 | 
			
		||||
            width: Avatar.defaultSize + 32,
 | 
			
		||||
            height: _HorizontalStoriesListState.height,
 | 
			
		||||
            child: InkWell(
 | 
			
		||||
              borderRadius: BorderRadius.circular(8),
 | 
			
		||||
              onTap: () async {
 | 
			
		||||
                if (story.senderId == Matrix.of(context).client.userID) {
 | 
			
		||||
                  await showOkAlertDialog(
 | 
			
		||||
                    context: context,
 | 
			
		||||
                    title: displayname,
 | 
			
		||||
                    message: story.presence.statusMsg,
 | 
			
		||||
                    okLabel: L10n.of(context).close,
 | 
			
		||||
                  );
 | 
			
		||||
                  return;
 | 
			
		||||
                }
 | 
			
		||||
                if (hasStatusMessage) {
 | 
			
		||||
                  if (OkCancelResult.ok !=
 | 
			
		||||
                      await showOkCancelAlertDialog(
 | 
			
		||||
                        context: context,
 | 
			
		||||
                        title: displayname,
 | 
			
		||||
                        message: story.presence.statusMsg,
 | 
			
		||||
                        okLabel: L10n.of(context).sendAMessage,
 | 
			
		||||
                        cancelLabel: L10n.of(context).close,
 | 
			
		||||
                      )) {
 | 
			
		||||
                    return;
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
                final roomId = await Matrix.of(context)
 | 
			
		||||
                    .client
 | 
			
		||||
                    .startDirectChat(story.senderId);
 | 
			
		||||
                await AdaptivePageLayout.of(context)
 | 
			
		||||
                    .pushNamedAndRemoveUntilIsFirst('/rooms/${roomId}');
 | 
			
		||||
              },
 | 
			
		||||
              child: Column(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.center,
 | 
			
		||||
                mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
                children: [
 | 
			
		||||
                  Container(
 | 
			
		||||
                    width: Avatar.defaultSize,
 | 
			
		||||
                    height: Avatar.defaultSize,
 | 
			
		||||
                    child: Stack(
 | 
			
		||||
                      children: [
 | 
			
		||||
                        Center(child: Avatar(avatarUrl, displayname)),
 | 
			
		||||
                        Align(
 | 
			
		||||
                          alignment: Alignment.bottomRight,
 | 
			
		||||
                          child: Icon(
 | 
			
		||||
                            Icons.circle,
 | 
			
		||||
                            color: story.color,
 | 
			
		||||
                            size: 12,
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  Padding(
 | 
			
		||||
                    padding:
 | 
			
		||||
                        const EdgeInsets.only(left: 4.0, right: 4.0, top: 4.0),
 | 
			
		||||
                    child: Text(displayname.split(' ').first,
 | 
			
		||||
                        maxLines: 1,
 | 
			
		||||
                        style: TextStyle(
 | 
			
		||||
                          fontWeight: hasStatusMessage ? FontWeight.bold : null,
 | 
			
		||||
                          color: hasStatusMessage
 | 
			
		||||
                              ? Theme.of(context).accentColor
 | 
			
		||||
                              : null,
 | 
			
		||||
                        )),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -5,6 +5,7 @@ import 'package:fluffychat/views/archive.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_details.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_encryption_settings.dart';
 | 
			
		||||
import 'package:fluffychat/views/contacts.dart';
 | 
			
		||||
import 'package:fluffychat/views/discover.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_list.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_permissions_settings.dart';
 | 
			
		||||
@ -61,6 +62,7 @@ class FluffyRoutes {
 | 
			
		||||
    }
 | 
			
		||||
    // Routes IF user is logged in
 | 
			
		||||
    else {
 | 
			
		||||
      final activeRoomId = Matrix.of(context).client.activeRoomId;
 | 
			
		||||
      switch (parts[1]) {
 | 
			
		||||
        case '':
 | 
			
		||||
          return ViewData(
 | 
			
		||||
@ -138,11 +140,18 @@ class FluffyRoutes {
 | 
			
		||||
            leftView: (_) => ChatList(),
 | 
			
		||||
            mainView: (_) => NewPrivateChat(),
 | 
			
		||||
          );
 | 
			
		||||
        case 'contacts':
 | 
			
		||||
          return ViewData(
 | 
			
		||||
            mainView: (_) => Contacts(),
 | 
			
		||||
            emptyView: (_) =>
 | 
			
		||||
                activeRoomId != null ? Chat(activeRoomId) : EmptyPage(),
 | 
			
		||||
          );
 | 
			
		||||
        case 'discover':
 | 
			
		||||
          if (parts.length == 3) {
 | 
			
		||||
            return ViewData(
 | 
			
		||||
              mainView: (_) => Discover(alias: parts[2]),
 | 
			
		||||
              emptyView: (_) => EmptyPage(),
 | 
			
		||||
              emptyView: (_) =>
 | 
			
		||||
                  activeRoomId != null ? Chat(activeRoomId) : EmptyPage(),
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          return ViewData(
 | 
			
		||||
@ -192,12 +201,14 @@ class FluffyRoutes {
 | 
			
		||||
          } else {
 | 
			
		||||
            return ViewData(
 | 
			
		||||
              mainView: (_) => Settings(),
 | 
			
		||||
              emptyView: (_) => EmptyPage(),
 | 
			
		||||
              emptyView: (_) =>
 | 
			
		||||
                  activeRoomId != null ? Chat(activeRoomId) : EmptyPage(),
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          return ViewData(
 | 
			
		||||
            mainView: (_) => ChatList(),
 | 
			
		||||
            emptyView: (_) => EmptyPage(),
 | 
			
		||||
            emptyView: (_) =>
 | 
			
		||||
                activeRoomId != null ? Chat(activeRoomId) : EmptyPage(),
 | 
			
		||||
          );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,13 @@ class App extends StatelessWidget {
 | 
			
		||||
                dividerColor: Theme.of(context).dividerColor,
 | 
			
		||||
                columnWidth: FluffyThemes.columnWidth,
 | 
			
		||||
                routeBuilder: (builder, settings) =>
 | 
			
		||||
                    Matrix.of(context).loginState == LoginState.logged
 | 
			
		||||
                    Matrix.of(context).loginState == LoginState.logged &&
 | 
			
		||||
                            !{
 | 
			
		||||
                              '/',
 | 
			
		||||
                              '/discover',
 | 
			
		||||
                              '/contacts',
 | 
			
		||||
                              '/settings',
 | 
			
		||||
                            }.contains(settings.name)
 | 
			
		||||
                        ? CupertinoPageRoute(builder: builder)
 | 
			
		||||
                        : FadeRoute(page: builder(context)),
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
@ -4,20 +4,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
import 'date_time_extension.dart';
 | 
			
		||||
 | 
			
		||||
extension on PresenceType {
 | 
			
		||||
  String getLocalized(BuildContext context) {
 | 
			
		||||
    switch (this) {
 | 
			
		||||
      case PresenceType.online:
 | 
			
		||||
        return L10n.of(context).online;
 | 
			
		||||
      case PresenceType.unavailable:
 | 
			
		||||
        return L10n.of(context).unavailable;
 | 
			
		||||
      case PresenceType.offline:
 | 
			
		||||
      default:
 | 
			
		||||
        return L10n.of(context).offline;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension PresenceExtension on Presence {
 | 
			
		||||
  String getLocalizedLastActiveAgo(BuildContext context) {
 | 
			
		||||
    if (presence.lastActiveAgo != null && presence.lastActiveAgo != 0) {
 | 
			
		||||
@ -35,7 +21,7 @@ extension PresenceExtension on Presence {
 | 
			
		||||
    if (presence.currentlyActive ?? false) {
 | 
			
		||||
      return L10n.of(context).currentlyActive;
 | 
			
		||||
    }
 | 
			
		||||
    return presence.presence.getLocalized(context);
 | 
			
		||||
    return getLocalizedLastActiveAgo(context);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Color get color {
 | 
			
		||||
 | 
			
		||||
@ -6,9 +6,8 @@ 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/horizontal_stories_list.dart';
 | 
			
		||||
import 'package:fluffychat/components/default_bottom_navigation_bar.dart';
 | 
			
		||||
import 'package:fluffychat/components/list_items/chat_list_item.dart';
 | 
			
		||||
import 'package:fluffychat/utils/fluffy_share.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:fluffychat/app_config.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
@ -105,51 +104,6 @@ class _ChatListState extends State<ChatList> {
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _onPopupMenuButtonSelect(ChatListPopupMenuItemActions action) {
 | 
			
		||||
    switch (action) {
 | 
			
		||||
      case ChatListPopupMenuItemActions.createGroup:
 | 
			
		||||
        AdaptivePageLayout.of(context).pushNamed('/newgroup');
 | 
			
		||||
        break;
 | 
			
		||||
      case ChatListPopupMenuItemActions.discover:
 | 
			
		||||
        AdaptivePageLayout.of(context).pushNamed('/discover');
 | 
			
		||||
        break;
 | 
			
		||||
      case ChatListPopupMenuItemActions.setStatus:
 | 
			
		||||
        _setStatus();
 | 
			
		||||
        break;
 | 
			
		||||
      case ChatListPopupMenuItemActions.inviteContact:
 | 
			
		||||
        FluffyShare.share(
 | 
			
		||||
            L10n.of(context).inviteText(Matrix.of(context).client.userID,
 | 
			
		||||
                'https://matrix.to/#/${Matrix.of(context).client.userID}'),
 | 
			
		||||
            context);
 | 
			
		||||
        break;
 | 
			
		||||
      case ChatListPopupMenuItemActions.settings:
 | 
			
		||||
        AdaptivePageLayout.of(context).pushNamed('/settings');
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _setStatus() async {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: L10n.of(context).setStatus,
 | 
			
		||||
          okLabel: L10n.of(context).ok,
 | 
			
		||||
          cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
        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 _toggleSelection(String roomId) {
 | 
			
		||||
    setState(() => _selectedRoomIds.contains(roomId)
 | 
			
		||||
        ? _selectedRoomIds.remove(roomId)
 | 
			
		||||
@ -231,6 +185,7 @@ class _ChatListState extends State<ChatList> {
 | 
			
		||||
          return Scaffold(
 | 
			
		||||
            appBar: appBar ??
 | 
			
		||||
                AppBar(
 | 
			
		||||
                  elevation: 1,
 | 
			
		||||
                  leading: selectMode == SelectMode.normal
 | 
			
		||||
                      ? null
 | 
			
		||||
                      : IconButton(
 | 
			
		||||
@ -299,66 +254,6 @@ class _ChatListState extends State<ChatList> {
 | 
			
		||||
                                  );
 | 
			
		||||
                                },
 | 
			
		||||
                              ),
 | 
			
		||||
                              PopupMenuButton<ChatListPopupMenuItemActions>(
 | 
			
		||||
                                onSelected: _onPopupMenuButtonSelect,
 | 
			
		||||
                                itemBuilder: (_) => [
 | 
			
		||||
                                  PopupMenuItem(
 | 
			
		||||
                                    value: ChatListPopupMenuItemActions
 | 
			
		||||
                                        .createGroup,
 | 
			
		||||
                                    child: Row(
 | 
			
		||||
                                      children: [
 | 
			
		||||
                                        Icon(Icons.group_add_outlined),
 | 
			
		||||
                                        SizedBox(width: 12),
 | 
			
		||||
                                        Text(L10n.of(context).createNewGroup),
 | 
			
		||||
                                      ],
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  PopupMenuItem(
 | 
			
		||||
                                    value:
 | 
			
		||||
                                        ChatListPopupMenuItemActions.discover,
 | 
			
		||||
                                    child: Row(
 | 
			
		||||
                                      children: [
 | 
			
		||||
                                        Icon(Icons.group_work_outlined),
 | 
			
		||||
                                        SizedBox(width: 12),
 | 
			
		||||
                                        Text(L10n.of(context).discoverGroups),
 | 
			
		||||
                                      ],
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  PopupMenuItem(
 | 
			
		||||
                                    value:
 | 
			
		||||
                                        ChatListPopupMenuItemActions.setStatus,
 | 
			
		||||
                                    child: Row(
 | 
			
		||||
                                      children: [
 | 
			
		||||
                                        Icon(Icons.edit_outlined),
 | 
			
		||||
                                        SizedBox(width: 12),
 | 
			
		||||
                                        Text(L10n.of(context).setStatus),
 | 
			
		||||
                                      ],
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  PopupMenuItem(
 | 
			
		||||
                                    value: ChatListPopupMenuItemActions
 | 
			
		||||
                                        .inviteContact,
 | 
			
		||||
                                    child: Row(
 | 
			
		||||
                                      children: [
 | 
			
		||||
                                        Icon(Icons.share_outlined),
 | 
			
		||||
                                        SizedBox(width: 12),
 | 
			
		||||
                                        Text(L10n.of(context).inviteContact),
 | 
			
		||||
                                      ],
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  PopupMenuItem(
 | 
			
		||||
                                    value:
 | 
			
		||||
                                        ChatListPopupMenuItemActions.settings,
 | 
			
		||||
                                    child: Row(
 | 
			
		||||
                                      children: [
 | 
			
		||||
                                        Icon(Icons.settings_outlined),
 | 
			
		||||
                                        SizedBox(width: 12),
 | 
			
		||||
                                        Text(L10n.of(context).settings),
 | 
			
		||||
                                      ],
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ],
 | 
			
		||||
                              ),
 | 
			
		||||
                            ],
 | 
			
		||||
                  title: Text(selectMode == SelectMode.share
 | 
			
		||||
                      ? L10n.of(context).share
 | 
			
		||||
@ -422,32 +317,16 @@ class _ChatListState extends State<ChatList> {
 | 
			
		||||
                              itemCount: totalCount + 1,
 | 
			
		||||
                              itemBuilder: (BuildContext context, int i) => i ==
 | 
			
		||||
                                      0
 | 
			
		||||
                                  ? Column(
 | 
			
		||||
                                      mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                                      children: [
 | 
			
		||||
                                        Padding(
 | 
			
		||||
                                          padding: EdgeInsets.all(12),
 | 
			
		||||
                                          child: DefaultAppBarSearchField(
 | 
			
		||||
                                            key: _searchFieldKey,
 | 
			
		||||
                                            hintText: L10n.of(context).search,
 | 
			
		||||
                                            prefixIcon:
 | 
			
		||||
                                                Icon(Icons.search_outlined),
 | 
			
		||||
                                            searchController: searchController,
 | 
			
		||||
                                            onChanged: (_) =>
 | 
			
		||||
                                                setState(() => null),
 | 
			
		||||
                                            padding: EdgeInsets.zero,
 | 
			
		||||
                                          ),
 | 
			
		||||
                                        ),
 | 
			
		||||
                                        if (selectMode == SelectMode.normal)
 | 
			
		||||
                                          Padding(
 | 
			
		||||
                                            padding:
 | 
			
		||||
                                                const EdgeInsets.only(top: 4.0),
 | 
			
		||||
                                            child: HorizontalStoriesList(
 | 
			
		||||
                                              searchQuery:
 | 
			
		||||
                                                  searchController.text,
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          ),
 | 
			
		||||
                                      ],
 | 
			
		||||
                                  ? Padding(
 | 
			
		||||
                                      padding: EdgeInsets.all(12),
 | 
			
		||||
                                      child: DefaultAppBarSearchField(
 | 
			
		||||
                                        key: _searchFieldKey,
 | 
			
		||||
                                        hintText: L10n.of(context).search,
 | 
			
		||||
                                        prefixIcon: Icon(Icons.search_outlined),
 | 
			
		||||
                                        searchController: searchController,
 | 
			
		||||
                                        onChanged: (_) => setState(() => null),
 | 
			
		||||
                                        padding: EdgeInsets.zero,
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    )
 | 
			
		||||
                                  : ChatListItem(
 | 
			
		||||
                                      rooms[i - 1],
 | 
			
		||||
@ -473,11 +352,18 @@ class _ChatListState extends State<ChatList> {
 | 
			
		||||
                    }),
 | 
			
		||||
              ),
 | 
			
		||||
            ]),
 | 
			
		||||
            floatingActionButton: FloatingActionButton(
 | 
			
		||||
              child: Icon(Icons.add_outlined),
 | 
			
		||||
              onPressed: () => AdaptivePageLayout.of(context)
 | 
			
		||||
                  .pushNamedAndRemoveUntilIsFirst('/newprivatechat'),
 | 
			
		||||
            ),
 | 
			
		||||
            floatingActionButton: selectMode == SelectMode.normal
 | 
			
		||||
                ? FloatingActionButton(
 | 
			
		||||
                    child: Icon(Icons.add_outlined),
 | 
			
		||||
                    onPressed: () => AdaptivePageLayout.of(context)
 | 
			
		||||
                        .pushNamedAndRemoveUntilIsFirst('/newprivatechat'),
 | 
			
		||||
                  )
 | 
			
		||||
                : null,
 | 
			
		||||
            floatingActionButtonLocation:
 | 
			
		||||
                FloatingActionButtonLocation.centerDocked,
 | 
			
		||||
            bottomNavigationBar: selectMode == SelectMode.normal
 | 
			
		||||
                ? DefaultBottomNavigationBar(currentIndex: 1)
 | 
			
		||||
                : null,
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										204
									
								
								lib/views/contacts.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								lib/views/contacts.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,204 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/components/avatar.dart';
 | 
			
		||||
import 'package:fluffychat/components/default_app_bar_search_field.dart';
 | 
			
		||||
import 'package:fluffychat/components/default_bottom_navigation_bar.dart';
 | 
			
		||||
import 'package:fluffychat/components/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/utils/fluffy_share.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import '../utils/client_presence_extension.dart';
 | 
			
		||||
import '../utils/presence_extension.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
 | 
			
		||||
class Contacts extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  _ContactsState createState() => _ContactsState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ContactsState extends State<Contacts> {
 | 
			
		||||
  StreamSubscription _onSync;
 | 
			
		||||
  final TextEditingController _controller = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _onSync?.cancel();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  DateTime _lastSetState = DateTime.now();
 | 
			
		||||
  Timer _coolDown;
 | 
			
		||||
 | 
			
		||||
  void _updateView() {
 | 
			
		||||
    _lastSetState = DateTime.now();
 | 
			
		||||
    setState(() => null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _setStatus(BuildContext context) async {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        title: L10n.of(context).setStatus,
 | 
			
		||||
        okLabel: L10n.of(context).ok,
 | 
			
		||||
        cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
        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,
 | 
			
		||||
          ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    _onSync ??= Matrix.of(context).client.onSync.stream.listen((_) {
 | 
			
		||||
      if (DateTime.now().millisecondsSinceEpoch -
 | 
			
		||||
              _lastSetState.millisecondsSinceEpoch <
 | 
			
		||||
          1000) {
 | 
			
		||||
        _coolDown?.cancel();
 | 
			
		||||
        _coolDown = Timer(Duration(seconds: 1), _updateView);
 | 
			
		||||
      } else {
 | 
			
		||||
        _updateView();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    final contactList = Matrix.of(context)
 | 
			
		||||
        .client
 | 
			
		||||
        .contactList
 | 
			
		||||
        .where((p) =>
 | 
			
		||||
            p.senderId.toLowerCase().contains(_controller.text.toLowerCase()))
 | 
			
		||||
        .toList();
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        automaticallyImplyLeading: false,
 | 
			
		||||
        elevation: 1,
 | 
			
		||||
        title: Text(L10n.of(context).contacts),
 | 
			
		||||
        actions: [
 | 
			
		||||
          TextButton.icon(
 | 
			
		||||
            label: Text(
 | 
			
		||||
              L10n.of(context).status,
 | 
			
		||||
              style: TextStyle(color: Theme.of(context).accentColor),
 | 
			
		||||
            ),
 | 
			
		||||
            icon:
 | 
			
		||||
                Icon(Icons.edit_outlined, color: Theme.of(context).accentColor),
 | 
			
		||||
            onPressed: () => _setStatus(context),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
      body: ListView.builder(
 | 
			
		||||
        itemCount: contactList.length + 1,
 | 
			
		||||
        itemBuilder: (_, i) => i == 0
 | 
			
		||||
            ? Column(
 | 
			
		||||
                mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                children: [
 | 
			
		||||
                  Padding(
 | 
			
		||||
                    padding: EdgeInsets.all(12),
 | 
			
		||||
                    child: DefaultAppBarSearchField(
 | 
			
		||||
                      hintText: L10n.of(context).search,
 | 
			
		||||
                      prefixIcon: Icon(Icons.search_outlined),
 | 
			
		||||
                      searchController: _controller,
 | 
			
		||||
                      onChanged: (_) => setState(() => null),
 | 
			
		||||
                      padding: EdgeInsets.zero,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  ListTile(
 | 
			
		||||
                    leading: CircleAvatar(
 | 
			
		||||
                      radius: Avatar.defaultSize / 2,
 | 
			
		||||
                      child: Icon(Icons.person_add_outlined),
 | 
			
		||||
                      backgroundColor: Theme.of(context).primaryColor,
 | 
			
		||||
                      foregroundColor: Colors.white,
 | 
			
		||||
                    ),
 | 
			
		||||
                    title: Text(L10n.of(context).addNewContact),
 | 
			
		||||
                    onTap: () => AdaptivePageLayout.of(context)
 | 
			
		||||
                        .pushNamed('/newprivatechat'),
 | 
			
		||||
                  ),
 | 
			
		||||
                  Divider(height: 1),
 | 
			
		||||
                  if (contactList.isEmpty)
 | 
			
		||||
                    Column(
 | 
			
		||||
                      mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
                      mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                      children: <Widget>[
 | 
			
		||||
                        SizedBox(height: 16),
 | 
			
		||||
                        Icon(
 | 
			
		||||
                          Icons.share_outlined,
 | 
			
		||||
                          size: 80,
 | 
			
		||||
                          color: Colors.grey,
 | 
			
		||||
                        ),
 | 
			
		||||
                        Center(
 | 
			
		||||
                          child: RaisedButton(
 | 
			
		||||
                            elevation: 7,
 | 
			
		||||
                            color: Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                            shape: RoundedRectangleBorder(
 | 
			
		||||
                              borderRadius: BorderRadius.circular(6),
 | 
			
		||||
                            ),
 | 
			
		||||
                            child: Text(
 | 
			
		||||
                              L10n.of(context).inviteContact,
 | 
			
		||||
                              style: TextStyle(
 | 
			
		||||
                                  color: Theme.of(context).accentColor),
 | 
			
		||||
                            ),
 | 
			
		||||
                            onPressed: () => FluffyShare.share(
 | 
			
		||||
                                L10n.of(context).inviteText(
 | 
			
		||||
                                    Matrix.of(context).client.userID,
 | 
			
		||||
                                    'https://matrix.to/#/${Matrix.of(context).client.userID}'),
 | 
			
		||||
                                context),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                ],
 | 
			
		||||
              )
 | 
			
		||||
            : _ContactListTile(contact: contactList[i - 1]),
 | 
			
		||||
      ),
 | 
			
		||||
      bottomNavigationBar: DefaultBottomNavigationBar(currentIndex: 0),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ContactListTile extends StatelessWidget {
 | 
			
		||||
  final Presence contact;
 | 
			
		||||
 | 
			
		||||
  const _ContactListTile({Key key, @required this.contact}) : super(key: key);
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    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: Container(
 | 
			
		||||
              width: Avatar.defaultSize,
 | 
			
		||||
              height: Avatar.defaultSize,
 | 
			
		||||
              child: Stack(
 | 
			
		||||
                children: [
 | 
			
		||||
                  Center(child: Avatar(avatarUrl, displayname)),
 | 
			
		||||
                  Align(
 | 
			
		||||
                    alignment: Alignment.bottomRight,
 | 
			
		||||
                    child: Icon(
 | 
			
		||||
                      Icons.circle,
 | 
			
		||||
                      color: contact.color,
 | 
			
		||||
                      size: 12,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            title: Text(displayname),
 | 
			
		||||
            subtitle: Text(contact.getLocalizedStatusMessage(context)),
 | 
			
		||||
            onTap: () => AdaptivePageLayout.of(context).pushNamed(
 | 
			
		||||
                '/rooms/${Matrix.of(context).client.getDirectChatFromUserId(contact.senderId)}'),
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -5,6 +5,7 @@ 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:fluffychat/components/default_bottom_navigation_bar.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/components/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -148,12 +149,18 @@ class _DiscoverState extends State<Discover> {
 | 
			
		||||
    });
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        elevation: 1,
 | 
			
		||||
        automaticallyImplyLeading: false,
 | 
			
		||||
        title: Text(L10n.of(context).discoverGroups),
 | 
			
		||||
        actions: [
 | 
			
		||||
          FlatButton(
 | 
			
		||||
            child: Text(
 | 
			
		||||
          TextButton.icon(
 | 
			
		||||
            label: Text(
 | 
			
		||||
              server ?? Matrix.of(context).client.userID.domain,
 | 
			
		||||
              style: TextStyle(color: Theme.of(context).primaryColor),
 | 
			
		||||
              style: TextStyle(color: Theme.of(context).accentColor),
 | 
			
		||||
            ),
 | 
			
		||||
            icon: Icon(
 | 
			
		||||
              Icons.edit_outlined,
 | 
			
		||||
              color: Theme.of(context).accentColor,
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: () => _setServer(context),
 | 
			
		||||
          ),
 | 
			
		||||
@ -287,6 +294,7 @@ class _DiscoverState extends State<Discover> {
 | 
			
		||||
              }),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
      bottomNavigationBar: DefaultBottomNavigationBar(currentIndex: 2),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/components/default_bottom_navigation_bar.dart';
 | 
			
		||||
import 'package:fluffychat/components/dialogs/bootstrap_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/components/sentry_switch_list_tile.dart';
 | 
			
		||||
import 'package:fluffychat/components/settings_switch_list_tile.dart';
 | 
			
		||||
@ -349,8 +350,8 @@ class _SettingsState extends State<Settings> {
 | 
			
		||||
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) =>
 | 
			
		||||
            <Widget>[
 | 
			
		||||
          SliverAppBar(
 | 
			
		||||
            elevation: Theme.of(context).appBarTheme.elevation,
 | 
			
		||||
            leading: BackButton(),
 | 
			
		||||
            elevation: 1,
 | 
			
		||||
            automaticallyImplyLeading: false,
 | 
			
		||||
            expandedHeight: 300.0,
 | 
			
		||||
            floating: true,
 | 
			
		||||
            pinned: true,
 | 
			
		||||
@ -578,6 +579,7 @@ class _SettingsState extends State<Settings> {
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      bottomNavigationBar: DefaultBottomNavigationBar(currentIndex: 3),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user