import 'dart:io'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:share_plus/share_plus.dart'; import '../../app/icons/app_icons.dart'; import '../../app/theme/app_colors.dart'; import '../../core/widgets/arabic_text.dart'; String buildAyatShareText(Map ayat) { final arabic = (ayat['teksArab'] ?? '').toString().trim(); final translation = (ayat['teksIndonesia'] ?? '').toString().trim(); final surahName = (ayat['surahName'] ?? '').toString().trim(); final verseNumber = (ayat['nomorAyat'] ?? '').toString().trim(); final reference = surahName.isNotEmpty && verseNumber.isNotEmpty ? 'QS. $surahName: $verseNumber' : 'Ayat Hari Ini'; final parts = [ if (arabic.isNotEmpty) arabic, if (translation.isNotEmpty) '"$translation"', reference, 'Dibagikan dari Jam Shalat Diary', ]; return parts.join('\n\n'); } Future showAyatShareSheet( BuildContext context, Map ayat, ) async { final shareText = buildAyatShareText(ayat); final isDark = Theme.of(context).brightness == Brightness.dark; await showModalBottomSheet( context: context, useSafeArea: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), builder: (sheetContext) { Future handleShareImage() async { Navigator.of(sheetContext).pop(); try { final pngBytes = await _captureAyatShareCardPng(context, ayat); final file = await _writeAyatShareImage(pngBytes); await Share.shareXFiles( [XFile(file.path)], text: 'Ayat Hari Ini', subject: 'Ayat Hari Ini', ); } catch (_) { if (!context.mounted) return; ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( const SnackBar( content: Text('Gagal menyiapkan gambar ayat'), duration: Duration(seconds: 2), ), ); } } Future handleShareText() async { Navigator.of(sheetContext).pop(); await Share.share( shareText, subject: 'Ayat Hari Ini', ); } Future handleCopyText() async { await Clipboard.setData(ClipboardData(text: shareText)); if (!sheetContext.mounted) return; Navigator.of(sheetContext).pop(); ScaffoldMessenger.of(context) ..hideCurrentSnackBar() ..showSnackBar( const SnackBar( content: Text('Teks ayat disalin ke clipboard'), duration: Duration(seconds: 2), ), ); } return Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Bagikan Ayat', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 6), Text( 'Pilih cara tercepat untuk membagikan ayat hari ini.', style: TextStyle( fontSize: 13, height: 1.5, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), const SizedBox(height: 18), _AyatShareActionTile( icon: const Icon( LucideIcons.image, size: 20, color: AppColors.primary, ), title: 'Bagikan Gambar', subtitle: 'Kirim kartu ayat yang siap dibagikan', badge: 'Utama', onTap: handleShareImage, ), const SizedBox(height: 10), _AyatShareActionTile( icon: const AppIcon( glyph: AppIcons.share, size: 20, color: AppColors.primary, ), title: 'Bagikan Teks', subtitle: 'Kirim ayat dan terjemahan ke aplikasi lain', onTap: handleShareText, ), const SizedBox(height: 10), _AyatShareActionTile( icon: const Icon( LucideIcons.copy, size: 20, color: AppColors.primary, ), title: 'Salin Teks', subtitle: 'Simpan ke clipboard untuk ditempel manual', onTap: handleCopyText, ), ], ), ); }, ); } Future _captureAyatShareCardPng( BuildContext context, Map ayat, ) async { final overlay = Overlay.of(context, rootOverlay: true); final boundaryKey = GlobalKey(); final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); final textDirection = Directionality.of(context); late final OverlayEntry entry; entry = OverlayEntry( builder: (_) => IgnorePointer( child: Material( color: Colors.transparent, child: Align( alignment: Alignment.topCenter, child: Opacity( opacity: 0.01, child: MediaQuery( data: mediaQuery, child: Theme( data: theme, child: Directionality( textDirection: textDirection, child: UnconstrainedBox( constrainedAxis: Axis.horizontal, child: RepaintBoundary( key: boundaryKey, child: _AyatShareCard( ayat: ayat, isDark: theme.brightness == Brightness.dark, ), ), ), ), ), ), ), ), ), ), ); overlay.insert(entry); try { await Future.delayed(const Duration(milliseconds: 20)); await WidgetsBinding.instance.endOfFrame; await Future.delayed(const Duration(milliseconds: 20)); final boundary = boundaryKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) { throw StateError('Ayat share card is not ready'); } final image = await boundary.toImage(pixelRatio: 3); final byteData = await image.toByteData(format: ui.ImageByteFormat.png); if (byteData == null) { throw StateError('Failed to encode ayat share card'); } return byteData.buffer.asUint8List(); } finally { entry.remove(); } } Future _writeAyatShareImage(Uint8List pngBytes) async { final directory = await Directory.systemTemp.createTemp('jamshalat_ayat_'); final file = File('${directory.path}/ayat_hari_ini.png'); await file.writeAsBytes(pngBytes, flush: true); return file; } class _AyatShareCard extends StatelessWidget { const _AyatShareCard({ required this.ayat, required this.isDark, }); final Map ayat; final bool isDark; @override Widget build(BuildContext context) { final arabic = (ayat['teksArab'] ?? '').toString().trim(); final translation = (ayat['teksIndonesia'] ?? '').toString().trim(); final surahName = (ayat['surahName'] ?? '').toString().trim(); final verseNumber = (ayat['nomorAyat'] ?? '').toString().trim(); final reference = surahName.isNotEmpty && verseNumber.isNotEmpty ? 'QS. $surahName: $verseNumber' : 'Ayat Hari Ini'; final isLongArabic = arabic.length > 120; final isVeryLongArabic = arabic.length > 180; final isLongTranslation = translation.length > 140; final isVeryLongTranslation = translation.length > 220; final arabicFontSize = isVeryLongArabic ? 22.0 : isLongArabic ? 24.0 : 28.0; final arabicHeight = isVeryLongArabic ? 1.55 : isLongArabic ? 1.62 : 1.75; final translationFontSize = isVeryLongTranslation ? 13.0 : isLongTranslation ? 14.0 : 15.0; final translationHeight = isVeryLongTranslation ? 1.5 : 1.6; final verticalPadding = isVeryLongTranslation ? 22.0 : 24.0; return SizedBox( width: 360, child: ClipRRect( borderRadius: BorderRadius.circular(30), child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isDark ? const [ Color(0xFF102028), Color(0xFF0F1217), Color(0xFF16343A), ] : const [ Color(0xFFF6FBFB), Color(0xFFFFFFFF), Color(0xFFEAF7F7), ], ), boxShadow: [ BoxShadow( color: AppColors.primary.withValues(alpha: isDark ? 0.24 : 0.12), blurRadius: 28, offset: const Offset(0, 18), ), ], ), child: Stack( children: [ Positioned.fill( child: IgnorePointer( child: Padding( padding: const EdgeInsets.all(14), child: CustomPaint( painter: _AyatFramePainter(isDark: isDark), ), ), ), ), Positioned( top: -38, right: -34, child: Container( width: 116, height: 116, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColors.primary.withValues(alpha: 0.05), ), ), ), Positioned( bottom: -46, left: -28, child: Container( width: 132, height: 132, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColors.navActiveGold.withValues(alpha: 0.05), ), ), ), Padding( padding: EdgeInsets.fromLTRB( 28, 28, 28, verticalPadding, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.14), borderRadius: BorderRadius.circular(14), ), child: const Icon( LucideIcons.bookMarked, size: 20, color: AppColors.primary, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Ayat Hari Ini', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, letterSpacing: 1.3, color: AppColors.primary, ), ), const SizedBox(height: 3), Text( reference, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w800, color: isDark ? Colors.white : AppColors.textPrimaryLight, ), ), ], ), ), ], ), if (arabic.isNotEmpty) ...[ const SizedBox(height: 28), ArabicText( arabic, baseFontSize: arabicFontSize, height: arabicHeight, textAlign: TextAlign.right, color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, ), ], if (translation.isNotEmpty) ...[ const SizedBox(height: 24), Container( width: double.infinity, padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: (isDark ? Colors.white : AppColors.primary) .withValues(alpha: isDark ? 0.05 : 0.08), borderRadius: BorderRadius.circular(20), border: Border.all( color: (isDark ? Colors.white : AppColors.primary) .withValues(alpha: 0.08), ), ), child: Text( '"$translation"', textAlign: TextAlign.left, style: TextStyle( fontSize: translationFontSize, height: translationHeight, fontStyle: FontStyle.italic, color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, ), ), ), ], const SizedBox(height: 22), Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), decoration: BoxDecoration( color: AppColors.navActiveGold.withValues(alpha: 0.16), borderRadius: BorderRadius.circular(999), ), child: const Text( 'Jam Shalat Diary', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.navActiveGoldDeep, ), ), ), const Spacer(), Flexible( child: Text( 'Bagikan kebaikan', textAlign: TextAlign.right, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ), ], ), ], ), ), ], ), ), ), ); } } class _AyatShareActionTile extends StatelessWidget { const _AyatShareActionTile({ required this.icon, required this.title, required this.subtitle, required this.onTap, this.badge, }); final Widget icon; final String title; final String subtitle; final VoidCallback onTap; final String? badge; @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(18), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(18), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.12) : AppColors.cream, ), ), child: Row( children: [ Container( width: 42, height: 42, decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), ), child: Center(child: icon), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w700, ), ), if (badge != null) ...[ const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: AppColors.navActiveGold.withValues(alpha: 0.18), borderRadius: BorderRadius.circular(999), ), child: Text( badge!, style: const TextStyle( fontSize: 10, fontWeight: FontWeight.w800, color: AppColors.navActiveGoldDeep, ), ), ), ], const SizedBox(height: 4), Text( subtitle, style: TextStyle( fontSize: 12, height: 1.4, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ), Icon( LucideIcons.chevronRight, size: 18, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ], ), ), ), ); } } class _AyatFramePainter extends CustomPainter { const _AyatFramePainter({required this.isDark}); final bool isDark; @override void paint(Canvas canvas, Size size) { final outerRect = RRect.fromRectAndRadius( Offset.zero & size, const Radius.circular(22), ); final outerPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1.6 ..shader = const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColors.navActiveGoldPale, AppColors.navActiveGold, AppColors.navActiveGoldDeep, ], ).createShader(Offset.zero & size); final outerGlow = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 5 ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6) ..color = AppColors.navActiveGold.withValues(alpha: isDark ? 0.08 : 0.05); final innerBounds = Rect.fromLTWH(8, 8, size.width - 16, size.height - 16); final innerFrame = RRect.fromRectAndRadius( innerBounds, const Radius.circular(18), ); final innerPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 0.8 ..color = (isDark ? Colors.white : AppColors.primary) .withValues(alpha: isDark ? 0.08 : 0.10); canvas.drawRRect(outerRect, outerGlow); canvas.drawRRect(outerRect, outerPaint); canvas.drawRRect(innerFrame, innerPaint); _drawMidMotif(canvas, size, top: true); _drawMidMotif(canvas, size, top: false); } void _drawMidMotif(Canvas canvas, Size size, {required bool top}) { final y = top ? 14.0 : size.height - 14.0; final centerX = size.width / 2; final linePaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 0.9 ..strokeCap = StrokeCap.round ..color = AppColors.navActiveGold.withValues(alpha: isDark ? 0.26 : 0.22); final diamondPaint = Paint() ..style = PaintingStyle.fill ..color = AppColors.primary.withValues(alpha: isDark ? 0.34 : 0.22); final diamondStroke = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 0.9 ..color = AppColors.navActiveGold.withValues(alpha: isDark ? 0.58 : 0.48); canvas.drawLine( Offset(centerX - 26, y), Offset(centerX - 10, y), linePaint, ); canvas.drawLine( Offset(centerX + 10, y), Offset(centerX + 26, y), linePaint, ); final diamondPath = Path() ..moveTo(centerX, y - 5) ..lineTo(centerX + 5, y) ..lineTo(centerX, y + 5) ..lineTo(centerX - 5, y) ..close(); canvas.drawPath(diamondPath, diamondPaint); canvas.drawPath(diamondPath, diamondStroke); } @override bool shouldRepaint(covariant _AyatFramePainter oldDelegate) { return oldDelegate.isDark != isDark; } }