emoji_weixin 0.3.1 copy "emoji_weixin: ^0.3.1" to clipboard
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.

example/lib/main.dart

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;
}
0
likes
140
points
226
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

WeChat-style emoji/sticker panel for Flutter with Klipy search, custom stickers, camera capture, and multi-language UI.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

extended_image, flutter, hive_ce, hive_ce_flutter, http, image_picker, material_ui, path, path_provider, pro_image_editor, uuid

More

Packages that depend on emoji_weixin