design: New sticker picker next to emoji picker

This commit is contained in:
krille-chan 2024-02-25 16:14:12 +01:00
parent 6c5133abfe
commit 5a6016931b
No known key found for this signature in database
5 changed files with 118 additions and 98 deletions

View file

@ -2481,5 +2481,7 @@
} }
}, },
"transparent": "Transparent", "transparent": "Transparent",
"incomingMessages": "Incoming messages" "incomingMessages": "Incoming messages",
"stickers": "Stickers",
"discover": "Discover"
} }

View file

@ -25,7 +25,6 @@ import 'package:fluffychat/pages/chat/chat_view.dart';
import 'package:fluffychat/pages/chat/event_info_dialog.dart'; import 'package:fluffychat/pages/chat/event_info_dialog.dart';
import 'package:fluffychat/pages/chat/recording_dialog.dart'; import 'package:fluffychat/pages/chat/recording_dialog.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/error_reporter.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
@ -37,7 +36,6 @@ import '../../utils/localized_exception_extension.dart';
import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart'; import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart';
import 'send_file_dialog.dart'; import 'send_file_dialog.dart';
import 'send_location_dialog.dart'; import 'send_location_dialog.dart';
import 'sticker_picker_dialog.dart';
class ChatPage extends StatelessWidget { class ChatPage extends StatelessWidget {
final String roomId; final String roomId;
@ -594,24 +592,6 @@ class ChatController extends State<ChatPageWithRoom>
); );
} }
void sendStickerAction() async {
final sticker = await showAdaptiveBottomSheet<ImagePackImageContent>(
context: context,
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 room.sendEvent(
eventContent,
type: EventTypes.Sticker,
);
}
void voiceMessageAction() async { void voiceMessageAction() async {
final scaffoldMessenger = ScaffoldMessenger.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context);
if (PlatformInfos.isAndroid) { if (PlatformInfos.isAndroid) {
@ -668,6 +648,10 @@ class ChatController extends State<ChatPageWithRoom>
}); });
} }
void hideEmojiPicker() {
setState(() => showEmojiPicker = false);
}
void emojiPickerAction() { void emojiPickerAction() {
if (showEmojiPicker) { if (showEmojiPicker) {
inputFocus.requestFocus(); inputFocus.requestFocus();
@ -1146,9 +1130,6 @@ class ChatController extends State<ChatPageWithRoom>
if (choice == 'camera-video') { if (choice == 'camera-video') {
openVideoCameraAction(); openVideoCameraAction();
} }
if (choice == 'sticker') {
sendStickerAction();
}
if (choice == 'location') { if (choice == 'location') {
sendLocationAction(); sendLocationAction();
} }

View file

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/sticker_picker_dialog.dart';
import 'chat.dart'; import 'chat.dart';
class ChatEmojiPicker extends StatelessWidget { class ChatEmojiPicker extends StatelessWidget {
@ -16,30 +18,65 @@ class ChatEmojiPicker extends StatelessWidget {
return AnimatedContainer( return AnimatedContainer(
duration: FluffyThemes.animationDuration, duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve, curve: FluffyThemes.animationCurve,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
height: controller.showEmojiPicker height: controller.showEmojiPicker
? MediaQuery.of(context).size.height / 2 ? MediaQuery.of(context).size.height / 2
: 0, : 0,
child: controller.showEmojiPicker child: controller.showEmojiPicker
? EmojiPicker( ? DefaultTabController(
onEmojiSelected: controller.onEmojiSelected, length: 2,
onBackspacePressed: controller.emojiPickerBackspace, child: Column(
config: Config( children: [
backspaceColor: theme.colorScheme.primary, TabBar(
bgColor: Color.lerp( tabs: [
theme.colorScheme.background, Tab(text: L10n.of(context)!.emojis),
theme.colorScheme.primaryContainer, Tab(text: L10n.of(context)!.stickers),
0.25, ],
)!, ),
iconColor: theme.colorScheme.primary.withOpacity(0.5), Expanded(
iconColorSelected: theme.colorScheme.primary, child: TabBarView(
indicatorColor: theme.colorScheme.primary, children: [
noRecents: const NoRecent(), EmojiPicker(
skinToneDialogBgColor: Color.lerp( onEmojiSelected: controller.onEmojiSelected,
theme.colorScheme.background, onBackspacePressed: controller.emojiPickerBackspace,
theme.colorScheme.primaryContainer, config: Config(
0.75, backspaceColor: theme.colorScheme.primary,
)!, bgColor: theme.brightness == Brightness.light
skinToneIndicatorColor: theme.colorScheme.onBackground, ? Colors.white
: Colors.black,
iconColor:
theme.colorScheme.primary.withOpacity(0.5),
iconColorSelected: theme.colorScheme.primary,
indicatorColor: theme.colorScheme.primary,
noRecents: const NoRecent(),
skinToneDialogBgColor: Color.lerp(
theme.colorScheme.background,
theme.colorScheme.primaryContainer,
0.75,
)!,
skinToneIndicatorColor:
theme.colorScheme.onBackground,
),
),
StickerPickerDialog(
room: controller.room,
onSelected: (sticker) {
controller.room.sendEvent(
{
'body': sticker.body,
if (sticker.info != null) 'info': sticker.info,
'url': sticker.url.toString(),
},
type: EventTypes.Sticker,
);
controller.hideEmojiPicker();
},
),
],
),
),
],
), ),
) )
: null, : null,

View file

@ -164,21 +164,6 @@ class ChatInputRow extends StatelessWidget {
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
), ),
), ),
if (controller.room
.getImagePacks(ImagePackUsage.sticker)
.isNotEmpty)
PopupMenuItem<String>(
value: 'sticker',
child: ListTile(
leading: const CircleAvatar(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
child: Icon(Icons.emoji_emotions_outlined),
),
title: Text(L10n.of(context)!.sendSticker),
contentPadding: const EdgeInsets.all(0),
),
),
if (PlatformInfos.isMobile) if (PlatformInfos.isMobile)
PopupMenuItem<String>( PopupMenuItem<String>(
value: 'location', value: 'location',
@ -225,7 +210,7 @@ class ChatInputRow extends StatelessWidget {
child: Icon( child: Icon(
controller.showEmojiPicker controller.showEmojiPicker
? Icons.keyboard ? Icons.keyboard
: Icons.emoji_emotions_outlined, : Icons.add_reaction_outlined,
key: ValueKey(controller.showEmojiPicker), key: ValueKey(controller.showEmojiPicker),
), ),
), ),

View file

@ -3,13 +3,20 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/url_launcher.dart';
import 'package:fluffychat/widgets/mxc_image.dart';
import '../../widgets/avatar.dart'; import '../../widgets/avatar.dart';
import 'events/image_bubble.dart';
class StickerPickerDialog extends StatefulWidget { class StickerPickerDialog extends StatefulWidget {
final Room room; final Room room;
final void Function(ImagePackImageContent) onSelected;
const StickerPickerDialog({required this.room, super.key}); const StickerPickerDialog({
required this.onSelected,
required this.room,
super.key,
});
@override @override
StickerPickerDialogState createState() => StickerPickerDialogState(); StickerPickerDialogState createState() => StickerPickerDialogState();
@ -58,24 +65,14 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
GridView.builder( GridView.builder(
itemCount: imageKeys.length, itemCount: imageKeys.length,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100, maxCrossAxisExtent: 128,
), ),
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int imageIndex) { itemBuilder: (BuildContext context, int imageIndex) {
final image = pack.images[imageKeys[imageIndex]]!; final image = pack.images[imageKeys[imageIndex]]!;
final fakeEvent = Event(
type: EventTypes.Sticker,
content: {
'url': image.url.toString(),
'info': image.info,
},
originServerTs: DateTime.now(),
room: widget.room,
eventId: 'fake_event',
senderId: widget.room.client.userID!,
);
return InkWell( return InkWell(
radius: AppConfig.borderRadius,
key: ValueKey(image.url.toString()), key: ValueKey(image.url.toString()),
onTap: () { onTap: () {
// copy the image // copy the image
@ -83,17 +80,16 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
ImagePackImageContent.fromJson(image.toJson().copy()); ImagePackImageContent.fromJson(image.toJson().copy());
// set the body, if it doesn't exist, to the key // set the body, if it doesn't exist, to the key
imageCopy.body ??= imageKeys[imageIndex]; imageCopy.body ??= imageKeys[imageIndex];
Navigator.of(context, rootNavigator: false) widget.onSelected(imageCopy);
.pop<ImagePackImageContent>(imageCopy);
}, },
child: AbsorbPointer( child: AbsorbPointer(
absorbing: true, absorbing: true,
child: ImageBubble( child: MxcImage(
fakeEvent, uri: image.url,
tapToView: false,
fit: BoxFit.contain, fit: BoxFit.contain,
width: 100, width: 128,
height: 100, height: 128,
animated: true,
), ),
), ),
); );
@ -112,28 +108,47 @@ class StickerPickerDialogState extends State<StickerPickerDialog> {
floating: true, floating: true,
pinned: true, pinned: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
titleSpacing: 0,
backgroundColor: Theme.of(context).dialogBackgroundColor, backgroundColor: Theme.of(context).dialogBackgroundColor,
leading: IconButton( title: SizedBox(
icon: const Icon(Icons.close), height: 42,
onPressed: Navigator.of(context, rootNavigator: false).pop, child: TextField(
), autofocus: false,
title: TextField( decoration: InputDecoration(
autofocus: false, hintText: L10n.of(context)!.search,
decoration: InputDecoration( prefixIcon: const Icon(Icons.search_outlined),
hintText: L10n.of(context)!.search, contentPadding: EdgeInsets.zero,
suffix: const Icon(Icons.search_outlined), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), onChanged: (s) => setState(() => searchFilter = s),
), ),
onChanged: (s) => setState(() => searchFilter = s),
), ),
), ),
SliverList( if (packSlugs.isEmpty)
delegate: SliverChildBuilderDelegate( SliverFillRemaining(
packBuilder, child: Center(
childCount: packSlugs.length, child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(L10n.of(context)!.noEmotesFound),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: () => UrlLauncher(
context,
'https://matrix.to/#/#fluffychat-stickers:janian.de',
).launchUrl(),
icon: const Icon(Icons.explore_outlined),
label: Text(L10n.of(context)!.discover),
),
],
),
),
)
else
SliverList(
delegate: SliverChildBuilderDelegate(
packBuilder,
childCount: packSlugs.length,
),
), ),
),
], ],
), ),
), ),