mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	refactor: Folder structure and MVC chat ui
This commit is contained in:
		
							parent
							
								
									1fe5b78ec6
								
							
						
					
					
						commit
						fb618243f5
					
				@ -2,14 +2,37 @@
 | 
			
		||||
 | 
			
		||||
FluffyChat tries to be as minimal as possible even in the code style. We try to keep the code clean, simple and easy to read. The source code of the app is under `/lib` with the main entry point `/lib/main.dart`.
 | 
			
		||||
 | 
			
		||||
### Directory Structure
 | 
			
		||||
### Directory Structure:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
- /lib
 | 
			
		||||
  - /config
 | 
			
		||||
    - app_config.dart
 | 
			
		||||
    - ...Constants, styles and other configurations
 | 
			
		||||
  - /l10n
 | 
			
		||||
    - intl_en.arb
 | 
			
		||||
    - ...Localization files
 | 
			
		||||
  - /models
 | 
			
		||||
    - app_model.dart
 | 
			
		||||
    - ...Data models used in the app
 | 
			
		||||
  - /utils
 | 
			
		||||
    - handy_function.dart
 | 
			
		||||
    - ...Helper functions and extensions
 | 
			
		||||
  - /views
 | 
			
		||||
    - /ui
 | 
			
		||||
      - home_ui.dart
 | 
			
		||||
      - details_ui.dart
 | 
			
		||||
    - /widgets
 | 
			
		||||
      - /dialogs
 | 
			
		||||
        - /ui
 | 
			
		||||
      - /list_items
 | 
			
		||||
        - /ui
 | 
			
		||||
      - /ui
 | 
			
		||||
    - home_view.dart
 | 
			
		||||
    - details_view.dart
 | 
			
		||||
    - ...The views and widgets of the app separated in Controllers and Views
 | 
			
		||||
  - main.dart
 | 
			
		||||
 | 
			
		||||
- `/lib/config/` Constants, styles and other configurations
 | 
			
		||||
- `/lib/controllers/` Controller classes regarding the MVC separation
 | 
			
		||||
- `/lib/l10n/` Localization files wi
 | 
			
		||||
- `/lib/utils/` Helper functions and extensions
 | 
			
		||||
- `/lib/views/` View classes and widgets
 | 
			
		||||
- `/lib/views/widgets/` Reusable Flutter widgets
 | 
			
		||||
 | 
			
		||||
Most of the business model is in the Famedly Matrix Dart SDK. We try to not keep a model inside of the source code but extend it under `/utils`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,31 +1,31 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/archive_controller.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/homeserver_picker_controller.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/invitation_selection_controller.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/sign_up_controller.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/sign_up_password_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/archive.dart';
 | 
			
		||||
import 'package:fluffychat/views/homeserver_picker.dart';
 | 
			
		||||
import 'package:fluffychat/views/invitation_selection.dart';
 | 
			
		||||
import 'package:fluffychat/views/sign_up.dart';
 | 
			
		||||
import 'package:fluffychat/views/sign_up_password.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/chat_details_controller.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/chat_encryption_settings_controller.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/chat_list_controller.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/chat_permissions_settings_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/empty_page.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_details.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_encryption_settings.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_list.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_permissions_settings.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/empty_page_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/loading_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/log_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/login.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/new_group_controller.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/new_private_chat_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/search_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/settings.dart';
 | 
			
		||||
import 'package:fluffychat/views/settings_3pid.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/device_settings_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/settings_emotes.dart';
 | 
			
		||||
import 'package:fluffychat/views/settings_ignore_list.dart';
 | 
			
		||||
import 'package:fluffychat/views/settings_multiple_emotes.dart';
 | 
			
		||||
import 'package:fluffychat/views/settings_notifications.dart';
 | 
			
		||||
import 'package:fluffychat/views/settings_style.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/login_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/new_group.dart';
 | 
			
		||||
import 'package:fluffychat/views/new_private_chat.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/search_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/settings_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/settings_3pid_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/device_settings.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/settings_emotes_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/settings_ignore_list_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/settings_multiple_emotes_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/settings_notifications_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/settings_style_ui.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class FluffyRoutes {
 | 
			
		||||
 | 
			
		||||
@ -1,223 +0,0 @@
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:file_picker_cross/file_picker_cross.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_details.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/utils/matrix_locals.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:image_picker/image_picker.dart';
 | 
			
		||||
 | 
			
		||||
class ChatDetails extends StatefulWidget {
 | 
			
		||||
  final String roomId;
 | 
			
		||||
 | 
			
		||||
  const ChatDetails(this.roomId);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  ChatDetailsController createState() => ChatDetailsController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
  List<User> members;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    members ??=
 | 
			
