You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			320 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Dart
		
	
			
		
		
	
	
			320 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Dart
		
	
import 'package:flutter/material.dart';
 | 
						|
 | 
						|
import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
						|
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
						|
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
						|
import 'package:go_router/go_router.dart';
 | 
						|
import 'package:matrix/matrix.dart';
 | 
						|
 | 
						|
import 'package:fluffychat/config/themes.dart';
 | 
						|
import 'package:fluffychat/widgets/permission_slider_dialog.dart';
 | 
						|
import '../../widgets/matrix.dart';
 | 
						|
import 'user_bottom_sheet_view.dart';
 | 
						|
 | 
						|
enum UserBottomSheetAction {
 | 
						|
  report,
 | 
						|
  mention,
 | 
						|
  ban,
 | 
						|
  kick,
 | 
						|
  unban,
 | 
						|
  message,
 | 
						|
  ignore,
 | 
						|
}
 | 
						|
 | 
						|
class LoadProfileBottomSheet extends StatelessWidget {
 | 
						|
  final String userId;
 | 
						|
  final BuildContext outerContext;
 | 
						|
 | 
						|
  const LoadProfileBottomSheet({
 | 
						|
    super.key,
 | 
						|
    required this.userId,
 | 
						|
    required this.outerContext,
 | 
						|
  });
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return FutureBuilder<ProfileInformation>(
 | 
						|
      future: Matrix.of(outerContext)
 | 
						|
          .client
 | 
						|
          .getUserProfile(userId)
 | 
						|
          .timeout(const Duration(seconds: 3)),
 | 
						|
      builder: (context, snapshot) {
 | 
						|
        if (snapshot.connectionState != ConnectionState.done &&
 | 
						|
            snapshot.data != null) {
 | 
						|
          return Scaffold(
 | 
						|
            appBar: AppBar(
 | 
						|
              leading: CloseButton(
 | 
						|
                onPressed: Navigator.of(context, rootNavigator: false).pop,
 | 
						|
              ),
 | 
						|
            ),
 | 
						|
            body: const Center(
 | 
						|
              child: CircularProgressIndicator.adaptive(),
 | 
						|
            ),
 | 
						|
          );
 | 
						|
        }
 | 
						|
        return UserBottomSheet(
 | 
						|
          outerContext: outerContext,
 | 
						|
          profile: Profile(
 | 
						|
            userId: userId,
 | 
						|
            avatarUrl: snapshot.data?.avatarUrl,
 | 
						|
            displayName: snapshot.data?.displayname,
 | 
						|
          ),
 | 
						|
          profileSearchError: snapshot.error,
 | 
						|
        );
 | 
						|
      },
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class UserBottomSheet extends StatefulWidget {
 | 
						|
  final User? user;
 | 
						|
  final Profile? profile;
 | 
						|
  final Function? onMention;
 | 
						|
  final BuildContext outerContext;
 | 
						|
  final Object? profileSearchError;
 | 
						|
 | 
						|
  const UserBottomSheet({
 | 
						|
    super.key,
 | 
						|
    this.user,
 | 
						|
    this.profile,
 | 
						|
    required this.outerContext,
 | 
						|
    this.onMention,
 | 
						|
    this.profileSearchError,
 | 
						|
  }) : assert(user != null || profile != null);
 | 
						|
 | 
						|
  @override
 | 
						|
  UserBottomSheetController createState() => UserBottomSheetController();
 | 
						|
}
 | 
						|
 | 
						|
class UserBottomSheetController extends State<UserBottomSheet> {
 | 
						|
  void participantAction(UserBottomSheetAction action) async {
 | 
						|
    final user = widget.user;
 | 
						|
    final userId = user?.id ?? widget.profile?.userId;
 | 
						|
    if (userId == null) throw ('user or profile must not be null!');
 | 
						|
 | 
						|
    switch (action) {
 | 
						|
      case UserBottomSheetAction.report:
 | 
						|
        if (user == null) throw ('User must not be null for this action!');
 | 
						|
 | 
						|
        final score = await showConfirmationDialog<int>(
 | 
						|
          context: context,
 | 
						|
          title: L10n.of(context)!.reportUser,
 | 
						|
          message: L10n.of(context)!.howOffensiveIsThisContent,
 | 
						|
          cancelLabel: L10n.of(context)!.cancel,
 | 
						|
          okLabel: L10n.of(context)!.ok,
 | 
						|
          actions: [
 | 
						|
            AlertDialogAction(
 | 
						|
              key: -100,
 | 
						|
              label: L10n.of(context)!.extremeOffensive,
 | 
						|
            ),
 | 
						|
            AlertDialogAction(
 | 
						|
              key: -50,
 | 
						|
              label: L10n.of(context)!.offensive,
 | 
						|
            ),
 | 
						|
            AlertDialogAction(
 | 
						|
              key: 0,
 | 
						|
              label: L10n.of(context)!.inoffensive,
 | 
						|
            ),
 | 
						|
          ],
 | 
						|
        );
 | 
						|
        if (score == null) return;
 | 
						|
        final reason = await showTextInputDialog(
 | 
						|
          useRootNavigator: false,
 | 
						|
          context: context,
 | 
						|
          title: L10n.of(context)!.whyDoYouWantToReportThis,
 | 
						|
          okLabel: L10n.of(context)!.ok,
 | 
						|
          cancelLabel: L10n.of(context)!.cancel,
 | 
						|
          textFields: [DialogTextField(hintText: L10n.of(context)!.reason)],
 | 
						|
        );
 | 
						|
        if (reason == null || reason.single.isEmpty) return;
 | 
						|
 | 
						|
        final result = await showFutureLoadingDialog(
 | 
						|
          context: context,
 | 
						|
          future: () => Matrix.of(widget.outerContext).client.reportContent(
 | 
						|
                user.room.id,
 | 
						|
                user.id,
 | 
						|
                reason: reason.single,
 | 
						|
                score: score,
 | 
						|
              ),
 | 
						|
        );
 | 
						|
        if (result.error != null) return;
 | 
						|
        ScaffoldMessenger.of(context).showSnackBar(
 | 
						|
          SnackBar(content: Text(L10n.of(context)!.contentHasBeenReported)),
 | 
						|
        );
 | 
						|
        break;
 | 
						|
      case UserBottomSheetAction.mention:
 | 
						|
        if (user == null) throw ('User must not be null for this action!');
 | 
						|
        Navigator.of(context).pop();
 | 
						|
        widget.onMention!();
 | 
						|
        break;
 | 
						|
      case UserBottomSheetAction.ban:
 | 
						|
        if (user == null) throw ('User must not be null for this action!');
 | 
						|
        if (await showOkCancelAlertDialog(
 | 
						|
              useRootNavigator: false,
 | 
						|
              context: context,
 | 
						|
              title: L10n.of(context)!.areYouSure,
 | 
						|
              okLabel: L10n.of(context)!.yes,
 | 
						|
              cancelLabel: L10n.of(context)!.no,
 | 
						|
              message: L10n.of(context)!.banUserDescription,
 | 
						|
            ) ==
 | 
						|
            OkCancelResult.ok) {
 | 
						|
          await showFutureLoadingDialog(
 | 
						|
            context: context,
 | 
						|
            future: () => user.ban(),
 | 
						|
          );
 | 
						|
          Navigator.of(context).pop();
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case UserBottomSheetAction.unban:
 | 
						|
        if (user == null) throw ('User must not be null for this action!');
 | 
						|
        if (await showOkCancelAlertDialog(
 | 
						|
              useRootNavigator: false,
 | 
						|
              context: context,
 | 
						|
              title: L10n.of(context)!.areYouSure,
 | 
						|
              okLabel: L10n.of(context)!.yes,
 | 
						|
              cancelLabel: L10n.of(context)!.no,
 | 
						|
              message: L10n.of(context)!.unbanUserDescription,
 | 
						|
            ) ==
 | 
						|
            OkCancelResult.ok) {
 | 
						|
          await showFutureLoadingDialog(
 | 
						|
            context: context,
 | 
						|
            future: () => user.unban(),
 | 
						|
          );
 | 
						|
          Navigator.of(context).pop();
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case UserBottomSheetAction.kick:
 | 
						|
        if (user == null) throw ('User must not be null for this action!');
 | 
						|
        if (await showOkCancelAlertDialog(
 | 
						|
              useRootNavigator: false,
 | 
						|
              context: context,
 | 
						|
              title: L10n.of(context)!.areYouSure,
 | 
						|
              okLabel: L10n.of(context)!.yes,
 | 
						|
              cancelLabel: L10n.of(context)!.no,
 | 
						|
              message: L10n.of(context)!.kickUserDescription,
 | 
						|
            ) ==
 | 
						|
            OkCancelResult.ok) {
 | 
						|
          await showFutureLoadingDialog(
 | 
						|
            context: context,
 | 
						|
            future: () => user.kick(),
 | 
						|
          );
 | 
						|
          Navigator.of(context).pop();
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case UserBottomSheetAction.message:
 | 
						|
        Navigator.of(context).pop();
 | 
						|
        // Workaround for https://github.com/flutter/flutter/issues/27495
 | 
						|
        await Future.delayed(FluffyThemes.animationDuration);
 | 
						|
 | 
						|
        final roomIdResult = await showFutureLoadingDialog(
 | 
						|
          context: widget.outerContext,
 | 
						|
          future: () => Matrix.of(widget.outerContext)
 | 
						|
              .client
 | 
						|
              .startDirectChat(user?.id ?? widget.profile!.userId),
 | 
						|
        );
 | 
						|
        final roomId = roomIdResult.result;
 | 
						|
        if (roomId == null) return;
 | 
						|
        widget.outerContext.go('/rooms/$roomId');
 | 
						|
        break;
 | 
						|
      case UserBottomSheetAction.ignore:
 | 
						|
        Navigator.of(context).pop();
 | 
						|
        // Workaround for https://github.com/flutter/flutter/issues/27495
 | 
						|
        await Future.delayed(FluffyThemes.animationDuration);
 | 
						|
        final userId = user?.id ?? widget.profile?.userId;
 | 
						|
        widget.outerContext
 | 
						|
            .go('/rooms/settings/security/ignorelist', extra: userId);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  bool isSending = false;
 | 
						|
 | 
						|
  Object? sendError;
 | 
						|
 | 
						|
  final TextEditingController sendController = TextEditingController();
 | 
						|
 | 
						|
  void sendAction([_]) async {
 | 
						|
    final userId = widget.user?.id ?? widget.profile?.userId;
 | 
						|
    final client = Matrix.of(widget.outerContext).client;
 | 
						|
    if (userId == null) throw ('user or profile must not be null!');
 | 
						|
 | 
						|
    final input = sendController.text.trim();
 | 
						|
    if (input.isEmpty) return;
 | 
						|
 | 
						|
    setState(() {
 | 
						|
      isSending = true;
 | 
						|
      sendError = null;
 | 
						|
    });
 | 
						|
    try {
 | 
						|
      final roomId = await client.startDirectChat(userId);
 | 
						|
      if (!mounted) return;
 | 
						|
      final room = client.getRoomById(roomId);
 | 
						|
      if (room == null) {
 | 
						|
        throw ('DM Room found or created but room not found in client');
 | 
						|
      }
 | 
						|
      await room.sendTextEvent(input);
 | 
						|
      setState(() {
 | 
						|
        isSending = false;
 | 
						|
        sendController.clear();
 | 
						|
      });
 | 
						|
    } catch (e, s) {
 | 
						|
      Logs().d('Unable to send message', e, s);
 | 
						|
      setState(() {
 | 
						|
        isSending = false;
 | 
						|
        sendError = e;
 | 
						|
      });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void knockAccept() async {
 | 
						|
    final user = widget.user!;
 | 
						|
    final result = await showFutureLoadingDialog(
 | 
						|
      context: context,
 | 
						|
      future: () => user.room.invite(user.id),
 | 
						|
    );
 | 
						|
    if (result.error != null) return;
 | 
						|
    Navigator.of(context).pop();
 | 
						|
  }
 | 
						|
 | 
						|
  void knockDecline() async {
 | 
						|
    final user = widget.user!;
 | 
						|
    final result = await showFutureLoadingDialog(
 | 
						|
      context: context,
 | 
						|
      future: () => user.room.kick(user.id),
 | 
						|
    );
 | 
						|
    if (result.error != null) return;
 | 
						|
    Navigator.of(context).pop();
 | 
						|
  }
 | 
						|
 | 
						|
  void setPowerLevel(int? newLevel) async {
 | 
						|
    final user = widget.user;
 | 
						|
    if (user == null) throw ('User must not be null for this action!');
 | 
						|
 | 
						|
    final level = newLevel ??
 | 
						|
        await showPermissionChooser(
 | 
						|
          context,
 | 
						|
          currentLevel: user.powerLevel,
 | 
						|
        );
 | 
						|
    if (level == null) return;
 | 
						|
 | 
						|
    if (level == 100) {
 | 
						|
      final consent = await showOkCancelAlertDialog(
 | 
						|
        useRootNavigator: false,
 | 
						|
        context: context,
 | 
						|
        title: L10n.of(context)!.areYouSure,
 | 
						|
        okLabel: L10n.of(context)!.yes,
 | 
						|
        cancelLabel: L10n.of(context)!.no,
 | 
						|
        message: L10n.of(context)!.makeAdminDescription,
 | 
						|
      );
 | 
						|
      if (consent != OkCancelResult.ok) return;
 | 
						|
    }
 | 
						|
 | 
						|
    await showFutureLoadingDialog(
 | 
						|
      context: context,
 | 
						|
      future: () => user.setPower(level),
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) => UserBottomSheetView(this);
 | 
						|
}
 |