mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +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.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/contacts.dart';
 | 
				
			||||||
import 'package:fluffychat/views/discover.dart';
 | 
					import 'package:fluffychat/views/discover.dart';
 | 
				
			||||||
import 'package:fluffychat/views/chat_list.dart';
 | 
					import 'package:fluffychat/views/chat_list.dart';
 | 
				
			||||||
import 'package:fluffychat/views/chat_permissions_settings.dart';
 | 
					import 'package:fluffychat/views/chat_permissions_settings.dart';
 | 
				
			||||||
@ -61,6 +62,7 @@ class FluffyRoutes {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    // Routes IF user is logged in
 | 
					    // Routes IF user is logged in
 | 
				
			||||||
    else {
 | 
					    else {
 | 
				
			||||||
 | 
					      final activeRoomId = Matrix.of(context).client.activeRoomId;
 | 
				
			||||||
      switch (parts[1]) {
 | 
					      switch (parts[1]) {
 | 
				
			||||||
        case '':
 | 
					        case '':
 | 
				
			||||||
          return ViewData(
 | 
					          return ViewData(
 | 
				
			||||||
@ -138,11 +140,18 @@ class FluffyRoutes {
 | 
				
			|||||||
            leftView: (_) => ChatList(),
 | 
					            leftView: (_) => ChatList(),
 | 
				
			||||||
            mainView: (_) => NewPrivateChat(),
 | 
					            mainView: (_) => NewPrivateChat(),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
 | 
					        case 'contacts':
 | 
				
			||||||
 | 
					          return ViewData(
 | 
				
			||||||
 | 
					            mainView: (_) => Contacts(),
 | 
				
			||||||
 | 
					            emptyView: (_) =>
 | 
				
			||||||
 | 
					                activeRoomId != null ? Chat(activeRoomId) : EmptyPage(),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
        case 'discover':
 | 
					        case 'discover':
 | 
				
			||||||
          if (parts.length == 3) {
 | 
					          if (parts.length == 3) {
 | 
				
			||||||
            return ViewData(
 | 
					            return ViewData(
 | 
				
			||||||
              mainView: (_) => Discover(alias: parts[2]),
 | 
					              mainView: (_) => Discover(alias: parts[2]),
 | 
				
			||||||
              emptyView: (_) => EmptyPage(),
 | 
					              emptyView: (_) =>
 | 
				
			||||||
 | 
					                  activeRoomId != null ? Chat(activeRoomId) : EmptyPage(),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return ViewData(
 | 
					          return ViewData(
 | 
				
			||||||
@ -192,12 +201,14 @@ class FluffyRoutes {
 | 
				
			|||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            return ViewData(
 | 
					            return ViewData(
 | 
				
			||||||
              mainView: (_) => Settings(),
 | 
					              mainView: (_) => Settings(),
 | 
				
			||||||
              emptyView: (_) => EmptyPage(),
 | 
					              emptyView: (_) =>
 | 
				
			||||||
 | 
					                  activeRoomId != null ? Chat(activeRoomId) : EmptyPage(),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return ViewData(
 | 
					          return ViewData(
 | 
				
			||||||
            mainView: (_) => ChatList(),
 | 
					            mainView: (_) => ChatList(),
 | 
				
			||||||
            emptyView: (_) => EmptyPage(),
 | 
					            emptyView: (_) =>
 | 
				
			||||||
 | 
					                activeRoomId != null ? Chat(activeRoomId) : EmptyPage(),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -78,7 +78,13 @@ class App extends StatelessWidget {
 | 
				
			|||||||
                dividerColor: Theme.of(context).dividerColor,
 | 
					                dividerColor: Theme.of(context).dividerColor,
 | 
				
			||||||
                columnWidth: FluffyThemes.columnWidth,
 | 
					                columnWidth: FluffyThemes.columnWidth,
 | 
				
			||||||
                routeBuilder: (builder, settings) =>
 | 
					                routeBuilder: (builder, settings) =>
 | 
				
			||||||
                    Matrix.of(context).loginState == LoginState.logged
 | 
					                    Matrix.of(context).loginState == LoginState.logged &&
 | 
				
			||||||
 | 
					                            !{
 | 
				
			||||||
 | 
					                              '/',
 | 
				
			||||||
 | 
					                              '/discover',
 | 
				
			||||||
 | 
					                              '/contacts',
 | 
				
			||||||
 | 
					                              '/settings',
 | 
				
			||||||
 | 
					                            }.contains(settings.name)
 | 
				
			||||||
                        ? CupertinoPageRoute(builder: builder)
 | 
					                        ? CupertinoPageRoute(builder: builder)
 | 
				
			||||||
                        : FadeRoute(page: builder(context)),
 | 
					                        : FadeRoute(page: builder(context)),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
 | 
				
			|||||||
@ -4,20 +4,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import 'date_time_extension.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 {
 | 
					extension PresenceExtension on Presence {
 | 
				
			||||||
  String getLocalizedLastActiveAgo(BuildContext context) {
 | 
					  String getLocalizedLastActiveAgo(BuildContext context) {
 | 
				
			||||||
    if (presence.lastActiveAgo != null && presence.lastActiveAgo != 0) {
 | 
					    if (presence.lastActiveAgo != null && presence.lastActiveAgo != 0) {
 | 
				
			||||||
@ -35,7 +21,7 @@ extension PresenceExtension on Presence {
 | 
				
			|||||||
    if (presence.currentlyActive ?? false) {
 | 
					    if (presence.currentlyActive ?? false) {
 | 
				
			||||||
      return L10n.of(context).currentlyActive;
 | 
					      return L10n.of(context).currentlyActive;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return presence.presence.getLocalized(context);
 | 
					    return getLocalizedLastActiveAgo(context);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Color get color {
 | 
					  Color get color {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,9 +6,8 @@ import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
				
			|||||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
					import 'package:famedlysdk/famedlysdk.dart';
 | 
				
			||||||
import 'package:fluffychat/components/connection_status_header.dart';
 | 
					import 'package:fluffychat/components/connection_status_header.dart';
 | 
				
			||||||
import 'package:fluffychat/components/default_app_bar_search_field.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/components/list_items/chat_list_item.dart';
 | 
				
			||||||
import 'package:fluffychat/utils/fluffy_share.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/cupertino.dart';
 | 
					import 'package:flutter/cupertino.dart';
 | 
				
			||||||
import 'package:fluffychat/app_config.dart';
 | 
					import 'package:fluffychat/app_config.dart';
 | 
				
			||||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
					import 'package:fluffychat/utils/platform_infos.dart';
 | 
				
			||||||
@ -105,51 +104,6 @@ class _ChatListState extends State<ChatList> {
 | 
				
			|||||||
    super.dispose();
 | 
					    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) {
 | 
					  void _toggleSelection(String roomId) {
 | 
				
			||||||
    setState(() => _selectedRoomIds.contains(roomId)
 | 
					    setState(() => _selectedRoomIds.contains(roomId)
 | 
				
			||||||
        ? _selectedRoomIds.remove(roomId)
 | 
					        ? _selectedRoomIds.remove(roomId)
 | 
				
			||||||
@ -231,6 +185,7 @@ class _ChatListState extends State<ChatList> {
 | 
				
			|||||||
          return Scaffold(
 | 
					          return Scaffold(
 | 
				
			||||||
            appBar: appBar ??
 | 
					            appBar: appBar ??
 | 
				
			||||||
                AppBar(
 | 
					                AppBar(
 | 
				
			||||||
 | 
					                  elevation: 1,
 | 
				
			||||||
                  leading: selectMode == SelectMode.normal
 | 
					                  leading: selectMode == SelectMode.normal
 | 
				
			||||||
                      ? null
 | 
					                      ? null
 | 
				
			||||||
                      : IconButton(
 | 
					                      : 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
 | 
					                  title: Text(selectMode == SelectMode.share
 | 
				
			||||||
                      ? L10n.of(context).share
 | 
					                      ? L10n.of(context).share
 | 
				
			||||||
@ -422,32 +317,16 @@ class _ChatListState extends State<ChatList> {
 | 
				
			|||||||
                              itemCount: totalCount + 1,
 | 
					                              itemCount: totalCount + 1,
 | 
				
			||||||
                              itemBuilder: (BuildContext context, int i) => i ==
 | 
					                              itemBuilder: (BuildContext context, int i) => i ==
 | 
				
			||||||
                                      0
 | 
					                                      0
 | 
				
			||||||
                                  ? Column(
 | 
					                                  ? Padding(
 | 
				
			||||||
                                      mainAxisSize: MainAxisSize.min,
 | 
					                                      padding: EdgeInsets.all(12),
 | 
				
			||||||
                                      children: [
 | 
					                                      child: DefaultAppBarSearchField(
 | 
				
			||||||
                                        Padding(
 | 
					                                        key: _searchFieldKey,
 | 
				
			||||||
                                          padding: EdgeInsets.all(12),
 | 
					                                        hintText: L10n.of(context).search,
 | 
				
			||||||
                                          child: DefaultAppBarSearchField(
 | 
					                                        prefixIcon: Icon(Icons.search_outlined),
 | 
				
			||||||
                                            key: _searchFieldKey,
 | 
					                                        searchController: searchController,
 | 
				
			||||||
                                            hintText: L10n.of(context).search,
 | 
					                                        onChanged: (_) => setState(() => null),
 | 
				
			||||||
                                            prefixIcon:
 | 
					                                        padding: EdgeInsets.zero,
 | 
				
			||||||
                                                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,
 | 
					 | 
				
			||||||
                                            ),
 | 
					 | 
				
			||||||
                                          ),
 | 
					 | 
				
			||||||
                                      ],
 | 
					 | 
				
			||||||
                                    )
 | 
					                                    )
 | 
				
			||||||
                                  : ChatListItem(
 | 
					                                  : ChatListItem(
 | 
				
			||||||
                                      rooms[i - 1],
 | 
					                                      rooms[i - 1],
 | 
				
			||||||
@ -473,11 +352,18 @@ class _ChatListState extends State<ChatList> {
 | 
				
			|||||||
                    }),
 | 
					                    }),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ]),
 | 
					            ]),
 | 
				
			||||||
            floatingActionButton: FloatingActionButton(
 | 
					            floatingActionButton: selectMode == SelectMode.normal
 | 
				
			||||||
              child: Icon(Icons.add_outlined),
 | 
					                ? FloatingActionButton(
 | 
				
			||||||
              onPressed: () => AdaptivePageLayout.of(context)
 | 
					                    child: Icon(Icons.add_outlined),
 | 
				
			||||||
                  .pushNamedAndRemoveUntilIsFirst('/newprivatechat'),
 | 
					                    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:famedlysdk/famedlysdk.dart';
 | 
				
			||||||
import 'package:fluffychat/components/avatar.dart';
 | 
					import 'package:fluffychat/components/avatar.dart';
 | 
				
			||||||
import 'package:fluffychat/components/default_app_bar_search_field.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:future_loading_dialog/future_loading_dialog.dart';
 | 
				
			||||||
import 'package:fluffychat/components/matrix.dart';
 | 
					import 'package:fluffychat/components/matrix.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
@ -148,12 +149,18 @@ class _DiscoverState extends State<Discover> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      appBar: AppBar(
 | 
					      appBar: AppBar(
 | 
				
			||||||
 | 
					        elevation: 1,
 | 
				
			||||||
 | 
					        automaticallyImplyLeading: false,
 | 
				
			||||||
        title: Text(L10n.of(context).discoverGroups),
 | 
					        title: Text(L10n.of(context).discoverGroups),
 | 
				
			||||||
        actions: [
 | 
					        actions: [
 | 
				
			||||||
          FlatButton(
 | 
					          TextButton.icon(
 | 
				
			||||||
            child: Text(
 | 
					            label: Text(
 | 
				
			||||||
              server ?? Matrix.of(context).client.userID.domain,
 | 
					              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),
 | 
					            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_dialog/adaptive_dialog.dart';
 | 
				
			||||||
import 'package:adaptive_page_layout/adaptive_page_layout.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/dialogs/bootstrap_dialog.dart';
 | 
				
			||||||
import 'package:fluffychat/components/sentry_switch_list_tile.dart';
 | 
					import 'package:fluffychat/components/sentry_switch_list_tile.dart';
 | 
				
			||||||
import 'package:fluffychat/components/settings_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) =>
 | 
					        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) =>
 | 
				
			||||||
            <Widget>[
 | 
					            <Widget>[
 | 
				
			||||||
          SliverAppBar(
 | 
					          SliverAppBar(
 | 
				
			||||||
            elevation: Theme.of(context).appBarTheme.elevation,
 | 
					            elevation: 1,
 | 
				
			||||||
            leading: BackButton(),
 | 
					            automaticallyImplyLeading: false,
 | 
				
			||||||
            expandedHeight: 300.0,
 | 
					            expandedHeight: 300.0,
 | 
				
			||||||
            floating: true,
 | 
					            floating: true,
 | 
				
			||||||
            pinned: 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