		||||
        Matrix.of(context).client.getRoomById(widget.roomId).getParticipants();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setDisplaynameAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).changeTheNameOfTheGroup,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          initialText: room.getLocalizedDisplayname(
 | 
			
		||||
            MatrixLocals(
 | 
			
		||||
              L10n.of(context),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
    if (input == null) return;
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.setName(input.single),
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      AdaptivePageLayout.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context).displaynameHasBeenChanged)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setCanonicalAliasAction(context) async {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).setInvitationLink,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: '#localpart:domain',
 | 
			
		||||
          initialText: L10n.of(context).alias.toLowerCase(),
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
    if (input == null) return;
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    final domain = room.client.userID.domain;
 | 
			
		||||
    final canonicalAlias = '%23' + input.single + '%3A' + domain;
 | 
			
		||||
    final aliasEvent = room.getState('m.room.aliases', domain);
 | 
			
		||||
    final aliases =
 | 
			
		||||
        aliasEvent != null ? aliasEvent.content['aliases'] ?? [] : [];
 | 
			
		||||
    if (aliases.indexWhere((s) => s == canonicalAlias) == -1) {
 | 
			
		||||
      final newAliases = List<String>.from(aliases);
 | 
			
		||||
      newAliases.add(canonicalAlias);
 | 
			
		||||
      final response = await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => room.client.requestRoomAliasInformation(canonicalAlias),
 | 
			
		||||
      );
 | 
			
		||||
      if (response.error != null) {
 | 
			
		||||
        final success = await showFutureLoadingDialog(
 | 
			
		||||
          context: context,
 | 
			
		||||
          future: () => room.client.createRoomAlias(canonicalAlias, room.id),
 | 
			
		||||
        );
 | 
			
		||||
        if (success.error != null) return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.client.sendState(room.id, 'm.room.canonical_alias', {
 | 
			
		||||
        'alias': input.single,
 | 
			
		||||
      }),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setTopicAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).setGroupDescription,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: L10n.of(context).setGroupDescription,
 | 
			
		||||
          initialText: room.topic,
 | 
			
		||||
          minLines: 1,
 | 
			
		||||
          maxLines: 4,
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
    if (input == null) return;
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.setDescription(input.single),
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      AdaptivePageLayout.of(context).showSnackBar(SnackBar(
 | 
			
		||||
          content: Text(L10n.of(context).groupDescriptionHasBeenChanged)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setGuestAccessAction(GuestAccess guestAccess) => showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(widget.roomId)
 | 
			
		||||
            .setGuestAccess(guestAccess),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void setHistoryVisibilityAction(HistoryVisibility historyVisibility) =>
 | 
			
		||||
      showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(widget.roomId)
 | 
			
		||||
            .setHistoryVisibility(historyVisibility),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void setJoinRulesAction(JoinRules joinRule) => showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(widget.roomId)
 | 
			
		||||
            .setJoinRules(joinRule),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void goToEmoteSettings() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    // okay, we need to test if there are any emote state events other than the default one
 | 
			
		||||
    // if so, we need to be directed to a selection screen for which pack we want to look at
 | 
			
		||||
    // otherwise, we just open the normal one.
 | 
			
		||||
    if ((room.states['im.ponies.room_emotes'] ?? <String, Event>{})
 | 
			
		||||
        .keys
 | 
			
		||||
        .any((String s) => s.isNotEmpty)) {
 | 
			
		||||
      await AdaptivePageLayout.of(context)
 | 
			
		||||
          .pushNamed('/rooms/${room.id}/emotes');
 | 
			
		||||
    } else {
 | 
			
		||||
      await AdaptivePageLayout.of(context)
 | 
			
		||||
          .pushNamed('/settings/emotes', arguments: {'room': room});
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setAvatarAction() async {
 | 
			
		||||
    MatrixFile file;
 | 
			
		||||
    if (PlatformInfos.isMobile) {
 | 
			
		||||
      final result = await ImagePicker().getImage(
 | 
			
		||||
          source: ImageSource.gallery,
 | 
			
		||||
          imageQuality: 50,
 | 
			
		||||
          maxWidth: 1600,
 | 
			
		||||
          maxHeight: 1600);
 | 
			
		||||
      if (result == null) return;
 | 
			
		||||
      file = MatrixFile(
 | 
			
		||||
        bytes: await result.readAsBytes(),
 | 
			
		||||
        name: result.path,
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      final result = await FilePickerCross.importFromStorage(
 | 
			
		||||
        type: FileTypeCross.image,
 | 
			
		||||
      );
 | 
			
		||||
      if (result == null) return;
 | 
			
		||||
      file = MatrixFile(
 | 
			
		||||
        bytes: result.toUint8List(),
 | 
			
		||||
        name: result.fileName,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.setAvatar(file),
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      AdaptivePageLayout.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context).avatarHasBeenChanged)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void requestMoreMembersAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    final participants = await showFutureLoadingDialog(
 | 
			
		||||
        context: context, future: () => room.requestParticipants());
 | 
			
		||||
    if (participants.error == null) {
 | 
			
		||||
      setState(() => members = participants.result);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => ChatDetailsView(this);
 | 
			
		||||
}
 | 
			
		||||
@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
 | 
			
		||||
import 'matrix_file_extension.dart';
 | 
			
		||||
import '../controllers/image_viewer_controller.dart';
 | 
			
		||||
import '../views/image_viewer.dart';
 | 
			
		||||
 | 
			
		||||
extension LocalizedBody on Event {
 | 
			
		||||
  void openFile(BuildContext context, {bool downloadOnly = false}) async {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/views/archive_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/archive_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
@ -19,5 +19,5 @@ class ArchiveController extends State<Archive> {
 | 
			
		||||
  void forgetAction(int i) => setState(() => archive.removeAt(i));
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => ArchiveView(this);
 | 
			
		||||
  Widget build(BuildContext context) => ArchiveUI(this);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1075
									
								
								lib/views/chat.dart
									
									
									
									
									
								
							
							
						
						
									
										1075
									
								
								lib/views/chat.dart
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,369 +1,223 @@
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/config/app_config.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/chat_details_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/avatar.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/utils/fluffy_share.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/views/widgets/chat_settings_popup_menu.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/content_banner.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/max_width_body.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/list_items/participant_list_item.dart';
 | 
			
		||||
import 'package:file_picker_cross/file_picker_cross.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/chat_details_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/utils/matrix_locals.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:matrix_link_text/link_text.dart';
 | 
			
		||||
import 'package:image_picker/image_picker.dart';
 | 
			
		||||
 | 
			
		||||
import '../utils/url_launcher.dart';
 | 
			
		||||
class ChatDetails extends StatefulWidget {
 | 
			
		||||
  final String roomId;
 | 
			
		||||
 | 
			
		||||
class ChatDetailsView extends StatelessWidget {
 | 
			
		||||
  final ChatDetailsController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatDetailsView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ChatDetails(this.roomId);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final room =
 | 
			
		||||
        Matrix.of(context).client.getRoomById(controller.widget.roomId);
 | 
			
		||||
    if (room == null) {
 | 
			
		||||
      return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          leading: BackButton(),
 | 
			
		||||
          title: Text(L10n.of(context).oopsSomethingWentWrong),
 | 
			
		||||
        ),
 | 
			
		||||
        body: Center(
 | 
			
		||||
          child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
 | 
			
		||||
        ),
 | 
			
		||||
  ChatDetailsController createState() => ChatDetailsController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ChatDetailsController extends State<ChatDetails> {
 | 
			
		||||
  List<User> members;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    members ??=
 | 
			
		||||
        Matrix.of(context).client.getRoomById(widget.roomId).getParticipants();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setDisplaynameAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).changeTheNameOfTheGroup,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          initialText: room.getLocalizedDisplayname(
 | 
			
		||||
            MatrixLocals(
 | 
			
		||||
              L10n.of(context),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
    if (input == null) return;
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.setName(input.single),
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      AdaptivePageLayout.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context).displaynameHasBeenChanged)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setCanonicalAliasAction(context) async {
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).setInvitationLink,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: '#localpart:domain',
 | 
			
		||||
          initialText: L10n.of(context).alias.toLowerCase(),
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
    if (input == null) return;
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    final domain = room.client.userID.domain;
 | 
			
		||||
    final canonicalAlias = '%23' + input.single + '%3A' + domain;
 | 
			
		||||
    final aliasEvent = room.getState('m.room.aliases', domain);
 | 
			
		||||
    final aliases =
 | 
			
		||||
        aliasEvent != null ? aliasEvent.content['aliases'] ?? [] : [];
 | 
			
		||||
    if (aliases.indexWhere((s) => s == canonicalAlias) == -1) {
 | 
			
		||||
      final newAliases = List<String>.from(aliases);
 | 
			
		||||
      newAliases.add(canonicalAlias);
 | 
			
		||||
      final response = await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => room.client.requestRoomAliasInformation(canonicalAlias),
 | 
			
		||||
      );
 | 
			
		||||
      if (response.error != null) {
 | 
			
		||||
        final success = await showFutureLoadingDialog(
 | 
			
		||||
          context: context,
 | 
			
		||||
          future: () => room.client.createRoomAlias(canonicalAlias, room.id),
 | 
			
		||||
        );
 | 
			
		||||
        if (success.error != null) return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.client.sendState(room.id, 'm.room.canonical_alias', {
 | 
			
		||||
        'alias': input.single,
 | 
			
		||||
      }),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setTopicAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    final input = await showTextInputDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      title: L10n.of(context).setGroupDescription,
 | 
			
		||||
      okLabel: L10n.of(context).ok,
 | 
			
		||||
      cancelLabel: L10n.of(context).cancel,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      textFields: [
 | 
			
		||||
        DialogTextField(
 | 
			
		||||
          hintText: L10n.of(context).setGroupDescription,
 | 
			
		||||
          initialText: room.topic,
 | 
			
		||||
          minLines: 1,
 | 
			
		||||
          maxLines: 4,
 | 
			
		||||
        )
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
    if (input == null) return;
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.setDescription(input.single),
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      AdaptivePageLayout.of(context).showSnackBar(SnackBar(
 | 
			
		||||
          content: Text(L10n.of(context).groupDescriptionHasBeenChanged)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setGuestAccessAction(GuestAccess guestAccess) => showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(widget.roomId)
 | 
			
		||||
            .setGuestAccess(guestAccess),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void setHistoryVisibilityAction(HistoryVisibility historyVisibility) =>
 | 
			
		||||
      showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(widget.roomId)
 | 
			
		||||
            .setHistoryVisibility(historyVisibility),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void setJoinRulesAction(JoinRules joinRule) => showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
        future: () => Matrix.of(context)
 | 
			
		||||
            .client
 | 
			
		||||
            .getRoomById(widget.roomId)
 | 
			
		||||
            .setJoinRules(joinRule),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  void goToEmoteSettings() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    // okay, we need to test if there are any emote state events other than the default one
 | 
			
		||||
    // if so, we need to be directed to a selection screen for which pack we want to look at
 | 
			
		||||
    // otherwise, we just open the normal one.
 | 
			
		||||
    if ((room.states['im.ponies.room_emotes'] ?? <String, Event>{})
 | 
			
		||||
        .keys
 | 
			
		||||
        .any((String s) => s.isNotEmpty)) {
 | 
			
		||||
      await AdaptivePageLayout.of(context)
 | 
			
		||||
          .pushNamed('/rooms/${room.id}/emotes');
 | 
			
		||||
    } else {
 | 
			
		||||
      await AdaptivePageLayout.of(context)
 | 
			
		||||
          .pushNamed('/settings/emotes', arguments: {'room': room});
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setAvatarAction() async {
 | 
			
		||||
    MatrixFile file;
 | 
			
		||||
    if (PlatformInfos.isMobile) {
 | 
			
		||||
      final result = await ImagePicker().getImage(
 | 
			
		||||
          source: ImageSource.gallery,
 | 
			
		||||
          imageQuality: 50,
 | 
			
		||||
          maxWidth: 1600,
 | 
			
		||||
          maxHeight: 1600);
 | 
			
		||||
      if (result == null) return;
 | 
			
		||||
      file = MatrixFile(
 | 
			
		||||
        bytes: await result.readAsBytes(),
 | 
			
		||||
        name: result.path,
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      final result = await FilePickerCross.importFromStorage(
 | 
			
		||||
        type: FileTypeCross.image,
 | 
			
		||||
      );
 | 
			
		||||
      if (result == null) return;
 | 
			
		||||
      file = MatrixFile(
 | 
			
		||||
        bytes: result.toUint8List(),
 | 
			
		||||
        name: result.fileName,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
 | 
			
		||||
    controller.members.removeWhere((u) => u.membership == Membership.leave);
 | 
			
		||||
    final actualMembersCount =
 | 
			
		||||
        room.mInvitedMemberCount + room.mJoinedMemberCount;
 | 
			
		||||
    final canRequestMoreMembers =
 | 
			
		||||
        controller.members.length < actualMembersCount;
 | 
			
		||||
    return StreamBuilder(
 | 
			
		||||
        stream: room.onUpdate.stream,
 | 
			
		||||
        builder: (context, snapshot) {
 | 
			
		||||
          return Scaffold(
 | 
			
		||||
            body: NestedScrollView(
 | 
			
		||||
              headerSliverBuilder:
 | 
			
		||||
                  (BuildContext context, bool innerBoxIsScrolled) => <Widget>[
 | 
			
		||||
                SliverAppBar(
 | 
			
		||||
                  elevation: Theme.of(context).appBarTheme.elevation,
 | 
			
		||||
                  leading: BackButton(),
 | 
			
		||||
                  expandedHeight: 300.0,
 | 
			
		||||
                  floating: true,
 | 
			
		||||
                  pinned: true,
 | 
			
		||||
                  actions: <Widget>[
 | 
			
		||||
                    if (room.canonicalAlias?.isNotEmpty ?? false)
 | 
			
		||||
                      IconButton(
 | 
			
		||||
                        tooltip: L10n.of(context).share,
 | 
			
		||||
                        icon: Icon(Icons.share_outlined),
 | 
			
		||||
                        onPressed: () => FluffyShare.share(
 | 
			
		||||
                            AppConfig.inviteLinkPrefix + room.canonicalAlias,
 | 
			
		||||
                            context),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ChatSettingsPopupMenu(room, false)
 | 
			
		||||
                  ],
 | 
			
		||||
                  title: Text(
 | 
			
		||||
                      room.getLocalizedDisplayname(
 | 
			
		||||
                          MatrixLocals(L10n.of(context))),
 | 
			
		||||
                      style: TextStyle(
 | 
			
		||||
                          color: Theme.of(context)
 | 
			
		||||
                              .appBarTheme
 | 
			
		||||
                              .textTheme
 | 
			
		||||
                              .headline6
 | 
			
		||||
                              .color)),
 | 
			
		||||
                  backgroundColor: Theme.of(context).appBarTheme.color,
 | 
			
		||||
                  flexibleSpace: FlexibleSpaceBar(
 | 
			
		||||
                    background: ContentBanner(room.avatar,
 | 
			
		||||
                        onEdit: room.canSendEvent('m.room.avatar')
 | 
			
		||||
                            ? controller.setAvatarAction
 | 
			
		||||
                            : null),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
              body: MaxWidthBody(
 | 
			
		||||
                child: ListView.builder(
 | 
			
		||||
                  itemCount: controller.members.length +
 | 
			
		||||
                      1 +
 | 
			
		||||
                      (canRequestMoreMembers ? 1 : 0),
 | 
			
		||||
                  itemBuilder: (BuildContext context, int i) => i == 0
 | 
			
		||||
                      ? Column(
 | 
			
		||||
                          crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
			
		||||
                          children: <Widget>[
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              leading: room.canSendEvent('m.room.topic')
 | 
			
		||||
                                  ? CircleAvatar(
 | 
			
		||||
                                      backgroundColor: Theme.of(context)
 | 
			
		||||
                                          .scaffoldBackgroundColor,
 | 
			
		||||
                                      foregroundColor: Colors.grey,
 | 
			
		||||
                                      radius: Avatar.defaultSize / 2,
 | 
			
		||||
                                      child: Icon(Icons.edit_outlined),
 | 
			
		||||
                                    )
 | 
			
		||||
                                  : null,
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                  '${L10n.of(context).groupDescription}:',
 | 
			
		||||
                                  style: TextStyle(
 | 
			
		||||
                                      color: Theme.of(context).accentColor,
 | 
			
		||||
                                      fontWeight: FontWeight.bold)),
 | 
			
		||||
                              subtitle: LinkText(
 | 
			
		||||
                                text: room.topic?.isEmpty ?? true
 | 
			
		||||
                                    ? L10n.of(context).addGroupDescription
 | 
			
		||||
                                    : room.topic,
 | 
			
		||||
                                linkStyle: TextStyle(color: Colors.blueAccent),
 | 
			
		||||
                                textStyle: TextStyle(
 | 
			
		||||
                                  fontSize: 14,
 | 
			
		||||
                                  color: Theme.of(context)
 | 
			
		||||
                                      .textTheme
 | 
			
		||||
                                      .bodyText2
 | 
			
		||||
                                      .color,
 | 
			
		||||
                                ),
 | 
			
		||||
                                onLinkTap: (url) =>
 | 
			
		||||
                                    UrlLauncher(context, url).launchUrl(),
 | 
			
		||||
                              ),
 | 
			
		||||
                              onTap: room.canSendEvent('m.room.topic')
 | 
			
		||||
                                  ? controller.setTopicAction
 | 
			
		||||
                                  : null,
 | 
			
		||||
                            ),
 | 
			
		||||
                            Divider(thickness: 1),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                L10n.of(context).settings,
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                  color: Theme.of(context).accentColor,
 | 
			
		||||
                                  fontWeight: FontWeight.bold,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            if (room.canSendEvent('m.room.name'))
 | 
			
		||||
                              ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                  backgroundColor:
 | 
			
		||||
                                      Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                  foregroundColor: Colors.grey,
 | 
			
		||||
                                  child: Icon(Icons.people_outlined),
 | 
			
		||||
                                ),
 | 
			
		||||
                                title: Text(
 | 
			
		||||
                                    L10n.of(context).changeTheNameOfTheGroup),
 | 
			
		||||
                                subtitle: Text(room.getLocalizedDisplayname(
 | 
			
		||||
                                    MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                onTap: controller.setDisplaynameAction,
 | 
			
		||||
                              ),
 | 
			
		||||
                            if (room.canSendEvent('m.room.canonical_alias') &&
 | 
			
		||||
                                room.joinRules == JoinRules.public)
 | 
			
		||||
                              ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                  backgroundColor:
 | 
			
		||||
                                      Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                  foregroundColor: Colors.grey,
 | 
			
		||||
                                  child: Icon(Icons.link_outlined),
 | 
			
		||||
                                ),
 | 
			
		||||
                                onTap: () =>
 | 
			
		||||
                                    controller.setCanonicalAliasAction(context),
 | 
			
		||||
                                title: Text(L10n.of(context).setInvitationLink),
 | 
			
		||||
                                subtitle: Text(
 | 
			
		||||
                                    (room.canonicalAlias?.isNotEmpty ?? false)
 | 
			
		||||
                                        ? room.canonicalAlias
 | 
			
		||||
                                        : L10n.of(context).none),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              leading: CircleAvatar(
 | 
			
		||||
                                backgroundColor:
 | 
			
		||||
                                    Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                foregroundColor: Colors.grey,
 | 
			
		||||
                                child: Icon(Icons.insert_emoticon_outlined),
 | 
			
		||||
                              ),
 | 
			
		||||
                              title: Text(L10n.of(context).emoteSettings),
 | 
			
		||||
                              subtitle: Text(L10n.of(context).setCustomEmotes),
 | 
			
		||||
                              onTap: controller.goToEmoteSettings,
 | 
			
		||||
                            ),
 | 
			
		||||
                            PopupMenuButton(
 | 
			
		||||
                              onSelected: controller.setJoinRulesAction,
 | 
			
		||||
                              itemBuilder: (BuildContext context) =>
 | 
			
		||||
                                  <PopupMenuEntry<JoinRules>>[
 | 
			
		||||
                                if (room.canChangeJoinRules)
 | 
			
		||||
                                  PopupMenuItem<JoinRules>(
 | 
			
		||||
                                    value: JoinRules.public,
 | 
			
		||||
                                    child: Text(JoinRules.public
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                if (room.canChangeJoinRules)
 | 
			
		||||
                                  PopupMenuItem<JoinRules>(
 | 
			
		||||
                                    value: JoinRules.invite,
 | 
			
		||||
                                    child: Text(JoinRules.invite
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                              ],
 | 
			
		||||
                              child: ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                    backgroundColor: Theme.of(context)
 | 
			
		||||
                                        .scaffoldBackgroundColor,
 | 
			
		||||
                                    foregroundColor: Colors.grey,
 | 
			
		||||
                                    child: Icon(Icons.public_outlined)),
 | 
			
		||||
                                title: Text(L10n.of(context)
 | 
			
		||||
                                    .whoIsAllowedToJoinThisGroup),
 | 
			
		||||
                                subtitle: Text(
 | 
			
		||||
                                  room.joinRules.getLocalizedString(
 | 
			
		||||
                                      MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            PopupMenuButton(
 | 
			
		||||
                              onSelected: controller.setHistoryVisibilityAction,
 | 
			
		||||
                              itemBuilder: (BuildContext context) =>
 | 
			
		||||
                                  <PopupMenuEntry<HistoryVisibility>>[
 | 
			
		||||
                                if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                  PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                    value: HistoryVisibility.invited,
 | 
			
		||||
                                    child: Text(HistoryVisibility.invited
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                  PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                    value: HistoryVisibility.joined,
 | 
			
		||||
                                    child: Text(HistoryVisibility.joined
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                  PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                    value: HistoryVisibility.shared,
 | 
			
		||||
                                    child: Text(HistoryVisibility.shared
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                  PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                    value: HistoryVisibility.world_readable,
 | 
			
		||||
                                    child: Text(HistoryVisibility.world_readable
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                              ],
 | 
			
		||||
                              child: ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                  backgroundColor:
 | 
			
		||||
                                      Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                  foregroundColor: Colors.grey,
 | 
			
		||||
                                  child: Icon(Icons.visibility_outlined),
 | 
			
		||||
                                ),
 | 
			
		||||
                                title: Text(L10n.of(context)
 | 
			
		||||
                                    .visibilityOfTheChatHistory),
 | 
			
		||||
                                subtitle: Text(
 | 
			
		||||
                                  room.historyVisibility.getLocalizedString(
 | 
			
		||||
                                      MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            if (room.joinRules == JoinRules.public)
 | 
			
		||||
                              PopupMenuButton(
 | 
			
		||||
                                onSelected: controller.setGuestAccessAction,
 | 
			
		||||
                                itemBuilder: (BuildContext context) =>
 | 
			
		||||
                                    <PopupMenuEntry<GuestAccess>>[
 | 
			
		||||
                                  if (room.canChangeGuestAccess)
 | 
			
		||||
                                    PopupMenuItem<GuestAccess>(
 | 
			
		||||
                                      value: GuestAccess.can_join,
 | 
			
		||||
                                      child: Text(
 | 
			
		||||
                                        GuestAccess.can_join.getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  if (room.canChangeGuestAccess)
 | 
			
		||||
                                    PopupMenuItem<GuestAccess>(
 | 
			
		||||
                                      value: GuestAccess.forbidden,
 | 
			
		||||
                                      child: Text(
 | 
			
		||||
                                        GuestAccess.forbidden
 | 
			
		||||
                                            .getLocalizedString(
 | 
			
		||||
                                                MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                ],
 | 
			
		||||
                                child: ListTile(
 | 
			
		||||
                                  leading: CircleAvatar(
 | 
			
		||||
                                    backgroundColor: Theme.of(context)
 | 
			
		||||
                                        .scaffoldBackgroundColor,
 | 
			
		||||
                                    foregroundColor: Colors.grey,
 | 
			
		||||
                                    child: Icon(Icons.info_outline),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  title: Text(
 | 
			
		||||
                                      L10n.of(context).areGuestsAllowedToJoin),
 | 
			
		||||
                                  subtitle: Text(
 | 
			
		||||
                                    room.guestAccess.getLocalizedString(
 | 
			
		||||
                                        MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              title: Text(L10n.of(context).editChatPermissions),
 | 
			
		||||
                              subtitle: Text(
 | 
			
		||||
                                  L10n.of(context).whoCanPerformWhichAction),
 | 
			
		||||
                              leading: CircleAvatar(
 | 
			
		||||
                                backgroundColor:
 | 
			
		||||
                                    Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                foregroundColor: Colors.grey,
 | 
			
		||||
                                child: Icon(Icons.edit_attributes_outlined),
 | 
			
		||||
                              ),
 | 
			
		||||
                              onTap: () => AdaptivePageLayout.of(context)
 | 
			
		||||
                                  .pushNamed('/rooms/${room.id}/permissions'),
 | 
			
		||||
                            ),
 | 
			
		||||
                            Divider(thickness: 1),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                actualMembersCount > 1
 | 
			
		||||
                                    ? L10n.of(context).countParticipants(
 | 
			
		||||
                                        actualMembersCount.toString())
 | 
			
		||||
                                    : L10n.of(context).emptyChat,
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                  color: Theme.of(context).accentColor,
 | 
			
		||||
                                  fontWeight: FontWeight.bold,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            room.canInvite
 | 
			
		||||
                                ? ListTile(
 | 
			
		||||
                                    title: Text(L10n.of(context).inviteContact),
 | 
			
		||||
                                    leading: CircleAvatar(
 | 
			
		||||
                                      backgroundColor:
 | 
			
		||||
                                          Theme.of(context).primaryColor,
 | 
			
		||||
                                      foregroundColor: Colors.white,
 | 
			
		||||
                                      radius: Avatar.defaultSize / 2,
 | 
			
		||||
                                      child: Icon(Icons.add_outlined),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    onTap: () => AdaptivePageLayout.of(context)
 | 
			
		||||
                                        .pushNamed('/rooms/${room.id}/invite'),
 | 
			
		||||
                                  )
 | 
			
		||||
                                : Container(),
 | 
			
		||||
                          ],
 | 
			
		||||
                        )
 | 
			
		||||
                      : i < controller.members.length + 1
 | 
			
		||||
                          ? ParticipantListItem(controller.members[i - 1])
 | 
			
		||||
                          : ListTile(
 | 
			
		||||
                              title: Text(L10n.of(context)
 | 
			
		||||
                                  .loadCountMoreParticipants(
 | 
			
		||||
                                      (actualMembersCount -
 | 
			
		||||
                                              controller.members.length)
 | 
			
		||||
                                          .toString())),
 | 
			
		||||
                              leading: CircleAvatar(
 | 
			
		||||
                                backgroundColor:
 | 
			
		||||
                                    Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                child: Icon(
 | 
			
		||||
                                  Icons.refresh,
 | 
			
		||||
                                  color: Colors.grey,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                              onTap: controller.requestMoreMembersAction,
 | 
			
		||||
                            ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
    final success = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.setAvatar(file),
 | 
			
		||||
    );
 | 
			
		||||
    if (success.error == null) {
 | 
			
		||||
      AdaptivePageLayout.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context).avatarHasBeenChanged)));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void requestMoreMembersAction() async {
 | 
			
		||||
    final room = Matrix.of(context).client.getRoomById(widget.roomId);
 | 
			
		||||
    final participants = await showFutureLoadingDialog(
 | 
			
		||||
        context: context, future: () => room.requestParticipants());
 | 
			
		||||
    if (participants.error == null) {
 | 
			
		||||
      setState(() => members = participants.result);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => ChatDetailsUI(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import 'package:famedlysdk/encryption.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_encryption_settings_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/chat_encryption_settings_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import '../views/widgets/dialogs/key_verification_dialog.dart';
 | 
			
		||||
import 'widgets/dialogs/key_verification_dialog.dart';
 | 
			
		||||
 | 
			
		||||
class ChatEncryptionSettings extends StatefulWidget {
 | 
			
		||||
  final String id;
 | 
			
		||||
@ -61,5 +61,5 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => ChatEncryptionSettingsView(this);
 | 
			
		||||
  Widget build(BuildContext context) => ChatEncryptionSettingsUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -5,7 +5,7 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/utils/fluffy_share.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_list_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/chat_list_ui.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:fluffychat/config/app_config.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
@ -13,7 +13,7 @@ import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
 | 
			
		||||
import '../views/widgets/matrix.dart';
 | 
			
		||||
import 'widgets/matrix.dart';
 | 
			
		||||
import '../utils/matrix_file_extension.dart';
 | 
			
		||||
import '../utils/url_launcher.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
@ -224,7 +224,7 @@ class ChatListController extends State<ChatList> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => ChatListView(this);
 | 
			
		||||
  Widget build(BuildContext context) => ChatListUI(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum ChatListPopupMenuItemActions {
 | 
			
		||||
@ -2,7 +2,7 @@ import 'dart:developer';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_permissions_settings_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/chat_permissions_settings_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/dialogs/permission_slider_dialog.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
@ -92,5 +92,5 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => ChatPermissionsSettingsView(this);
 | 
			
		||||
  Widget build(BuildContext context) => ChatPermissionsSettingsUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:famedlysdk/encryption/utils/key_verification.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/views/device_settings_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/device_settings_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/dialogs/key_verification_dialog.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
import '../views/widgets/matrix.dart';
 | 
			
		||||
import 'widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class DevicesSettings extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
@ -136,5 +136,5 @@ class DevicesSettingsController extends State<DevicesSettings> {
 | 
			
		||||
    ..sort((a, b) => b.lastSeenTs.compareTo(a.lastSeenTs));
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => DevicesSettingsView(this);
 | 
			
		||||
  Widget build(BuildContext context) => DevicesSettingsUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -2,7 +2,7 @@ import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/views/homeserver_picker_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/homeserver_picker_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/config/app_config.dart';
 | 
			
		||||
import 'package:fluffychat/config/setting_keys.dart';
 | 
			
		||||
@ -130,5 +130,5 @@ class HomeserverPickerController extends State<HomeserverPicker> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => HomeserverPickerView(this);
 | 
			
		||||
  Widget build(BuildContext context) => HomeserverPickerUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'package:fluffychat/views/image_viewer_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/image_viewer_ui.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
@ -38,5 +38,5 @@ class ImageViewerController extends State<ImageViewer> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => ImageViewerView(this);
 | 
			
		||||
  Widget build(BuildContext context) => ImageViewerUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -3,7 +3,7 @@ import 'dart:async';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/views/invitation_selection_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/invitation_selection_ui.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -116,5 +116,5 @@ class InvitationSelectionController extends State<InvitationSelection> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => InvitationSelectionView(this);
 | 
			
		||||
  Widget build(BuildContext context) => InvitationSelectionUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart' as sdk;
 | 
			
		||||
import 'package:fluffychat/views/new_group_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/new_group_ui.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -40,5 +40,5 @@ class NewGroupController extends State<NewGroup> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => NewGroupView(this);
 | 
			
		||||
  Widget build(BuildContext context) => NewGroupUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -3,7 +3,7 @@ import 'dart:async';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/utils/fluffy_share.dart';
 | 
			
		||||
import 'package:fluffychat/views/new_private_chat_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/new_private_chat_ui.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -112,5 +112,5 @@ class NewPrivateChatController extends State<NewPrivateChat> {
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => NewPrivateChatView(this);
 | 
			
		||||
  Widget build(BuildContext context) => NewPrivateChatUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:file_picker_cross/file_picker_cross.dart';
 | 
			
		||||
import 'package:fluffychat/views/sign_up_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/sign_up_ui.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
@ -67,5 +67,5 @@ class SignUpController extends State<SignUp> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => SignUpView(this);
 | 
			
		||||
  Widget build(BuildContext context) => SignUpUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -2,7 +2,7 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/views/sign_up_password_view.dart';
 | 
			
		||||
import 'package:fluffychat/views/ui/sign_up_password_ui.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
@ -127,5 +127,5 @@ class SignUpPasswordController extends State<SignUpPassword> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) => SignUpPasswordView(this);
 | 
			
		||||
  Widget build(BuildContext context) => SignUpPasswordUI(this);
 | 
			
		||||
}
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/archive_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/archive.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/list_items/chat_list_item.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class ArchiveView extends StatelessWidget {
 | 
			
		||||
class ArchiveUI extends StatelessWidget {
 | 
			
		||||
  final ArchiveController controller;
 | 
			
		||||
 | 
			
		||||
  const ArchiveView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ArchiveUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
							
								
								
									
										369
									
								
								lib/views/ui/chat_details_ui.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								lib/views/ui/chat_details_ui.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,369 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/config/app_config.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_details.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/avatar.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/utils/fluffy_share.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/views/widgets/chat_settings_popup_menu.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/content_banner.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/max_width_body.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/list_items/participant_list_item.dart';
 | 
			
		||||
import 'package:fluffychat/utils/matrix_locals.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:matrix_link_text/link_text.dart';
 | 
			
		||||
 | 
			
		||||
import '../../utils/url_launcher.dart';
 | 
			
		||||
 | 
			
		||||
class ChatDetailsUI extends StatelessWidget {
 | 
			
		||||
  final ChatDetailsController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatDetailsUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final room =
 | 
			
		||||
        Matrix.of(context).client.getRoomById(controller.widget.roomId);
 | 
			
		||||
    if (room == null) {
 | 
			
		||||
      return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          leading: BackButton(),
 | 
			
		||||
          title: Text(L10n.of(context).oopsSomethingWentWrong),
 | 
			
		||||
        ),
 | 
			
		||||
        body: Center(
 | 
			
		||||
          child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    controller.members.removeWhere((u) => u.membership == Membership.leave);
 | 
			
		||||
    final actualMembersCount =
 | 
			
		||||
        room.mInvitedMemberCount + room.mJoinedMemberCount;
 | 
			
		||||
    final canRequestMoreMembers =
 | 
			
		||||
        controller.members.length < actualMembersCount;
 | 
			
		||||
    return StreamBuilder(
 | 
			
		||||
        stream: room.onUpdate.stream,
 | 
			
		||||
        builder: (context, snapshot) {
 | 
			
		||||
          return Scaffold(
 | 
			
		||||
            body: NestedScrollView(
 | 
			
		||||
              headerSliverBuilder:
 | 
			
		||||
                  (BuildContext context, bool innerBoxIsScrolled) => <Widget>[
 | 
			
		||||
                SliverAppBar(
 | 
			
		||||
                  elevation: Theme.of(context).appBarTheme.elevation,
 | 
			
		||||
                  leading: BackButton(),
 | 
			
		||||
                  expandedHeight: 300.0,
 | 
			
		||||
                  floating: true,
 | 
			
		||||
                  pinned: true,
 | 
			
		||||
                  actions: <Widget>[
 | 
			
		||||
                    if (room.canonicalAlias?.isNotEmpty ?? false)
 | 
			
		||||
                      IconButton(
 | 
			
		||||
                        tooltip: L10n.of(context).share,
 | 
			
		||||
                        icon: Icon(Icons.share_outlined),
 | 
			
		||||
                        onPressed: () => FluffyShare.share(
 | 
			
		||||
                            AppConfig.inviteLinkPrefix + room.canonicalAlias,
 | 
			
		||||
                            context),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ChatSettingsPopupMenu(room, false)
 | 
			
		||||
                  ],
 | 
			
		||||
                  title: Text(
 | 
			
		||||
                      room.getLocalizedDisplayname(
 | 
			
		||||
                          MatrixLocals(L10n.of(context))),
 | 
			
		||||
                      style: TextStyle(
 | 
			
		||||
                          color: Theme.of(context)
 | 
			
		||||
                              .appBarTheme
 | 
			
		||||
                              .textTheme
 | 
			
		||||
                              .headline6
 | 
			
		||||
                              .color)),
 | 
			
		||||
                  backgroundColor: Theme.of(context).appBarTheme.color,
 | 
			
		||||
                  flexibleSpace: FlexibleSpaceBar(
 | 
			
		||||
                    background: ContentBanner(room.avatar,
 | 
			
		||||
                        onEdit: room.canSendEvent('m.room.avatar')
 | 
			
		||||
                            ? controller.setAvatarAction
 | 
			
		||||
                            : null),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
              body: MaxWidthBody(
 | 
			
		||||
                child: ListView.builder(
 | 
			
		||||
                  itemCount: controller.members.length +
 | 
			
		||||
                      1 +
 | 
			
		||||
                      (canRequestMoreMembers ? 1 : 0),
 | 
			
		||||
                  itemBuilder: (BuildContext context, int i) => i == 0
 | 
			
		||||
                      ? Column(
 | 
			
		||||
                          crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
			
		||||
                          children: <Widget>[
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              leading: room.canSendEvent('m.room.topic')
 | 
			
		||||
                                  ? CircleAvatar(
 | 
			
		||||
                                      backgroundColor: Theme.of(context)
 | 
			
		||||
                                          .scaffoldBackgroundColor,
 | 
			
		||||
                                      foregroundColor: Colors.grey,
 | 
			
		||||
                                      radius: Avatar.defaultSize / 2,
 | 
			
		||||
                                      child: Icon(Icons.edit_outlined),
 | 
			
		||||
                                    )
 | 
			
		||||
                                  : null,
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                  '${L10n.of(context).groupDescription}:',
 | 
			
		||||
                                  style: TextStyle(
 | 
			
		||||
                                      color: Theme.of(context).accentColor,
 | 
			
		||||
                                      fontWeight: FontWeight.bold)),
 | 
			
		||||
                              subtitle: LinkText(
 | 
			
		||||
                                text: room.topic?.isEmpty ?? true
 | 
			
		||||
                                    ? L10n.of(context).addGroupDescription
 | 
			
		||||
                                    : room.topic,
 | 
			
		||||
                                linkStyle: TextStyle(color: Colors.blueAccent),
 | 
			
		||||
                                textStyle: TextStyle(
 | 
			
		||||
                                  fontSize: 14,
 | 
			
		||||
                                  color: Theme.of(context)
 | 
			
		||||
                                      .textTheme
 | 
			
		||||
                                      .bodyText2
 | 
			
		||||
                                      .color,
 | 
			
		||||
                                ),
 | 
			
		||||
                                onLinkTap: (url) =>
 | 
			
		||||
                                    UrlLauncher(context, url).launchUrl(),
 | 
			
		||||
                              ),
 | 
			
		||||
                              onTap: room.canSendEvent('m.room.topic')
 | 
			
		||||
                                  ? controller.setTopicAction
 | 
			
		||||
                                  : null,
 | 
			
		||||
                            ),
 | 
			
		||||
                            Divider(thickness: 1),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                L10n.of(context).settings,
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                  color: Theme.of(context).accentColor,
 | 
			
		||||
                                  fontWeight: FontWeight.bold,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            if (room.canSendEvent('m.room.name'))
 | 
			
		||||
                              ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                  backgroundColor:
 | 
			
		||||
                                      Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                  foregroundColor: Colors.grey,
 | 
			
		||||
                                  child: Icon(Icons.people_outlined),
 | 
			
		||||
                                ),
 | 
			
		||||
                                title: Text(
 | 
			
		||||
                                    L10n.of(context).changeTheNameOfTheGroup),
 | 
			
		||||
                                subtitle: Text(room.getLocalizedDisplayname(
 | 
			
		||||
                                    MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                onTap: controller.setDisplaynameAction,
 | 
			
		||||
                              ),
 | 
			
		||||
                            if (room.canSendEvent('m.room.canonical_alias') &&
 | 
			
		||||
                                room.joinRules == JoinRules.public)
 | 
			
		||||
                              ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                  backgroundColor:
 | 
			
		||||
                                      Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                  foregroundColor: Colors.grey,
 | 
			
		||||
                                  child: Icon(Icons.link_outlined),
 | 
			
		||||
                                ),
 | 
			
		||||
                                onTap: () =>
 | 
			
		||||
                                    controller.setCanonicalAliasAction(context),
 | 
			
		||||
                                title: Text(L10n.of(context).setInvitationLink),
 | 
			
		||||
                                subtitle: Text(
 | 
			
		||||
                                    (room.canonicalAlias?.isNotEmpty ?? false)
 | 
			
		||||
                                        ? room.canonicalAlias
 | 
			
		||||
                                        : L10n.of(context).none),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              leading: CircleAvatar(
 | 
			
		||||
                                backgroundColor:
 | 
			
		||||
                                    Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                foregroundColor: Colors.grey,
 | 
			
		||||
                                child: Icon(Icons.insert_emoticon_outlined),
 | 
			
		||||
                              ),
 | 
			
		||||
                              title: Text(L10n.of(context).emoteSettings),
 | 
			
		||||
                              subtitle: Text(L10n.of(context).setCustomEmotes),
 | 
			
		||||
                              onTap: controller.goToEmoteSettings,
 | 
			
		||||
                            ),
 | 
			
		||||
                            PopupMenuButton(
 | 
			
		||||
                              onSelected: controller.setJoinRulesAction,
 | 
			
		||||
                              itemBuilder: (BuildContext context) =>
 | 
			
		||||
                                  <PopupMenuEntry<JoinRules>>[
 | 
			
		||||
                                if (room.canChangeJoinRules)
 | 
			
		||||
                                  PopupMenuItem<JoinRules>(
 | 
			
		||||
                                    value: JoinRules.public,
 | 
			
		||||
                                    child: Text(JoinRules.public
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                if (room.canChangeJoinRules)
 | 
			
		||||
                                  PopupMenuItem<JoinRules>(
 | 
			
		||||
                                    value: JoinRules.invite,
 | 
			
		||||
                                    child: Text(JoinRules.invite
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                              ],
 | 
			
		||||
                              child: ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                    backgroundColor: Theme.of(context)
 | 
			
		||||
                                        .scaffoldBackgroundColor,
 | 
			
		||||
                                    foregroundColor: Colors.grey,
 | 
			
		||||
                                    child: Icon(Icons.public_outlined)),
 | 
			
		||||
                                title: Text(L10n.of(context)
 | 
			
		||||
                                    .whoIsAllowedToJoinThisGroup),
 | 
			
		||||
                                subtitle: Text(
 | 
			
		||||
                                  room.joinRules.getLocalizedString(
 | 
			
		||||
                                      MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            PopupMenuButton(
 | 
			
		||||
                              onSelected: controller.setHistoryVisibilityAction,
 | 
			
		||||
                              itemBuilder: (BuildContext context) =>
 | 
			
		||||
                                  <PopupMenuEntry<HistoryVisibility>>[
 | 
			
		||||
                                if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                  PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                    value: HistoryVisibility.invited,
 | 
			
		||||
                                    child: Text(HistoryVisibility.invited
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                  PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                    value: HistoryVisibility.joined,
 | 
			
		||||
                                    child: Text(HistoryVisibility.joined
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                  PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                    value: HistoryVisibility.shared,
 | 
			
		||||
                                    child: Text(HistoryVisibility.shared
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                if (room.canChangeHistoryVisibility)
 | 
			
		||||
                                  PopupMenuItem<HistoryVisibility>(
 | 
			
		||||
                                    value: HistoryVisibility.world_readable,
 | 
			
		||||
                                    child: Text(HistoryVisibility.world_readable
 | 
			
		||||
                                        .getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context)))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                              ],
 | 
			
		||||
                              child: ListTile(
 | 
			
		||||
                                leading: CircleAvatar(
 | 
			
		||||
                                  backgroundColor:
 | 
			
		||||
                                      Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                  foregroundColor: Colors.grey,
 | 
			
		||||
                                  child: Icon(Icons.visibility_outlined),
 | 
			
		||||
                                ),
 | 
			
		||||
                                title: Text(L10n.of(context)
 | 
			
		||||
                                    .visibilityOfTheChatHistory),
 | 
			
		||||
                                subtitle: Text(
 | 
			
		||||
                                  room.historyVisibility.getLocalizedString(
 | 
			
		||||
                                      MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            if (room.joinRules == JoinRules.public)
 | 
			
		||||
                              PopupMenuButton(
 | 
			
		||||
                                onSelected: controller.setGuestAccessAction,
 | 
			
		||||
                                itemBuilder: (BuildContext context) =>
 | 
			
		||||
                                    <PopupMenuEntry<GuestAccess>>[
 | 
			
		||||
                                  if (room.canChangeGuestAccess)
 | 
			
		||||
                                    PopupMenuItem<GuestAccess>(
 | 
			
		||||
                                      value: GuestAccess.can_join,
 | 
			
		||||
                                      child: Text(
 | 
			
		||||
                                        GuestAccess.can_join.getLocalizedString(
 | 
			
		||||
                                            MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  if (room.canChangeGuestAccess)
 | 
			
		||||
                                    PopupMenuItem<GuestAccess>(
 | 
			
		||||
                                      value: GuestAccess.forbidden,
 | 
			
		||||
                                      child: Text(
 | 
			
		||||
                                        GuestAccess.forbidden
 | 
			
		||||
                                            .getLocalizedString(
 | 
			
		||||
                                                MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                ],
 | 
			
		||||
                                child: ListTile(
 | 
			
		||||
                                  leading: CircleAvatar(
 | 
			
		||||
                                    backgroundColor: Theme.of(context)
 | 
			
		||||
                                        .scaffoldBackgroundColor,
 | 
			
		||||
                                    foregroundColor: Colors.grey,
 | 
			
		||||
                                    child: Icon(Icons.info_outline),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  title: Text(
 | 
			
		||||
                                      L10n.of(context).areGuestsAllowedToJoin),
 | 
			
		||||
                                  subtitle: Text(
 | 
			
		||||
                                    room.guestAccess.getLocalizedString(
 | 
			
		||||
                                        MatrixLocals(L10n.of(context))),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              title: Text(L10n.of(context).editChatPermissions),
 | 
			
		||||
                              subtitle: Text(
 | 
			
		||||
                                  L10n.of(context).whoCanPerformWhichAction),
 | 
			
		||||
                              leading: CircleAvatar(
 | 
			
		||||
                                backgroundColor:
 | 
			
		||||
                                    Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                foregroundColor: Colors.grey,
 | 
			
		||||
                                child: Icon(Icons.edit_attributes_outlined),
 | 
			
		||||
                              ),
 | 
			
		||||
                              onTap: () => AdaptivePageLayout.of(context)
 | 
			
		||||
                                  .pushNamed('/rooms/${room.id}/permissions'),
 | 
			
		||||
                            ),
 | 
			
		||||
                            Divider(thickness: 1),
 | 
			
		||||
                            ListTile(
 | 
			
		||||
                              title: Text(
 | 
			
		||||
                                actualMembersCount > 1
 | 
			
		||||
                                    ? L10n.of(context).countParticipants(
 | 
			
		||||
                                        actualMembersCount.toString())
 | 
			
		||||
                                    : L10n.of(context).emptyChat,
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                  color: Theme.of(context).accentColor,
 | 
			
		||||
                                  fontWeight: FontWeight.bold,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            room.canInvite
 | 
			
		||||
                                ? ListTile(
 | 
			
		||||
                                    title: Text(L10n.of(context).inviteContact),
 | 
			
		||||
                                    leading: CircleAvatar(
 | 
			
		||||
                                      backgroundColor:
 | 
			
		||||
                                          Theme.of(context).primaryColor,
 | 
			
		||||
                                      foregroundColor: Colors.white,
 | 
			
		||||
                                      radius: Avatar.defaultSize / 2,
 | 
			
		||||
                                      child: Icon(Icons.add_outlined),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    onTap: () => AdaptivePageLayout.of(context)
 | 
			
		||||
                                        .pushNamed('/rooms/${room.id}/invite'),
 | 
			
		||||
                                  )
 | 
			
		||||
                                : Container(),
 | 
			
		||||
                          ],
 | 
			
		||||
                        )
 | 
			
		||||
                      : i < controller.members.length + 1
 | 
			
		||||
                          ? ParticipantListItem(controller.members[i - 1])
 | 
			
		||||
                          : ListTile(
 | 
			
		||||
                              title: Text(L10n.of(context)
 | 
			
		||||
                                  .loadCountMoreParticipants(
 | 
			
		||||
                                      (actualMembersCount -
 | 
			
		||||
                                              controller.members.length)
 | 
			
		||||
                                          .toString())),
 | 
			
		||||
                              leading: CircleAvatar(
 | 
			
		||||
                                backgroundColor:
 | 
			
		||||
                                    Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                                child: Icon(
 | 
			
		||||
                                  Icons.refresh,
 | 
			
		||||
                                  color: Colors.grey,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                              onTap: controller.requestMoreMembersAction,
 | 
			
		||||
                            ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,17 +1,16 @@
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/chat_encryption_settings_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_encryption_settings.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/avatar.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/max_width_body.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import '../utils/device_extension.dart';
 | 
			
		||||
import '../../utils/device_extension.dart';
 | 
			
		||||
 | 
			
		||||
class ChatEncryptionSettingsView extends StatelessWidget {
 | 
			
		||||
class ChatEncryptionSettingsUI extends StatelessWidget {
 | 
			
		||||
  final ChatEncryptionSettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatEncryptionSettingsView(this.controller, {Key key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
  const ChatEncryptionSettingsUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -1,19 +1,19 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/chat_list_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_list.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/connection_status_header.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/list_items/chat_list_item.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:fluffychat/config/app_config.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'widgets/matrix.dart';
 | 
			
		||||
import '../widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class ChatListView extends StatelessWidget {
 | 
			
		||||
class ChatListUI extends StatelessWidget {
 | 
			
		||||
  final ChatListController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatListView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ChatListUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:fluffychat/controllers/chat_permissions_settings_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat_permissions_settings.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/list_items/permission_list_tile.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/max_width_body.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
@ -7,11 +7,10 @@ import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
 | 
			
		||||
class ChatPermissionsSettingsView extends StatelessWidget {
 | 
			
		||||
class ChatPermissionsSettingsUI extends StatelessWidget {
 | 
			
		||||
  final ChatPermissionsSettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatPermissionsSettingsView(this.controller, {Key key})
 | 
			
		||||
      : super(key: key);
 | 
			
		||||
  const ChatPermissionsSettingsUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
							
								
								
									
										747
									
								
								lib/views/ui/chat_ui.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										747
									
								
								lib/views/ui/chat_ui.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,747 @@
 | 
			
		||||
import 'dart:math';
 | 
			
		||||
import 'dart:ui';
 | 
			
		||||
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/views/chat.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/avatar.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/chat_settings_popup_menu.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/connection_status_header.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/input_bar.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/unread_badge_back_button.dart';
 | 
			
		||||
import 'package:fluffychat/config/themes.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/encryption_button.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/list_items/message.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/reply_content.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/user_bottom_sheet.dart';
 | 
			
		||||
import 'package:fluffychat/config/app_emojis.dart';
 | 
			
		||||
import 'package:fluffychat/utils/matrix_locals.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'package:fluffychat/utils/room_status_extension.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:scroll_to_index/scroll_to_index.dart';
 | 
			
		||||
import 'package:swipe_to_action/swipe_to_action.dart';
 | 
			
		||||
 | 
			
		||||
class ChatUI extends StatelessWidget {
 | 
			
		||||
  final ChatController controller;
 | 
			
		||||
 | 
			
		||||
  const ChatUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    controller.matrix = Matrix.of(context);
 | 
			
		||||
    final client = controller.matrix.client;
 | 
			
		||||
    controller.room ??= client.getRoomById(controller.widget.id);
 | 
			
		||||
    if (controller.room == null) {
 | 
			
		||||
      return Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text(L10n.of(context).oopsSomethingWentWrong),
 | 
			
		||||
        ),
 | 
			
		||||
        body: Center(
 | 
			
		||||
          child: Text(L10n.of(context).youAreNoLongerParticipatingInThisChat),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    controller.matrix.client.activeRoomId = controller.widget.id;
 | 
			
		||||
 | 
			
		||||
    if (controller.room.membership == Membership.invite) {
 | 
			
		||||
      showFutureLoadingDialog(
 | 
			
		||||
          context: context, future: () => controller.room.join());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: controller.selectMode
 | 
			
		||||
            ? IconButton(
 | 
			
		||||
                icon: Icon(Icons.close),
 | 
			
		||||
                onPressed: controller.clearSelectedEvents,
 | 
			
		||||
                tooltip: L10n.of(context).close,
 | 
			
		||||
              )
 | 
			
		||||
            : AdaptivePageLayout.of(context).columnMode(context)
 | 
			
		||||
                ? null
 | 
			
		||||
                : UnreadBadgeBackButton(roomId: controller.widget.id),
 | 
			
		||||
        titleSpacing:
 | 
			
		||||
            AdaptivePageLayout.of(context).columnMode(context) ? null : 0,
 | 
			
		||||
        title: controller.selectedEvents.isEmpty
 | 
			
		||||
            ? StreamBuilder(
 | 
			
		||||
                stream: controller.room.onUpdate.stream,
 | 
			
		||||
                builder: (context, snapshot) => ListTile(
 | 
			
		||||
                      leading: Avatar(
 | 
			
		||||
                          controller.room.avatar, controller.room.displayname),
 | 
			
		||||
                      contentPadding: EdgeInsets.zero,
 | 
			
		||||
                      onTap: controller.room.isDirectChat
 | 
			
		||||
                          ? () => showModalBottomSheet(
 | 
			
		||||
                                context: context,
 | 
			
		||||
                                builder: (c) => UserBottomSheet(
 | 
			
		||||
                                  user: controller.room.getUserByMXIDSync(
 | 
			
		||||
                                      controller.room.directChatMatrixID),
 | 
			
		||||
                                  onMention: () => controller
 | 
			
		||||
                                          .sendController.text +=
 | 
			
		||||
                                      '${controller.room.directChatMatrixID} ',
 | 
			
		||||
                                ),
 | 
			
		||||
                              )
 | 
			
		||||
                          : () => (!AdaptivePageLayout.of(context)
 | 
			
		||||
                                      .columnMode(context) ||
 | 
			
		||||
                                  AdaptivePageLayout.of(context)
 | 
			
		||||
                                          .viewDataStack
 | 
			
		||||
                                          .length <
 | 
			
		||||
                                      3)
 | 
			
		||||
                              ? AdaptivePageLayout.of(context).pushNamed(
 | 
			
		||||
                                  '/rooms/${controller.room.id}/details')
 | 
			
		||||
                              : null,
 | 
			
		||||
                      title: Text(
 | 
			
		||||
                          controller.room.getLocalizedDisplayname(
 | 
			
		||||
                              MatrixLocals(L10n.of(context))),
 | 
			
		||||
                          maxLines: 1),
 | 
			
		||||
                      subtitle: controller.room
 | 
			
		||||
                              .getLocalizedTypingText(context)
 | 
			
		||||
                              .isEmpty
 | 
			
		||||
                          ? StreamBuilder<Object>(
 | 
			
		||||
                              stream: Matrix.of(context)
 | 
			
		||||
                                  .client
 | 
			
		||||
                                  .onPresence
 | 
			
		||||
                                  .stream
 | 
			
		||||
                                  .where((p) =>
 | 
			
		||||
                                      p.senderId ==
 | 
			
		||||
                                      controller.room.directChatMatrixID),
 | 
			
		||||
                              builder: (context, snapshot) => Text(
 | 
			
		||||
                                    controller.room.getLocalizedStatus(context),
 | 
			
		||||
                                    maxLines: 1,
 | 
			
		||||
                                    //overflow: TextOverflow.ellipsis,
 | 
			
		||||
                                  ))
 | 
			
		||||
                          : Row(
 | 
			
		||||
                              children: <Widget>[
 | 
			
		||||
                                Icon(Icons.edit_outlined,
 | 
			
		||||
                                    color: Theme.of(context).accentColor,
 | 
			
		||||
                                    size: 13),
 | 
			
		||||
                                SizedBox(width: 4),
 | 
			
		||||
                                Expanded(
 | 
			
		||||
                                  child: Text(
 | 
			
		||||
                                    controller.room
 | 
			
		||||
                                        .getLocalizedTypingText(context),
 | 
			
		||||
                                    maxLines: 1,
 | 
			
		||||
                                    style: TextStyle(
 | 
			
		||||
                                      color: Theme.of(context).accentColor,
 | 
			
		||||
                                      fontStyle: FontStyle.italic,
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ],
 | 
			
		||||
                            ),
 | 
			
		||||
                    ))
 | 
			
		||||
            : Text(L10n.of(context)
 | 
			
		||||
                .numberSelected(controller.selectedEvents.length.toString())),
 | 
			
		||||
        actions: controller.selectMode
 | 
			
		||||
            ? <Widget>[
 | 
			
		||||
                if (controller.selectedEvents.length == 1 &&
 | 
			
		||||
                    controller.selectedEvents.first.status > 0 &&
 | 
			
		||||
                    controller.selectedEvents.first.senderId == client.userID)
 | 
			
		||||
                  IconButton(
 | 
			
		||||
                    icon: Icon(Icons.edit_outlined),
 | 
			
		||||
                    tooltip: L10n.of(context).edit,
 | 
			
		||||
                    onPressed: controller.editSelectedEventAction,
 | 
			
		||||
                  ),
 | 
			
		||||
                PopupMenuButton(
 | 
			
		||||
                  onSelected: controller.onEventActionPopupMenuSelected,
 | 
			
		||||
                  itemBuilder: (_) => [
 | 
			
		||||
                    PopupMenuItem(
 | 
			
		||||
                      value: 'copy',
 | 
			
		||||
                      child: Text(L10n.of(context).copy),
 | 
			
		||||
                    ),
 | 
			
		||||
                    if (controller.canRedactSelectedEvents)
 | 
			
		||||
                      PopupMenuItem(
 | 
			
		||||
                        value: 'redact',
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          L10n.of(context).redactMessage,
 | 
			
		||||
                          style: TextStyle(color: Colors.orange),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    if (controller.selectedEvents.length == 1)
 | 
			
		||||
                      PopupMenuItem(
 | 
			
		||||
                        value: 'report',
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          L10n.of(context).reportMessage,
 | 
			
		||||
                          style: TextStyle(color: Colors.red),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ]
 | 
			
		||||
            : <Widget>[
 | 
			
		||||
                if (controller.room.canSendDefaultStates)
 | 
			
		||||
                  IconButton(
 | 
			
		||||
                    tooltip: L10n.of(context).videoCall,
 | 
			
		||||
                    icon: Icon(Icons.video_call_outlined),
 | 
			
		||||
                    onPressed: controller.startCallAction,
 | 
			
		||||
                  ),
 | 
			
		||||
                ChatSettingsPopupMenu(
 | 
			
		||||
                    controller.room, !controller.room.isDirectChat),
 | 
			
		||||
              ],
 | 
			
		||||
      ),
 | 
			
		||||
      floatingActionButton: controller.showScrollDownButton
 | 
			
		||||
          ? Padding(
 | 
			
		||||
              padding: const EdgeInsets.only(bottom: 56.0),
 | 
			
		||||
              child: FloatingActionButton(
 | 
			
		||||
                onPressed: controller.scrollDown,
 | 
			
		||||
                foregroundColor: Theme.of(context).textTheme.bodyText2.color,
 | 
			
		||||
                backgroundColor: Theme.of(context).scaffoldBackgroundColor,
 | 
			
		||||
                mini: true,
 | 
			
		||||
                child: Icon(Icons.arrow_downward_outlined,
 | 
			
		||||
                    color: Theme.of(context).primaryColor),
 | 
			
		||||
              ),
 | 
			
		||||
            )
 | 
			
		||||
          : null,
 | 
			
		||||
      body: Stack(
 | 
			
		||||
        children: <Widget>[
 | 
			
		||||
          if (Matrix.of(context).wallpaper != null)
 | 
			
		||||
            Image.file(
 | 
			
		||||
              Matrix.of(context).wallpaper,
 | 
			
		||||
              width: double.infinity,
 | 
			
		||||
              height: double.infinity,
 | 
			
		||||
              fit: BoxFit.cover,
 | 
			
		||||
            ),
 | 
			
		||||
          SafeArea(
 | 
			
		||||
            child: Column(
 | 
			
		||||
              children: <Widget>[
 | 
			
		||||
                ConnectionStatusHeader(),
 | 
			
		||||
                if (controller.room.getState(EventTypes.RoomTombstone) != null)
 | 
			
		||||
                  Container(
 | 
			
		||||
                    height: 72,
 | 
			
		||||
                    child: Material(
 | 
			
		||||
                      color: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                      child: ListTile(
 | 
			
		||||
                        leading: CircleAvatar(
 | 
			
		||||
                          foregroundColor: Theme.of(context).accentColor,
 | 
			
		||||
                          backgroundColor: Theme.of(context).backgroundColor,
 | 
			
		||||
                          child: Icon(Icons.upgrade_outlined),
 | 
			
		||||
                        ),
 | 
			
		||||
                        title: Text(
 | 
			
		||||
                          controller.room
 | 
			
		||||
                              .getState(EventTypes.RoomTombstone)
 | 
			
		||||
                              .parsedTombstoneContent
 | 
			
		||||
                              .body,
 | 
			
		||||
                          maxLines: 1,
 | 
			
		||||
                          overflow: TextOverflow.ellipsis,
 | 
			
		||||
                        ),
 | 
			
		||||
                        subtitle: Text(L10n.of(context).goToTheNewRoom),
 | 
			
		||||
                        onTap: controller.goToNewRoomAction,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                Expanded(
 | 
			
		||||
                  child: FutureBuilder<bool>(
 | 
			
		||||
                    future: controller.getTimeline(),
 | 
			
		||||
                    builder: (BuildContext context, snapshot) {
 | 
			
		||||
                      if (!snapshot.hasData) {
 | 
			
		||||
                        return Center(
 | 
			
		||||
                          child: CircularProgressIndicator(),
 | 
			
		||||
                        );
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                      // create a map of eventId --> index to greatly improve performance of
 | 
			
		||||
                      // ListView's findChildIndexCallback
 | 
			
		||||
                      final thisEventsKeyMap = <String, int>{};
 | 
			
		||||
                      for (var i = 0;
 | 
			
		||||
                          i < controller.filteredEvents.length;
 | 
			
		||||
                          i++) {
 | 
			
		||||
                        thisEventsKeyMap[controller.filteredEvents[i].eventId] =
 | 
			
		||||
                            i;
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                      final horizontalPadding = max(
 | 
			
		||||
                              0,
 | 
			
		||||
                              (MediaQuery.of(context).size.width -
 | 
			
		||||
                                      FluffyThemes.columnWidth *
 | 
			
		||||
                                          (AdaptivePageLayout.of(context)
 | 
			
		||||
                                                      .currentViewData
 | 
			
		||||
                                                      .rightView !=
 | 
			
		||||
                                                  null
 | 
			
		||||
                                              ? 4.5
 | 
			
		||||
                                              : 3.5)) /
 | 
			
		||||
                                  2)
 | 
			
		||||
                          .toDouble();
 | 
			
		||||
 | 
			
		||||
                      return ListView.custom(
 | 
			
		||||
                        padding: EdgeInsets.only(
 | 
			
		||||
                          top: 16,
 | 
			
		||||
                          left: horizontalPadding,
 | 
			
		||||
                          right: horizontalPadding,
 | 
			
		||||
                        ),
 | 
			
		||||
                        reverse: true,
 | 
			
		||||
                        controller: controller.scrollController,
 | 
			
		||||
                        childrenDelegate: SliverChildBuilderDelegate(
 | 
			
		||||
                          (BuildContext context, int i) {
 | 
			
		||||
                            return i == controller.filteredEvents.length + 1
 | 
			
		||||
                                ? controller.timeline.isRequestingHistory
 | 
			
		||||
                                    ? Container(
 | 
			
		||||
                                        height: 50,
 | 
			
		||||
                                        alignment: Alignment.center,
 | 
			
		||||
                                        padding: EdgeInsets.all(8),
 | 
			
		||||
                                        child: CircularProgressIndicator(),
 | 
			
		||||
                                      )
 | 
			
		||||
                                    : controller.canLoadMore
 | 
			
		||||
                                        ? TextButton(
 | 
			
		||||
                                            onPressed:
 | 
			
		||||
                                                controller.requestHistory,
 | 
			
		||||
                                            child: Text(
 | 
			
		||||
                                              L10n.of(context).loadMore,
 | 
			
		||||
                                              style: TextStyle(
 | 
			
		||||
                                                color: Theme.of(context)
 | 
			
		||||
                                                    .primaryColor,
 | 
			
		||||
                                                fontWeight: FontWeight.bold,
 | 
			
		||||
                                                decoration:
 | 
			
		||||
                                                    TextDecoration.underline,
 | 
			
		||||
                                              ),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          )
 | 
			
		||||
                                        : Container()
 | 
			
		||||
                                : i == 0
 | 
			
		||||
                                    ? StreamBuilder(
 | 
			
		||||
                                        stream: controller.room.onUpdate.stream,
 | 
			
		||||
                                        builder: (_, __) {
 | 
			
		||||
                                          final seenByText = controller.room
 | 
			
		||||
                                              .getLocalizedSeenByText(
 | 
			
		||||
                                            context,
 | 
			
		||||
                                            controller.timeline,
 | 
			
		||||
                                            controller.filteredEvents,
 | 
			
		||||
                                            controller.unfolded,
 | 
			
		||||
                                          );
 | 
			
		||||
                                          return AnimatedContainer(
 | 
			
		||||
                                            height: seenByText.isEmpty ? 0 : 24,
 | 
			
		||||
                                            duration: seenByText.isEmpty
 | 
			
		||||
                                                ? Duration(milliseconds: 0)
 | 
			
		||||
                                                : Duration(milliseconds: 300),
 | 
			
		||||
                                            alignment: controller.filteredEvents
 | 
			
		||||
                                                        .first.senderId ==
 | 
			
		||||
                                                    client.userID
 | 
			
		||||
                                                ? Alignment.topRight
 | 
			
		||||
                                                : Alignment.topLeft,
 | 
			
		||||
                                            padding: EdgeInsets.only(
 | 
			
		||||
                                              left: 8,
 | 
			
		||||
                                              right: 8,
 | 
			
		||||
                                              bottom: 8,
 | 
			
		||||
                                            ),
 | 
			
		||||
                                            child: Container(
 | 
			
		||||
                                              padding: EdgeInsets.symmetric(
 | 
			
		||||
                                                  horizontal: 4),
 | 
			
		||||
                                              decoration: BoxDecoration(
 | 
			
		||||
                                                color: Theme.of(context)
 | 
			
		||||
                                                    .scaffoldBackgroundColor
 | 
			
		||||
                                                    .withOpacity(0.8),
 | 
			
		||||
                                                borderRadius:
 | 
			
		||||
                                                    BorderRadius.circular(4),
 | 
			
		||||
                                              ),
 | 
			
		||||
                                              child: Text(
 | 
			
		||||
                                                seenByText,
 | 
			
		||||
                                                maxLines: 1,
 | 
			
		||||
                                                overflow: TextOverflow.ellipsis,
 | 
			
		||||
                                                style: TextStyle(
 | 
			
		||||
                                                  color: Theme.of(context)
 | 
			
		||||
                                                      .accentColor,
 | 
			
		||||
                                                ),
 | 
			
		||||
                                              ),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          );
 | 
			
		||||
                                        },
 | 
			
		||||
                                      )
 | 
			
		||||
                                    : AutoScrollTag(
 | 
			
		||||
                                        key: ValueKey(controller
 | 
			
		||||
                                            .filteredEvents[i - 1].eventId),
 | 
			
		||||
                                        index: i - 1,
 | 
			
		||||
                                        controller: controller.scrollController,
 | 
			
		||||
                                        child: Swipeable(
 | 
			
		||||
                                          key: ValueKey(controller
 | 
			
		||||
                                              .filteredEvents[i - 1].eventId),
 | 
			
		||||
                                          background: Padding(
 | 
			
		||||
                                            padding: EdgeInsets.symmetric(
 | 
			
		||||
                                                horizontal: 12.0),
 | 
			
		||||
                                            child: Center(
 | 
			
		||||
                                              child: Icon(Icons.reply_outlined),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          ),
 | 
			
		||||
                                          direction: SwipeDirection.endToStart,
 | 
			
		||||
                                          onSwipe: (direction) =>
 | 
			
		||||
                                              controller.replyAction(
 | 
			
		||||
                                                  replyTo: controller
 | 
			
		||||
                                                      .filteredEvents[i - 1]),
 | 
			
		||||
                                          child: Message(
 | 
			
		||||
                                              controller.filteredEvents[i - 1],
 | 
			
		||||
                                              onAvatarTab: (Event event) =>
 | 
			
		||||
                                                  showModalBottomSheet(
 | 
			
		||||
                                                    context: context,
 | 
			
		||||
                                                    builder: (c) =>
 | 
			
		||||
                                                        UserBottomSheet(
 | 
			
		||||
                                                      user: event.sender,
 | 
			
		||||
                                                      onMention: () => controller
 | 
			
		||||
                                                              .sendController
 | 
			
		||||
                                                              .text +=
 | 
			
		||||
                                                          '${event.senderId} ',
 | 
			
		||||
                                                    ),
 | 
			
		||||
                                                  ),
 | 
			
		||||
                                              unfold: controller.unfold,
 | 
			
		||||
                                              onSelect:
 | 
			
		||||
                                                  controller.onSelectMessage,
 | 
			
		||||
                                              scrollToEventId:
 | 
			
		||||
                                                  (String eventId) => controller
 | 
			
		||||
                                                      .scrollToEventId(eventId),
 | 
			
		||||
                                              longPressSelect: controller
 | 
			
		||||
                                                  .selectedEvents.isEmpty,
 | 
			
		||||
                                              selected: controller
 | 
			
		||||
                                                  .selectedEvents
 | 
			
		||||
                                                  .contains(controller
 | 
			
		||||
                                                      .filteredEvents[i - 1]),
 | 
			
		||||
                                              timeline: controller.timeline,
 | 
			
		||||
                                              nextEvent: i >= 2
 | 
			
		||||
                                                  ? controller
 | 
			
		||||
                                                      .filteredEvents[i - 2]
 | 
			
		||||
                                                  : null),
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      );
 | 
			
		||||
                          },
 | 
			
		||||
                          childCount: controller.filteredEvents.length + 2,
 | 
			
		||||
                          findChildIndexCallback: (key) => controller
 | 
			
		||||
                              .findChildIndexCallback(key, thisEventsKeyMap),
 | 
			
		||||
                        ),
 | 
			
		||||
                      );
 | 
			
		||||
                    },
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                AnimatedContainer(
 | 
			
		||||
                  duration: Duration(milliseconds: 300),
 | 
			
		||||
                  height: (controller.editEvent == null &&
 | 
			
		||||
                          controller.replyEvent == null &&
 | 
			
		||||
                          controller.room.canSendDefaultMessages &&
 | 
			
		||||
                          controller.selectedEvents.length == 1)
 | 
			
		||||
                      ? 56
 | 
			
		||||
                      : 0,
 | 
			
		||||
                  child: Material(
 | 
			
		||||
                    color: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                    child: Builder(builder: (context) {
 | 
			
		||||
                      if (!(controller.editEvent == null &&
 | 
			
		||||
                          controller.replyEvent == null &&
 | 
			
		||||
                          controller.selectedEvents.length == 1)) {
 | 
			
		||||
                        return Container();
 | 
			
		||||
                      }
 | 
			
		||||
                      final emojis = List<String>.from(AppEmojis.emojis);
 | 
			
		||||
                      final allReactionEvents = controller.selectedEvents.first
 | 
			
		||||
                          .aggregatedEvents(
 | 
			
		||||
                              controller.timeline, RelationshipTypes.Reaction)
 | 
			
		||||
                          ?.where((event) =>
 | 
			
		||||
                              event.senderId == event.room.client.userID &&
 | 
			
		||||
                              event.type == 'm.reaction');
 | 
			
		||||
 | 
			
		||||
                      allReactionEvents.forEach((event) {
 | 
			
		||||
                        try {
 | 
			
		||||
                          emojis.remove(event.content['m.relates_to']['key']);
 | 
			
		||||
                        } catch (_) {}
 | 
			
		||||
                      });
 | 
			
		||||
                      return ListView.builder(
 | 
			
		||||
                        scrollDirection: Axis.horizontal,
 | 
			
		||||
                        itemCount: emojis.length + 1,
 | 
			
		||||
                        itemBuilder: (c, i) => i == emojis.length
 | 
			
		||||
                            ? InkWell(
 | 
			
		||||
                                borderRadius: BorderRadius.circular(8),
 | 
			
		||||
                                onTap: () => controller
 | 
			
		||||
                                    .pickEmojiAction(allReactionEvents),
 | 
			
		||||
                                child: Container(
 | 
			
		||||
                                  width: 56,
 | 
			
		||||
                                  height: 56,
 | 
			
		||||
                                  alignment: Alignment.center,
 | 
			
		||||
                                  child: Icon(Icons.add_outlined),
 | 
			
		||||
                                ),
 | 
			
		||||
                              )
 | 
			
		||||
                            : InkWell(
 | 
			
		||||
                                borderRadius: BorderRadius.circular(8),
 | 
			
		||||
                                onTap: () =>
 | 
			
		||||
                                    controller.sendEmojiAction(emojis[i]),
 | 
			
		||||
                                child: Container(
 | 
			
		||||
                                  width: 56,
 | 
			
		||||
                                  height: 56,
 | 
			
		||||
                                  alignment: Alignment.center,
 | 
			
		||||
                                  child: Text(
 | 
			
		||||
                                    emojis[i],
 | 
			
		||||
                                    style: TextStyle(fontSize: 30),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                      );
 | 
			
		||||
                    }),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                AnimatedContainer(
 | 
			
		||||
                  duration: Duration(milliseconds: 300),
 | 
			
		||||
                  height: controller.editEvent != null ||
 | 
			
		||||
                          controller.replyEvent != null
 | 
			
		||||
                      ? 56
 | 
			
		||||
                      : 0,
 | 
			
		||||
                  child: Material(
 | 
			
		||||
                    color: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                    child: Row(
 | 
			
		||||
                      children: <Widget>[
 | 
			
		||||
                        IconButton(
 | 
			
		||||
                          tooltip: L10n.of(context).close,
 | 
			
		||||
                          icon: Icon(Icons.close),
 | 
			
		||||
                          onPressed: controller.cancelReplyEventAction,
 | 
			
		||||
                        ),
 | 
			
		||||
                        Expanded(
 | 
			
		||||
                          child: controller.replyEvent != null
 | 
			
		||||
                              ? ReplyContent(controller.replyEvent,
 | 
			
		||||
                                  timeline: controller.timeline)
 | 
			
		||||
                              : _EditContent(controller.editEvent
 | 
			
		||||
                                  ?.getDisplayEvent(controller.timeline)),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                Divider(
 | 
			
		||||
                  height: 1,
 | 
			
		||||
                  thickness: 1,
 | 
			
		||||
                ),
 | 
			
		||||
                controller.room.canSendDefaultMessages &&
 | 
			
		||||
                        controller.room.membership == Membership.join
 | 
			
		||||
                    ? Container(
 | 
			
		||||
                        decoration: BoxDecoration(
 | 
			
		||||
                          color: Theme.of(context).backgroundColor,
 | 
			
		||||
                        ),
 | 
			
		||||
                        child: Row(
 | 
			
		||||
                          crossAxisAlignment: CrossAxisAlignment.end,
 | 
			
		||||
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
			
		||||
                          children: controller.selectMode
 | 
			
		||||
                              ? <Widget>[
 | 
			
		||||
                                  Container(
 | 
			
		||||
                                    height: 56,
 | 
			
		||||
                                    child: TextButton(
 | 
			
		||||
                                      onPressed: controller.forwardEventsAction,
 | 
			
		||||
                                      child: Row(
 | 
			
		||||
                                        children: <Widget>[
 | 
			
		||||
                                          Icon(Icons
 | 
			
		||||
                                              .keyboard_arrow_left_outlined),
 | 
			
		||||
                                          Text(L10n.of(context).forward),
 | 
			
		||||
                                        ],
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  controller.selectedEvents.length == 1
 | 
			
		||||
                                      ? controller.selectedEvents.first
 | 
			
		||||
                                                  .getDisplayEvent(
 | 
			
		||||
                                                      controller.timeline)
 | 
			
		||||
                                                  .status >
 | 
			
		||||
                                              0
 | 
			
		||||
                                          ? Container(
 | 
			
		||||
                                              height: 56,
 | 
			
		||||
                                              child: TextButton(
 | 
			
		||||
                                                onPressed:
 | 
			
		||||
                                                    controller.replyAction,
 | 
			
		||||
                                                child: Row(
 | 
			
		||||
                                                  children: <Widget>[
 | 
			
		||||
                                                    Text(
 | 
			
		||||
                                                        L10n.of(context).reply),
 | 
			
		||||
                                                    Icon(Icons
 | 
			
		||||
                                                        .keyboard_arrow_right),
 | 
			
		||||
                                                  ],
 | 
			
		||||
                                                ),
 | 
			
		||||
                                              ),
 | 
			
		||||
                                            )
 | 
			
		||||
                                          : Container(
 | 
			
		||||
                                              height: 56,
 | 
			
		||||
                                              child: TextButton(
 | 
			
		||||
                                                onPressed:
 | 
			
		||||
                                                    controller.sendAgainAction,
 | 
			
		||||
                                                child: Row(
 | 
			
		||||
                                                  children: <Widget>[
 | 
			
		||||
                                                    Text(L10n.of(context)
 | 
			
		||||
                                                        .tryToSendAgain),
 | 
			
		||||
                                                    SizedBox(width: 4),
 | 
			
		||||
                                                    Icon(Icons.send_outlined,
 | 
			
		||||
                                                        size: 16),
 | 
			
		||||
                                                  ],
 | 
			
		||||
                                                ),
 | 
			
		||||
                                              ),
 | 
			
		||||
                                            )
 | 
			
		||||
                                      : Container(),
 | 
			
		||||
                                ]
 | 
			
		||||
                              : <Widget>[
 | 
			
		||||
                                  if (controller.inputText.isEmpty)
 | 
			
		||||
                                    Container(
 | 
			
		||||
                                      height: 56,
 | 
			
		||||
                                      alignment: Alignment.center,
 | 
			
		||||
                                      child: PopupMenuButton<String>(
 | 
			
		||||
                                        icon: Icon(Icons.add_outlined),
 | 
			
		||||
                                        onSelected: controller
 | 
			
		||||
                                            .onAddPopupMenuButtonSelected,
 | 
			
		||||
                                        itemBuilder: (BuildContext context) =>
 | 
			
		||||
                                            <PopupMenuEntry<String>>[
 | 
			
		||||
                                          PopupMenuItem<String>(
 | 
			
		||||
                                            value: 'file',
 | 
			
		||||
                                            child: ListTile(
 | 
			
		||||
                                              leading: CircleAvatar(
 | 
			
		||||
                                                backgroundColor: Colors.green,
 | 
			
		||||
                                                foregroundColor: Colors.white,
 | 
			
		||||
                                                child: Icon(
 | 
			
		||||
                                                    Icons.attachment_outlined),
 | 
			
		||||
                                              ),
 | 
			
		||||
                                              title: Text(
 | 
			
		||||
                                                  L10n.of(context).sendFile),
 | 
			
		||||
                                              contentPadding: EdgeInsets.all(0),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          ),
 | 
			
		||||
                                          PopupMenuItem<String>(
 | 
			
		||||
                                            value: 'image',
 | 
			
		||||
                                            child: ListTile(
 | 
			
		||||
                                              leading: CircleAvatar(
 | 
			
		||||
                                                backgroundColor: Colors.blue,
 | 
			
		||||
                                                foregroundColor: Colors.white,
 | 
			
		||||
                                                child:
 | 
			
		||||
                                                    Icon(Icons.image_outlined),
 | 
			
		||||
                                              ),
 | 
			
		||||
                                              title: Text(
 | 
			
		||||
                                                  L10n.of(context).sendImage),
 | 
			
		||||
                                              contentPadding: EdgeInsets.all(0),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          ),
 | 
			
		||||
                                          if (PlatformInfos.isMobile)
 | 
			
		||||
                                            PopupMenuItem<String>(
 | 
			
		||||
                                              value: 'camera',
 | 
			
		||||
                                              child: ListTile(
 | 
			
		||||
                                                leading: CircleAvatar(
 | 
			
		||||
                                                  backgroundColor:
 | 
			
		||||
                                                      Colors.purple,
 | 
			
		||||
                                                  foregroundColor: Colors.white,
 | 
			
		||||
                                                  child: Icon(Icons
 | 
			
		||||
                                                      .camera_alt_outlined),
 | 
			
		||||
                                                ),
 | 
			
		||||
                                                title: Text(L10n.of(context)
 | 
			
		||||
                                                    .openCamera),
 | 
			
		||||
                                                contentPadding:
 | 
			
		||||
                                                    EdgeInsets.all(0),
 | 
			
		||||
                                              ),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          if (PlatformInfos.isMobile)
 | 
			
		||||
                                            PopupMenuItem<String>(
 | 
			
		||||
                                              value: 'voice',
 | 
			
		||||
                                              child: ListTile(
 | 
			
		||||
                                                leading: CircleAvatar(
 | 
			
		||||
                                                  backgroundColor: Colors.red,
 | 
			
		||||
                                                  foregroundColor: Colors.white,
 | 
			
		||||
                                                  child: Icon(
 | 
			
		||||
                                                      Icons.mic_none_outlined),
 | 
			
		||||
                                                ),
 | 
			
		||||
                                                title: Text(L10n.of(context)
 | 
			
		||||
                                                    .voiceMessage),
 | 
			
		||||
                                                contentPadding:
 | 
			
		||||
                                                    EdgeInsets.all(0),
 | 
			
		||||
                                              ),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                        ],
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  Container(
 | 
			
		||||
                                    height: 56,
 | 
			
		||||
                                    alignment: Alignment.center,
 | 
			
		||||
                                    child: EncryptionButton(controller.room),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  Expanded(
 | 
			
		||||
                                    child: Padding(
 | 
			
		||||
                                      padding: const EdgeInsets.symmetric(
 | 
			
		||||
                                          vertical: 4.0),
 | 
			
		||||
                                      child: InputBar(
 | 
			
		||||
                                        room: controller.room,
 | 
			
		||||
                                        minLines: 1,
 | 
			
		||||
                                        maxLines: kIsWeb ? 1 : 8,
 | 
			
		||||
                                        autofocus: !PlatformInfos.isMobile,
 | 
			
		||||
                                        keyboardType: !PlatformInfos.isMobile
 | 
			
		||||
                                            ? TextInputType.text
 | 
			
		||||
                                            : TextInputType.multiline,
 | 
			
		||||
                                        onSubmitted:
 | 
			
		||||
                                            controller.onInputBarSubmitted,
 | 
			
		||||
                                        focusNode: controller.inputFocus,
 | 
			
		||||
                                        controller: controller.sendController,
 | 
			
		||||
                                        decoration: InputDecoration(
 | 
			
		||||
                                          hintText:
 | 
			
		||||
                                              L10n.of(context).writeAMessage,
 | 
			
		||||
                                          hintMaxLines: 1,
 | 
			
		||||
                                          border: InputBorder.none,
 | 
			
		||||
                                          enabledBorder: InputBorder.none,
 | 
			
		||||
                                          filled: false,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                        onChanged: controller.onInputBarChanged,
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  if (PlatformInfos.isMobile &&
 | 
			
		||||
                                      controller.inputText.isEmpty)
 | 
			
		||||
                                    Container(
 | 
			
		||||
                                      height: 56,
 | 
			
		||||
                                      alignment: Alignment.center,
 | 
			
		||||
                                      child: IconButton(
 | 
			
		||||
                                        tooltip: L10n.of(context).voiceMessage,
 | 
			
		||||
                                        icon: Icon(Icons.mic_none_outlined),
 | 
			
		||||
                                        onPressed:
 | 
			
		||||
                                            controller.voiceMessageAction,
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                  if (!PlatformInfos.isMobile ||
 | 
			
		||||
                                      controller.inputText.isNotEmpty)
 | 
			
		||||
                                    Container(
 | 
			
		||||
                                      height: 56,
 | 
			
		||||
                                      alignment: Alignment.center,
 | 
			
		||||
                                      child: IconButton(
 | 
			
		||||
                                        icon: Icon(Icons.send_outlined),
 | 
			
		||||
                                        onPressed: controller.send,
 | 
			
		||||
                                        tooltip: L10n.of(context).send,
 | 
			
		||||
                                      ),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                ],
 | 
			
		||||
                        ),
 | 
			
		||||
                      )
 | 
			
		||||
                    : Container(),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EditContent extends StatelessWidget {
 | 
			
		||||
  final Event event;
 | 
			
		||||
 | 
			
		||||
  _EditContent(this.event);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (event == null) {
 | 
			
		||||
      return Container();
 | 
			
		||||
    }
 | 
			
		||||
    return Row(
 | 
			
		||||
      children: <Widget>[
 | 
			
		||||
        Icon(
 | 
			
		||||
          Icons.edit,
 | 
			
		||||
          color: Theme.of(context).primaryColor,
 | 
			
		||||
        ),
 | 
			
		||||
        Container(width: 15.0),
 | 
			
		||||
        Text(
 | 
			
		||||
          event?.getLocalizedBody(
 | 
			
		||||
                MatrixLocals(L10n.of(context)),
 | 
			
		||||
                withSenderNamePrefix: false,
 | 
			
		||||
                hideReply: true,
 | 
			
		||||
              ) ??
 | 
			
		||||
              '',
 | 
			
		||||
          overflow: TextOverflow.ellipsis,
 | 
			
		||||
          maxLines: 1,
 | 
			
		||||
          style: TextStyle(
 | 
			
		||||
            color: Theme.of(context).textTheme.bodyText2.color,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,14 +1,14 @@
 | 
			
		||||
import 'package:fluffychat/controllers/device_settings_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/device_settings.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/max_width_body.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
import 'widgets/list_items/user_device_list_item.dart';
 | 
			
		||||
import '../widgets/list_items/user_device_list_item.dart';
 | 
			
		||||
 | 
			
		||||
class DevicesSettingsView extends StatelessWidget {
 | 
			
		||||
class DevicesSettingsUI extends StatelessWidget {
 | 
			
		||||
  final DevicesSettingsController controller;
 | 
			
		||||
 | 
			
		||||
  const DevicesSettingsView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const DevicesSettingsUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import '../controllers/homeserver_picker_controller.dart';
 | 
			
		||||
import '../homeserver_picker.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/default_app_bar_search_field.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/fluffy_banner.dart';
 | 
			
		||||
import 'package:fluffychat/config/app_config.dart';
 | 
			
		||||
@ -10,13 +10,10 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:url_launcher/url_launcher.dart';
 | 
			
		||||
 | 
			
		||||
class HomeserverPickerView extends StatelessWidget {
 | 
			
		||||
class HomeserverPickerUI extends StatelessWidget {
 | 
			
		||||
  final HomeserverPickerController controller;
 | 
			
		||||
 | 
			
		||||
  const HomeserverPickerView(
 | 
			
		||||
    this.controller, {
 | 
			
		||||
    Key key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  const HomeserverPickerUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
import '../controllers/image_viewer_controller.dart';
 | 
			
		||||
import '../image_viewer.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/image_bubble.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class ImageViewerView extends StatelessWidget {
 | 
			
		||||
class ImageViewerUI extends StatelessWidget {
 | 
			
		||||
  final ImageViewerController controller;
 | 
			
		||||
 | 
			
		||||
  const ImageViewerView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const ImageViewerUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:fluffychat/controllers/invitation_selection_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/invitation_selection.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/default_app_bar_search_field.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
@ -8,13 +8,10 @@ import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class InvitationSelectionView extends StatelessWidget {
 | 
			
		||||
class InvitationSelectionUI extends StatelessWidget {
 | 
			
		||||
  final InvitationSelectionController controller;
 | 
			
		||||
 | 
			
		||||
  const InvitationSelectionView(
 | 
			
		||||
    this.controller, {
 | 
			
		||||
    Key key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  const InvitationSelectionUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -9,7 +9,7 @@ import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import '../utils/platform_infos.dart';
 | 
			
		||||
import '../../utils/platform_infos.dart';
 | 
			
		||||
import 'package:email_validator/email_validator.dart';
 | 
			
		||||
 | 
			
		||||
class Login extends StatefulWidget {
 | 
			
		||||
@ -1,15 +1,12 @@
 | 
			
		||||
import 'package:fluffychat/controllers/new_group_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/new_group.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/max_width_body.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class NewGroupView extends StatelessWidget {
 | 
			
		||||
class NewGroupUI extends StatelessWidget {
 | 
			
		||||
  final NewGroupController controller;
 | 
			
		||||
 | 
			
		||||
  const NewGroupView(
 | 
			
		||||
    this.controller, {
 | 
			
		||||
    Key key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  const NewGroupUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/new_private_chat_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/new_private_chat.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/avatar.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/contacts_list.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/max_width_body.dart';
 | 
			
		||||
@ -8,10 +8,10 @@ import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
 | 
			
		||||
class NewPrivateChatView extends StatelessWidget {
 | 
			
		||||
class NewPrivateChatUI extends StatelessWidget {
 | 
			
		||||
  final NewPrivateChatController controller;
 | 
			
		||||
 | 
			
		||||
  const NewPrivateChatView(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
  const NewPrivateChatUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -11,7 +11,7 @@ import 'package:fluffychat/views/widgets/matrix.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/localized_exception_extension.dart';
 | 
			
		||||
import '../../utils/localized_exception_extension.dart';
 | 
			
		||||
 | 
			
		||||
class SearchView extends StatefulWidget {
 | 
			
		||||
  final String alias;
 | 
			
		||||
@ -13,7 +13,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:image_picker/image_picker.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import '../views/widgets/matrix.dart';
 | 
			
		||||
import '../widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class EmotesSettings extends StatefulWidget {
 | 
			
		||||
  final Room room;
 | 
			
		||||
@ -5,7 +5,7 @@ import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
import '../views/widgets/matrix.dart';
 | 
			
		||||
import '../widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsIgnoreList extends StatefulWidget {
 | 
			
		||||
  final String initialUserId;
 | 
			
		||||
@ -9,9 +9,9 @@ import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
 | 
			
		||||
import 'package:open_noti_settings/open_noti_settings.dart';
 | 
			
		||||
import '../utils/localized_exception_extension.dart';
 | 
			
		||||
import '../../utils/localized_exception_extension.dart';
 | 
			
		||||
 | 
			
		||||
import '../views/widgets/matrix.dart';
 | 
			
		||||
import '../widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class NotificationSettingsItem {
 | 
			
		||||
  final PushRuleKind type;
 | 
			
		||||
@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:image_picker/image_picker.dart';
 | 
			
		||||
 | 
			
		||||
import '../config/app_config.dart';
 | 
			
		||||
import '../views/widgets/matrix.dart';
 | 
			
		||||
import '../../config/app_config.dart';
 | 
			
		||||
import '../widgets/matrix.dart';
 | 
			
		||||
 | 
			
		||||
class SettingsStyle extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
@ -21,11 +21,11 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
 | 
			
		||||
import 'package:image_picker/image_picker.dart';
 | 
			
		||||
import 'package:url_launcher/url_launcher.dart';
 | 
			
		||||
 | 
			
		||||
import '../views/widgets/content_banner.dart';
 | 
			
		||||
import '../widgets/content_banner.dart';
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import '../views/widgets/matrix.dart';
 | 
			
		||||
import '../config/app_config.dart';
 | 
			
		||||
import '../config/setting_keys.dart';
 | 
			
		||||
import '../widgets/matrix.dart';
 | 
			
		||||
import '../../config/app_config.dart';
 | 
			
		||||
import '../../config/setting_keys.dart';
 | 
			
		||||
 | 
			
		||||
class Settings extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
@ -1,16 +1,13 @@
 | 
			
		||||
import 'package:fluffychat/controllers/sign_up_password_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/sign_up_password.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/views/widgets/one_page_card.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class SignUpPasswordView extends StatelessWidget {
 | 
			
		||||
class SignUpPasswordUI extends StatelessWidget {
 | 
			
		||||
  final SignUpPasswordController controller;
 | 
			
		||||
 | 
			
		||||
  const SignUpPasswordView(
 | 
			
		||||
    this.controller, {
 | 
			
		||||
    Key key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  const SignUpPasswordUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import 'package:adaptive_page_layout/adaptive_page_layout.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/sign_up_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/sign_up.dart';
 | 
			
		||||
import 'package:fluffychat/views/widgets/fluffy_banner.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/views/widgets/matrix.dart';
 | 
			
		||||
@ -8,13 +8,10 @@ import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
 | 
			
		||||
class SignUpView extends StatelessWidget {
 | 
			
		||||
class SignUpUI extends StatelessWidget {
 | 
			
		||||
  final SignUpController controller;
 | 
			
		||||
 | 
			
		||||
  const SignUpView(
 | 
			
		||||
    this.controller, {
 | 
			
		||||
    Key key,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
  const SignUpUI(this.controller, {Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import 'package:famedlysdk/famedlysdk.dart';
 | 
			
		||||
import 'package:fluffychat/controllers/image_viewer_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/image_viewer.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter_blurhash/flutter_blurhash.dart';
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ import 'package:provider/provider.dart';
 | 
			
		||||
import 'package:universal_html/prefer_universal/html.dart' as html;
 | 
			
		||||
import 'package:http/http.dart' as http;
 | 
			
		||||
import 'package:url_launcher/url_launcher.dart';
 | 
			
		||||
/*import 'package:fluffychat/views/chat.dart';
 | 
			
		||||
/*import 'package:fluffychat/views/chat_ui.dart';
 | 
			
		||||
import 'package:fluffychat/app_config.dart';
 | 
			
		||||
import 'package:dbus/dbus.dart';
 | 
			
		||||
import 'package:desktop_notifications/desktop_notifications.dart';*/
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:fluffychat/controllers/homeserver_picker_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/homeserver_picker.dart';
 | 
			
		||||
import 'package:fluffychat/main.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:fluffychat/controllers/sign_up_password_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/sign_up_password.dart';
 | 
			
		||||
import 'package:fluffychat/main.dart';
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import 'package:fluffychat/controllers/sign_up_controller.dart';
 | 
			
		||||
import 'package:fluffychat/views/sign_up.dart';
 | 
			
		||||
import 'package:fluffychat/main.dart';
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user