emoji_weixin 0.3.1
emoji_weixin: ^0.3.1 copied to clipboard
WeChat-style emoji/sticker panel for Flutter with Klipy search, custom stickers, camera capture, and multi-language UI.
import 'package:emoji_weixin/emoji_weixin.dart';
import 'package:material_ui/material_ui.dart';
import 'app_config.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final appConfig = await AppConfig.load();
EmojiWeixinConfig.configure(
EmojiWeixinConfig(
klipyApiKey: appConfig.klipyApiKey,
locale: appConfig.locale,
),
);
runApp(const EmojiWeixinDemoApp());
}
class EmojiWeixinDemoApp extends StatelessWidget {
const EmojiWeixinDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'emoji_weixin Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF07C160)),
useMaterial3: true,
),
builder: (context, child) => MaterialUiCompatibilityBridge( // ignore: deprecated_member_use
child: child!,
),
home: const ChatDemoPage(),
);
}
}
class ChatDemoPage extends StatefulWidget {
const ChatDemoPage({super.key});
@override
State<ChatDemoPage> createState() => _ChatDemoPageState();
}
class _ChatDemoPageState extends State<ChatDemoPage> {
final _messages = <_ChatMessage>[
_ChatMessage.text('你好,点下方表情按钮试试仿微信表情面板。'),
];
final _textController = TextEditingController();
final _favoriteService = KlipyStickerService();
bool _showPanel = false;
int _panelEpoch = 0;
@override
void dispose() {
_textController.dispose();
super.dispose();
}
void _sendText() {
final text = _textController.text.trim();
if (text.isEmpty) return;
setState(() {
_messages.add(_ChatMessage.text(text));
_textController.clear();
});
}
void _onSticker(Sticker sticker) {
setState(() {
_messages.add(_ChatMessage.sticker(sticker));
_showPanel = false;
});
}
Future<void> _showStickerMenu(
Offset globalPosition,
Sticker sticker,
) async {
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
final selected = await showMenu<String>(
context: context,
position: RelativeRect.fromRect(
globalPosition & const Size(1, 1),
Offset.zero & overlay.size,
),
items: const [
PopupMenuItem(value: 'favorite', child: Text('收藏')),
],
);
if (!mounted || selected != 'favorite') return;
await _favoriteSticker(sticker);
}
Future<void> _favoriteSticker(Sticker sticker) async {
final url = sticker.networkUrl;
if (url == null || url.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('该表情无法收藏')),
);
return;
}
try {
await _favoriteService.saveStickerToFavorite(sticker);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已添加到收藏')),
);
// Remount panel so the favorites tab picks up the new sticker.
setState(() => _panelEpoch++);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('收藏失败: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFEDEDED),
appBar: AppBar(
title: const Text('表情包演示'),
backgroundColor: const Color(0xFFEDEDED),
foregroundColor: Colors.black87,
elevation: 0.5,
),
body: Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
itemCount: _messages.length,
itemBuilder: (context, index) {
final msg = _messages[index];
if (msg.sticker != null) {
final sticker = msg.sticker!;
return Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: GestureDetector(
onSecondaryTapDown: (details) {
_showStickerMenu(details.globalPosition, sticker);
},
onLongPressStart: (details) {
_showStickerMenu(details.globalPosition, sticker);
},
child: SizedBox(
width: 120,
height: 120,
child: StickerRenderer(sticker: sticker),
),
),
),
);
}
return Align(
alignment: Alignment.centerRight,
child: Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(10),
constraints: const BoxConstraints(maxWidth: 240),
decoration: BoxDecoration(
color: const Color(0xFF95EC69),
borderRadius: BorderRadius.circular(6),
),
child: Text(msg.text ?? ''),
),
);
},
),
),
_buildInputBar(),
if (_showPanel)
EmojiWeixinPanel(
key: ValueKey(_panelEpoch),
onStickerSelected: _onSticker,
),
],
),
);
}
Widget _buildInputBar() {
return Container(
color: const Color(0xFFF7F7F7),
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
child: SafeArea(
top: false,
child: Row(
children: [
IconButton(
icon: Icon(
_showPanel
? Icons.keyboard_alt_outlined
: Icons.emoji_emotions_outlined,
),
onPressed: () => setState(() => _showPanel = !_showPanel),
),
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: '输入消息',
filled: true,
fillColor: Colors.white,
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: BorderSide.none,
),
),
onTap: () => setState(() => _showPanel = false),
onSubmitted: (_) => _sendText(),
),
),
const SizedBox(width: 8),
FilledButton(
onPressed: _sendText,
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFF07C160),
),
child: const Text('发送'),
),
],
),
),
);
}
}
class _ChatMessage {
_ChatMessage.text(this.text) : sticker = null;
_ChatMessage.sticker(this.sticker) : text = null;
final String? text;
final Sticker? sticker;
}