|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 455 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 841 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,22 @@
|
|||||||
|
class ReactionInfo {
|
||||||
|
final String targetMessageId;
|
||||||
|
final String emoji;
|
||||||
|
|
||||||
|
ReactionInfo({
|
||||||
|
required this.targetMessageId,
|
||||||
|
required this.emoji,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReactionHelper {
|
||||||
|
/// Parse reaction format: r:[messageId]:[emoji]
|
||||||
|
static ReactionInfo? parseReaction(String text) {
|
||||||
|
final regex = RegExp(r'^r:([^:]+):(.+)$');
|
||||||
|
final match = regex.firstMatch(text);
|
||||||
|
if (match == null) return null;
|
||||||
|
return ReactionInfo(
|
||||||
|
targetMessageId: match.group(1)!,
|
||||||
|
emoji: match.group(2)!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class EmojiPicker extends StatelessWidget {
|
||||||
|
final Function(String) onEmojiSelected;
|
||||||
|
|
||||||
|
const EmojiPicker({
|
||||||
|
super.key,
|
||||||
|
required this.onEmojiSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const List<String> quickEmojis = ['👍', '❤️', '😂', '🎉', '👏', '🔥'];
|
||||||
|
|
||||||
|
static const Map<String, List<String>> emojiCategories = {
|
||||||
|
'Smileys': [
|
||||||
|
'😀', '😃', '😄', '😁', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘',
|
||||||
|
'😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🥸', '🤩', '🥳', '😏',
|
||||||
|
'😒', '😞', '😔', '😟', '😕', '🙁', '😣', '😖', '😫', '😩', '🥺', '😢', '😭', '😤', '😠', '😡',
|
||||||
|
'🤬', '🤯', '😳', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🤗', '🤔', '🤭', '🤫', '🤥', '😶',
|
||||||
|
],
|
||||||
|
'Gestures': [
|
||||||
|
'👍', '👎', '👊', '✊', '🤛', '🤜', '🤞', '✌️', '🤟', '🤘', '👌', '🤌', '🤏', '👈', '👉', '👆',
|
||||||
|
'👇', '☝️', '👋', '🤚', '🖐️', '✋', '🖖', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️', '💅', '🤳',
|
||||||
|
],
|
||||||
|
'Hearts': [
|
||||||
|
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❤️🔥', '❤️🩹', '💕', '💞', '💓', '💗',
|
||||||
|
'💖', '💘', '💝', '💟', '💌', '💢', '💥', '💫', '💦', '💨', '🕳️', '💬', '👁️🗨️', '🗨️', '🗯️', '💭',
|
||||||
|
],
|
||||||
|
'Objects': [
|
||||||
|
'🎉', '🎊', '🎈', '🎁', '🎀', '🪅', '🪆', '🏆', '🥇', '🥈', '🥉', '⚽', '⚾', '🥎', '🏀', '🏐',
|
||||||
|
'🏈', '🏉', '🎾', '🥏', '🎳', '🏏', '🏑', '🏒', '🥍', '🏓', '🏸', '🥊', '🥋', '🥅', '⛳', '🔥',
|
||||||
|
'⭐', '🌟', '✨', '⚡', '💡', '🔦', '🏮', '🪔', '📱', '💻', '⌚', '📷', '📺', '📻', '🎵', '🎶',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.5,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Add Reaction',
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 12,
|
||||||
|
children: quickEmojis
|
||||||
|
.map(
|
||||||
|
(emoji) => InkWell(
|
||||||
|
onTap: () {
|
||||||
|
onEmojiSelected(emoji);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
emoji,
|
||||||
|
style: const TextStyle(fontSize: 28),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Expanded(
|
||||||
|
child: DefaultTabController(
|
||||||
|
length: emojiCategories.length,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TabBar(
|
||||||
|
isScrollable: true,
|
||||||
|
tabs: emojiCategories.keys
|
||||||
|
.map((cat) => Tab(text: cat))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
children: emojiCategories.values
|
||||||
|
.map(
|
||||||
|
(emojis) => GridView.builder(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 8,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
),
|
||||||
|
itemCount: emojis.length,
|
||||||
|
itemBuilder: (context, index) => InkWell(
|
||||||
|
onTap: () {
|
||||||
|
onEmojiSelected(emojis[index]);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
emojis[index],
|
||||||
|
style: const TextStyle(fontSize: 28),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 579 KiB |