import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; // debugPrint import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:printing/printing.dart'; import 'package:pdf/pdf.dart'; import 'package:googleapis/drive/v3.dart' as drive; import 'package:google_sign_in/google_sign_in.dart' as sign_in; import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart'; import '../services/pdf_service.dart'; import '../models/sake_item.dart'; import '../providers/menu_providers.dart'; import '../theme/app_colors.dart'; class PdfPreviewScreen extends ConsumerWidget { final List items; final String title; final String date; final bool includePhoto; final bool includePoem; final bool includeChart; final bool includePrice; final bool includeDate; final bool includeQr; final String pdfSize; final bool isMonochrome; const PdfPreviewScreen({ super.key, required this.items, required this.title, required this.date, required this.includePhoto, required this.includePoem, required this.includeChart, required this.includePrice, required this.includeDate, required this.includeQr, required this.pdfSize, required this.isMonochrome, }); @override Widget build(BuildContext context, WidgetRef ref) { // Phase 4: Watch new PDF Settings final isPortrait = ref.watch(pdfIsPortraitProvider); final density = ref.watch(pdfDensityProvider); return Scaffold( backgroundColor: Colors.white, // 明示的に白背景を設定 appBar: AppBar( title: const Text('お品書きプレビュー'), automaticallyImplyLeading: false, // ヘッダー戻るボタンを無効化 actions: [ IconButton( icon: const Icon(Icons.close), onPressed: () => _showExitDialog(context, ref), tooltip: '終了', ), ], ), body: PdfPreview( build: (format) => PdfService.generateMenuPdf( items, title: title, date: date, includePhoto: includePhoto, includePoem: includePoem, includeChart: includeChart, includePrice: includePrice, includeDate: includeDate, includeQr: includeQr, pdfSize: pdfSize, isMonochrome: isMonochrome, isPortrait: isPortrait, density: density, ), initialPageFormat: _getPageFormat(pdfSize, isPortrait), canChangePageFormat: false, // User selects in Edit screen canChangeOrientation: false, canDebug: false, allowSharing: false, // Handled by custom button allowPrinting: false, // Handled by custom button if needed (or share -> print) useActions: false, // Disable default bottom bar scrollViewDecoration: const BoxDecoration( color: Colors.white, ), pdfPreviewPageDecoration: BoxDecoration( color: Colors.white, // FIX: Ensure paper is white boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.3), blurRadius: 10, spreadRadius: 2, ), ], ), loadingWidget: const Center(child: CircularProgressIndicator()), onError: (context, error) => Center(child: Text('エラーが発生しました: $error')), ), bottomNavigationBar: SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ // 操作ガイド (フッターの真上) Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.7), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.touch_app, color: Colors.white, size: 16), const SizedBox(width: 8), Flexible( child: Text( 'メニュー領域を2回タップで拡大・縮小', style: TextStyle(color: Colors.white, fontSize: 12), textAlign: TextAlign.center, ), ), ], ), ), ), ), // フッターボタン Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).cardColor, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, -2), ), ], ), child: Row( children: [ // 戻るボタン (左端) SizedBox( width: 56, height: 56, child: OutlinedButton( onPressed: () => Navigator.pop(context), style: OutlinedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), side: BorderSide(color: Colors.grey[300]!), padding: EdgeInsets.zero, ), child: Icon(Icons.arrow_back, color: Colors.grey[700]), ), ), const SizedBox(width: 8), // 共有ボタン Expanded( child: _PdfActionButton( icon: Icons.share, label: '共有', color: Theme.of(context).extension()!.brandPrimary, onPressed: () => _sharePdf(context, ref), ), ), const SizedBox(width: 8), // Google Driveボタン Expanded( child: _PdfActionButton( icon: Icons.cloud_upload, label: 'Drive', color: Theme.of(context).extension()!.brandAccent, onPressed: () => _uploadToDrive(context, ref), ), ), const SizedBox(width: 8), // 印刷ボタン Expanded( child: _PdfActionButton( icon: Icons.print, label: '印刷', color: Theme.of(context).extension()!.textSecondary, onPressed: () => _printPdf(context, ref), ), ), ], ), ), ], ), ), ); } PdfPageFormat _getPageFormat(String size, bool isPortrait) { PdfPageFormat format; switch (size) { case 'a5': format = PdfPageFormat.a5; break; case 'b5': format = PdfPageFormat(257 * PdfPageFormat.mm, 182 * PdfPageFormat.mm); break; case 'a4': default: format = PdfPageFormat.a4; break; } return isPortrait ? format : format.landscape; } /// PDF生成の共通処理 Future _generatePdfBytes(WidgetRef ref) async { final isPortrait = ref.read(pdfIsPortraitProvider); final density = ref.read(pdfDensityProvider); return await PdfService.generateMenuPdf( items, title: title, date: date, includePhoto: includePhoto, includePoem: includePoem, includeChart: includeChart, includePrice: includePrice, includeDate: includeDate, includeQr: includeQr, pdfSize: pdfSize, isMonochrome: isMonochrome, isPortrait: isPortrait, density: density, ); } /// 共有機能(既存機能) Future _sharePdf(BuildContext context, WidgetRef ref) async { try { final bytes = await _generatePdfBytes(ref); await Printing.sharePdf( bytes: bytes, filename: 'oshinagaki_${DateTime.now().toString().split(' ')[0]}.pdf', ); } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('共有エラー: $e')), ); } } } /// Google Driveアップロード機能 Future _uploadToDrive(BuildContext context, WidgetRef ref) async { try { // 1. Google Sign In with Drive scope final googleSignIn = sign_in.GoogleSignIn( scopes: [drive.DriveApi.driveFileScope], ); final account = await googleSignIn.signIn(); if (account == null) return; // ユーザーがキャンセル // 2. Get authenticated HTTP client final httpClient = (await googleSignIn.authenticatedClient())!; final driveApi = drive.DriveApi(httpClient); // 3. Generate PDF if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('PDFを生成中...')), ); } final bytes = await _generatePdfBytes(ref); // 4. Upload to Drive final fileName = 'お品書き_${DateTime.now().toString().split(' ')[0]}.pdf'; final driveFile = drive.File() ..name = fileName ..mimeType = 'application/pdf'; debugPrint('[PDF_DRIVE] 📤 アップロード開始: $fileName (${bytes.length} bytes)'); final uploadedFile = await driveApi.files.create( driveFile, uploadMedia: drive.Media( Stream.value(bytes.toList()), bytes.length, ), ); if (uploadedFile.id == null) { debugPrint('[PDF_DRIVE] ❌ アップロード失敗: ID取得不可'); throw Exception('アップロードに失敗しました(IDなし)'); } debugPrint('[PDF_DRIVE] ✅ アップロード完了: ID=${uploadedFile.id}'); // 5. Success notification if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Google Driveに保存しました: $fileName'), duration: const Duration(seconds: 3), action: SnackBarAction( label: 'OK', onPressed: () {}, ), ), ); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Driveアップロードエラー: $e'), duration: const Duration(seconds: 4), ), ); } } } /// 印刷機能 Future _printPdf(BuildContext context, WidgetRef ref) async { try { final bytes = await _generatePdfBytes(ref); await Printing.layoutPdf( onLayout: (_) => bytes, name: 'お品書き', format: _getPageFormat(pdfSize, ref.read(pdfIsPortraitProvider)), ); } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('印刷エラー: $e')), ); } } } Future _showExitDialog(BuildContext context, WidgetRef ref) async { final appColors = Theme.of(context).extension()!; final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('お品書き作成を終了しますか?'), content: const Text('入力内容は保存されません。'), actions: [ TextButton( child: const Text('キャンセル'), onPressed: () => Navigator.pop(context, false), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: appColors.brandPrimary, foregroundColor: appColors.surfaceSubtle, ), onPressed: () => Navigator.pop(context, true), child: const Text('終了'), ), ], ), ); if (confirmed == true && context.mounted) { ref.read(menuModeProvider.notifier).set(false); ref.read(selectedMenuSakeIdsProvider.notifier).clear(); Navigator.of(context).popUntil((route) => route.isFirst); } } } /// PDF操作ボタンの共通ウィジェット class _PdfActionButton extends StatelessWidget { final IconData icon; final String label; final Color color; final VoidCallback onPressed; const _PdfActionButton({ required this.icon, required this.label, required this.color, required this.onPressed, }); @override Widget build(BuildContext context) { return SizedBox( height: 56, child: ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( backgroundColor: color, foregroundColor: Colors.white, // Always white text on colored buttons shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), elevation: 2, // Add elevation for better visibility ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 20), const SizedBox(height: 2), Text( label, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ); } }