import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:video_encoder/video_encoder.dart'; class CustomNotification { static const MethodChannel _channel = MethodChannel('custom_notifications'); static Future show( Widget widget, { Duration duration = const Duration(seconds: 2), List? buttons, }) async { final gifBytes = await _widgetToGif(widget, duration); if (gifBytes != null) { await _channel.invokeMethod('showNotification', { 'gif': gifBytes, 'buttons': buttons?.map((b) => b.toMap()).toList(), }); } } static Future _widgetToGif(Widget widget, Duration duration) async { final controller = AnimationController(vsync: TestVSync(), duration: duration); final frames = []; for (var i = 0; i < 30; i++) { controller.value = i / 30; final imageBytes = await _widgetToImage(widget); if (imageBytes != null) frames.add(imageBytes); } controller.dispose(); return VideoEncoder.encodeGif(frames, 30); } static Future _widgetToImage(Widget widget) async { final renderRepaintBoundary = RenderRepaintBoundary(); final pipelineOwner = PipelineOwner(); final renderView = RenderView( child: renderRepaintBoundary, configuration: ViewConfiguration(size: const Size(300, 100)), window: WidgetsBinding.instance.window, ); pipelineOwner.rootNode = renderView; final buildOwner = BuildOwner(focusManager: FocusManager()); final renderObjectToWidgetAdapter = RenderObjectToWidgetAdapter( container: renderRepaintBoundary, child: Directionality( textDirection: TextDirection.ltr, child: widget, ), ); renderObjectToWidgetAdapter.attachToRenderTree(buildOwner); buildOwner.buildScope(renderRepaintBoundary); buildOwner.finalizeTree(); pipelineOwner.flushLayout(); pipelineOwner.flushCompositingBits(); pipelineOwner.flushPaint(); final image = await renderRepaintBoundary.toImage(); final byteData = await image.toByteData(format: ui.ImageByteFormat.png); return byteData?.buffer.asUint8List(); } } class NotificationButton { final String title; final String action; NotificationButton({required this.title, required this.action}); Map toMap() => {'title': title, 'action': action}; }