mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	Merge branch 'soru/stickers' into 'main'
feat: Add sticker picker See merge request famedly/fluffychat!452
This commit is contained in:
		
						commit
						5fe0289c96
					
				@ -1885,6 +1885,11 @@
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
  "sendSticker": "Send sticker",
 | 
			
		||||
  "@sendSticker": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
    "placeholders": {}
 | 
			
		||||
  },
 | 
			
		||||
  "sendOriginal": "Send original",
 | 
			
		||||
  "@sendOriginal": {
 | 
			
		||||
    "type": "text",
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,7 @@ import 'package:vrouter/vrouter.dart';
 | 
			
		||||
import '../utils/localized_exception_extension.dart';
 | 
			
		||||
 | 
			
		||||
import 'send_file_dialog.dart';
 | 
			
		||||
import 'sticker_picker_dialog.dart';
 | 
			
		||||
import '../utils/matrix_sdk_extensions.dart/filtered_timeline_extension.dart';
 | 
			
		||||
import '../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
 | 
			
		||||
 | 
			
		||||
@ -308,6 +309,28 @@ class ChatController extends State<Chat> {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void sendStickerAction() async {
 | 
			
		||||
    final sticker = await showModalBottomSheet<ImagePackImageContent>(
 | 
			
		||||
      context: context,
 | 
			
		||||
      useRootNavigator: false,
 | 
			
		||||
      builder: (c) => StickerPickerDialog(room: room),
 | 
			
		||||
    );
 | 
			
		||||
    if (sticker == null) return;
 | 
			
		||||
    final eventContent = <String, dynamic>{
 | 
			
		||||
      'body': sticker.body,
 | 
			
		||||
      if (sticker.info != null) 'info': sticker.info,
 | 
			
		||||
      'url': sticker.url.toString(),
 | 
			
		||||
    };
 | 
			
		||||
    // send the sticker
 | 
			
		||||
    await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
      future: () => room.sendEvent(
 | 
			
		||||
        eventContent,
 | 
			
		||||
        type: EventTypes.Sticker,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void voiceMessageAction() async {
 | 
			
		||||
    if (await Permission.microphone.isGranted != true) {
 | 
			
		||||
      final status = await Permission.microphone.request();
 | 
			
		||||
@ -645,12 +668,16 @@ class ChatController extends State<Chat> {
 | 
			
		||||
  void onAddPopupMenuButtonSelected(String choice) {
 | 
			
		||||
    if (choice == 'file') {
 | 
			
		||||
      sendFileAction();
 | 
			
		||||
    } else if (choice == 'image') {
 | 
			
		||||
    }
 | 
			
		||||
    if (choice == 'image') {
 | 
			
		||||
      sendImageAction();
 | 
			
		||||
    }
 | 
			
		||||
    if (choice == 'camera') {
 | 
			
		||||
      openCameraAction();
 | 
			
		||||
    }
 | 
			
		||||
    if (choice == 'sticker') {
 | 
			
		||||
      sendStickerAction();
 | 
			
		||||
    }
 | 
			
		||||
    if (choice == 'voice') {
 | 
			
		||||
      voiceMessageAction();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										133
									
								
								lib/pages/sticker_picker_dialog.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								lib/pages/sticker_picker_dialog.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,133 @@
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
 | 
			
		||||
import '../widgets/event_content/image_bubble.dart';
 | 
			
		||||
import '../widgets/avatar.dart';
 | 
			
		||||
import '../widgets/default_app_bar_search_field.dart';
 | 
			
		||||
 | 
			
		||||
class StickerPickerDialog extends StatefulWidget {
 | 
			
		||||
  final Room room;
 | 
			
		||||
 | 
			
		||||
  const StickerPickerDialog({this.room, Key key}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  StickerPickerDialogState createState() => StickerPickerDialogState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class StickerPickerDialogState extends State<StickerPickerDialog> {
 | 
			
		||||
  String searchFilter;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final stickerPacks = widget.room.getImagePacks(ImagePackUsage.sticker);
 | 
			
		||||
    final packSlugs = stickerPacks.keys.toList();
 | 
			
		||||
 | 
			
		||||
    final _packBuilder = (BuildContext context, int packIndex) {
 | 
			
		||||
      final pack = stickerPacks[packSlugs[packIndex]];
 | 
			
		||||
      final filteredImagePackImageEntried = pack.images.entries.toList();
 | 
			
		||||
      if (searchFilter?.isNotEmpty ?? false) {
 | 
			
		||||
        filteredImagePackImageEntried.removeWhere((e) =>
 | 
			
		||||
            !(e.key.toLowerCase().contains(searchFilter.toLowerCase()) ||
 | 
			
		||||
                (e.value.body
 | 
			
		||||
                        ?.toLowerCase()
 | 
			
		||||
                        ?.contains(searchFilter.toLowerCase()) ??
 | 
			
		||||
                    false)));
 | 
			
		||||
      }
 | 
			
		||||
      final imageKeys =
 | 
			
		||||
          filteredImagePackImageEntried.map((e) => e.key).toList();
 | 
			
		||||
      if (imageKeys.isEmpty) {
 | 
			
		||||
        return Container();
 | 
			
		||||
      }
 | 
			
		||||
      final packName = pack.pack.displayName ?? packSlugs[packIndex];
 | 
			
		||||
      return Column(
 | 
			
		||||
        children: <Widget>[
 | 
			
		||||
          if (packIndex != 0) SizedBox(height: 20),
 | 
			
		||||
          if (packName != 'user')
 | 
			
		||||
            ListTile(
 | 
			
		||||
              leading: Avatar(
 | 
			
		||||
                pack.pack.avatarUrl,
 | 
			
		||||
                packName,
 | 
			
		||||
                client: widget.room.client,
 | 
			
		||||
              ),
 | 
			
		||||
              title: Text(packName),
 | 
			
		||||
            ),
 | 
			
		||||
          SizedBox(height: 6),
 | 
			
		||||
          GridView.builder(
 | 
			
		||||
            itemCount: imageKeys.length,
 | 
			
		||||
            gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
 | 
			
		||||
                maxCrossAxisExtent: 100),
 | 
			
		||||
            shrinkWrap: true,
 | 
			
		||||
            physics: NeverScrollableScrollPhysics(),
 | 
			
		||||
            itemBuilder: (BuildContext context, int imageIndex) {
 | 
			
		||||
              final image = pack.images[imageKeys[imageIndex]];
 | 
			
		||||
              final fakeEvent = Event.fromJson(<String, dynamic>{
 | 
			
		||||
                'type': EventTypes.Sticker,
 | 
			
		||||
                'content': <String, dynamic>{
 | 
			
		||||
                  'url': image.url.toString(),
 | 
			
		||||
                  'info': image.info,
 | 
			
		||||
                },
 | 
			
		||||
                'event_id': 'fake_event',
 | 
			
		||||
              }, widget.room);
 | 
			
		||||
              return InkWell(
 | 
			
		||||
                key: ValueKey(image.url.toString()),
 | 
			
		||||
                onTap: () {
 | 
			
		||||
                  // copy the image
 | 
			
		||||
                  final imageCopy =
 | 
			
		||||
                      ImagePackImageContent.fromJson(image.toJson().copy());
 | 
			
		||||
                  // set the body, if it doesn't exist, to the key
 | 
			
		||||
                  imageCopy.body ??= imageKeys[imageIndex];
 | 
			
		||||
                  Navigator.of(context, rootNavigator: false)
 | 
			
		||||
                      .pop<ImagePackImageContent>(imageCopy);
 | 
			
		||||
                },
 | 
			
		||||
                child: AbsorbPointer(
 | 
			
		||||
                  absorbing: true,
 | 
			
		||||
                  child: ImageBubble(
 | 
			
		||||
                    fakeEvent,
 | 
			
		||||
                    tapToView: false,
 | 
			
		||||
                    fit: BoxFit.contain,
 | 
			
		||||
                    width: 100,
 | 
			
		||||
                    height: 100,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              );
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      body: Container(
 | 
			
		||||
        width: double.maxFinite,
 | 
			
		||||
        child: CustomScrollView(
 | 
			
		||||
          slivers: <Widget>[
 | 
			
		||||
            SliverAppBar(
 | 
			
		||||
              floating: true,
 | 
			
		||||
              pinned: true,
 | 
			
		||||
              automaticallyImplyLeading: false,
 | 
			
		||||
              titleSpacing: 0,
 | 
			
		||||
              backgroundColor: Theme.of(context).dialogBackgroundColor,
 | 
			
		||||
              leading: IconButton(
 | 
			
		||||
                icon: Icon(Icons.close),
 | 
			
		||||
                onPressed: Navigator.of(context, rootNavigator: false).pop,
 | 
			
		||||
              ),
 | 
			
		||||
              title: DefaultAppBarSearchField(
 | 
			
		||||
                autofocus: false,
 | 
			
		||||
                hintText: L10n.of(context).search,
 | 
			
		||||
                suffix: Icon(Icons.search_outlined),
 | 
			
		||||
                onChanged: (s) => setState(() => searchFilter = s),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
            SliverList(
 | 
			
		||||
                delegate: SliverChildBuilderDelegate(
 | 
			
		||||
              _packBuilder,
 | 
			
		||||
              childCount: packSlugs.length,
 | 
			
		||||
            )),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -626,6 +626,20 @@ class ChatView extends StatelessWidget {
 | 
			
		||||
                                              contentPadding: EdgeInsets.all(0),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                          ),
 | 
			
		||||
                                        PopupMenuItem<String>(
 | 
			
		||||
                                          value: 'sticker',
 | 
			
		||||
                                          child: ListTile(
 | 
			
		||||
                                            leading: CircleAvatar(
 | 
			
		||||
                                              backgroundColor: Colors.orange,
 | 
			
		||||
                                              foregroundColor: Colors.white,
 | 
			
		||||
                                              child: Icon(Icons
 | 
			
		||||
                                                  .emoji_emotions_outlined),
 | 
			
		||||
                                            ),
 | 
			
		||||
                                            title: Text(
 | 
			
		||||
                                                L10n.of(context).sendSticker),
 | 
			
		||||
                                            contentPadding: EdgeInsets.all(0),
 | 
			
		||||
                                          ),
 | 
			
		||||
                                        ),
 | 
			
		||||
                                        if (PlatformInfos.isMobile)
 | 
			
		||||
                                          PopupMenuItem<String>(
 | 
			
		||||
                                            value: 'voice',
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user