feat: Remove store logo functionality and update printing services
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>master
							parent
							
								
									c2e6f6b945
								
							
						
					
					
						commit
						64e36aa691
					
				| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
# Printing Status Card Implementation
 | 
			
		||||
 | 
			
		||||
I've successfully implemented a floating card with animations that shows the printing status when the user presses the print button.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
1. **Animated Appearance/Disappearance**:
 | 
			
		||||
   - Smooth scale and fade animations when showing/hiding
 | 
			
		||||
   - Elastic entrance animation for a polished feel
 | 
			
		||||
 | 
			
		||||
2. **Visual Design**:
 | 
			
		||||
   - Beautiful gradient background (purple to blue)
 | 
			
		||||
   - Clear icon and text indicators
 | 
			
		||||
   - Indeterminate progress bar
 | 
			
		||||
   - Rounded corners and shadow for depth
 | 
			
		||||
 | 
			
		||||
3. **Functionality**:
 | 
			
		||||
   - Automatically appears when printing starts
 | 
			
		||||
   - Automatically disappears when printing completes
 | 
			
		||||
   - Manual dismiss option with the close button
 | 
			
		||||
 | 
			
		||||
## Files Modified
 | 
			
		||||
 | 
			
		||||
1. Created new widget: `lib/widgets/printing_status_card.dart`
 | 
			
		||||
2. Modified: `lib/screens/receipt_screen.dart` to integrate the widget
 | 
			
		||||
 | 
			
		||||
## How It Works
 | 
			
		||||
 | 
			
		||||
When the user presses the "Cetak Struk" button in the speed dial:
 | 
			
		||||
1. The `_isPrinting` state is set to `true`
 | 
			
		||||
2. The `PrintingStatusCard` becomes visible with animations
 | 
			
		||||
3. The printing process begins
 | 
			
		||||
4. When printing completes (success or failure), `_isPrinting` is set to `false`
 | 
			
		||||
5. The card smoothly animates out of view
 | 
			
		||||
 | 
			
		||||
The card also allows manual dismissal by the user if needed.
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 91 B  | 
| 
						 | 
				
			
			@ -2,7 +2,6 @@ import 'package:cashumit/screens/config_screen.dart';
 | 
			
		|||
import 'package:cashumit/screens/transaction_screen.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:cashumit/screens/receipt_screen.dart';
 | 
			
		||||
import 'package:cashumit/utils/store_logo_utils.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
import 'package:cashumit/providers/receipt_provider.dart';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -10,9 +9,6 @@ void main() async {
 | 
			
		|||
  // Ensure WidgetsFlutterBinding is initialized for async operations
 | 
			
		||||
  WidgetsFlutterBinding.ensureInitialized();
 | 
			
		||||
  
 | 
			
		||||
  // Initialize the store logo from asset
 | 
			
		||||
  await copyAndSaveStoreLogoFromAsset('assets/images/store_logo.png');
 | 
			
		||||
  
 | 
			
		||||
  runApp(
 | 
			
		||||
    MultiProvider(
 | 
			
		||||
      providers: [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,13 +30,11 @@ class EscPosPrintService {
 | 
			
		|||
    final storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
 | 
			
		||||
    final adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
 | 
			
		||||
    final adminPhone = prefs.getString('admin_phone') ?? '08123456789';
 | 
			
		||||
    final logoPath = prefs.getString('store_logo_path'); // Load logo path
 | 
			
		||||
    
 | 
			
		||||
    print('Nama toko: $storeName');
 | 
			
		||||
    print('Alamat toko: $storeAddress');
 | 
			
		||||
    print('Nama admin: $adminName');
 | 
			
		||||
    print('Telepon admin: $adminPhone');
 | 
			
		||||
    print('Logo path: $logoPath');
 | 
			
		||||
    
 | 
			
		||||
    // Format tanggal
 | 
			
		||||
    final dateFormatter = DateFormat('dd/MM/yyyy HH:mm');
 | 
			
		||||
| 
						 | 
				
			
			@ -64,93 +62,14 @@ class EscPosPrintService {
 | 
			
		|||
    // Mulai dengan inisialisasi printer
 | 
			
		||||
    List<int> bytes = [];
 | 
			
		||||
    
 | 
			
		||||
    // Tambahkan logo jika ada path-nya
 | 
			
		||||
    if (logoPath != null && logoPath.isNotEmpty) {
 | 
			
		||||
      try {
 | 
			
		||||
        // Membaca file gambar dari path lokal dengan timeout
 | 
			
		||||
        final file = File(logoPath);
 | 
			
		||||
        bool fileExists = false;
 | 
			
		||||
        try {
 | 
			
		||||
          fileExists = await file.exists();
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          print('Error saat memeriksa keberadaan file logo: $e');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (fileExists) {
 | 
			
		||||
          // Baca file dengan timeout
 | 
			
		||||
          Uint8List imageBytes;
 | 
			
		||||
          try {
 | 
			
		||||
            imageBytes = await file.readAsBytes();
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            print('Gagal membaca file logo: $e');
 | 
			
		||||
            throw Exception('Gagal membaca file logo');
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          // Decode gambar dengan penanganan error menggunakan isolate
 | 
			
		||||
          Uint8List? processedImageBytes;
 | 
			
		||||
          try {
 | 
			
		||||
            processedImageBytes = await compute(processImageInIsolate, ImageProcessParams(imageBytes, 200));
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            print('Gagal mendecode gambar: $e');
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          if (processedImageBytes != null) {
 | 
			
		||||
            // Decode the processed image bytes back to an image object for printing
 | 
			
		||||
            final img.Image? image = img.decodeImage(processedImageBytes);
 | 
			
		||||
            if (image != null) {
 | 
			
		||||
              bytes += generator.image(image);
 | 
			
		||||
            } else {
 | 
			
		||||
              // Jika gagal decode gambar, gunakan teks sebagai fallback
 | 
			
		||||
              bytes += generator.text(storeName,
 | 
			
		||||
                  styles: PosStyles(
 | 
			
		||||
                    bold: true,
 | 
			
		||||
                    height: PosTextSize.size1,
 | 
			
		||||
                    width: PosTextSize.size1,
 | 
			
		||||
                    align: PosAlign.center,
 | 
			
		||||
                  ));
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            // Jika gagal decode gambar, gunakan teks sebagai fallback
 | 
			
		||||
            bytes += generator.text(storeName,
 | 
			
		||||
                styles: PosStyles(
 | 
			
		||||
                  bold: true,
 | 
			
		||||
                  height: PosTextSize.size1,
 | 
			
		||||
                  width: PosTextSize.size1,
 | 
			
		||||
                  align: PosAlign.center,
 | 
			
		||||
                ));
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          print('File logo tidak ditemukan: $logoPath');
 | 
			
		||||
          // Jika file tidak ditemukan, gunakan teks sebagai fallback
 | 
			
		||||
          bytes += generator.text(storeName,
 | 
			
		||||
              styles: PosStyles(
 | 
			
		||||
                bold: true,
 | 
			
		||||
                height: PosTextSize.size1,
 | 
			
		||||
                width: PosTextSize.size1,
 | 
			
		||||
                align: PosAlign.center,
 | 
			
		||||
              ));
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        print('Gagal memuat logo: $e');
 | 
			
		||||
        // Jika gagal memuat logo, gunakan teks sebagai fallback
 | 
			
		||||
        bytes += generator.text(storeName,
 | 
			
		||||
            styles: PosStyles(
 | 
			
		||||
              bold: true,
 | 
			
		||||
              height: PosTextSize.size1,
 | 
			
		||||
              width: PosTextSize.size1,
 | 
			
		||||
              align: PosAlign.center,
 | 
			
		||||
            ));
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // Jika tidak ada logo, gunakan nama toko sebagai header
 | 
			
		||||
      bytes += generator.text(storeName,
 | 
			
		||||
          styles: PosStyles(
 | 
			
		||||
            bold: true,
 | 
			
		||||
            height: PosTextSize.size1,
 | 
			
		||||
            width: PosTextSize.size1,
 | 
			
		||||
            align: PosAlign.center,
 | 
			
		||||
          ));
 | 
			
		||||
    }
 | 
			
		||||
    // Tambahkan nama toko sebagai header
 | 
			
		||||
    bytes += generator.text(storeName,
 | 
			
		||||
        styles: PosStyles(
 | 
			
		||||
          bold: true,
 | 
			
		||||
          height: PosTextSize.size1,
 | 
			
		||||
          width: PosTextSize.size1,
 | 
			
		||||
          align: PosAlign.center,
 | 
			
		||||
        ));
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      bytes += generator.text(storeAddress,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,25 +17,6 @@ class StrukTextGenerator {
 | 
			
		|||
    return char * width;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Fungsi untuk membuat representasi ASCII sederhana dari logo
 | 
			
		||||
  static String createAsciiLogo(String storeName, int width) {
 | 
			
		||||
    final buffer = StringBuffer();
 | 
			
		||||
    
 | 
			
		||||
    // Membuat logo ASCII sederhana dengan nama toko
 | 
			
		||||
    final int nameLength = storeName.length;
 | 
			
		||||
    final int boxWidth = nameLength + 4;
 | 
			
		||||
    final String horizontalLine = '=' * boxWidth;
 | 
			
		||||
    final String emptyLine = '|${' ' * (boxWidth - 2)}|';
 | 
			
		||||
    
 | 
			
		||||
    buffer.writeln(centerText(horizontalLine, width));
 | 
			
		||||
    buffer.writeln(centerText(emptyLine, width));
 | 
			
		||||
    buffer.writeln(centerText('| $storeName |', width));
 | 
			
		||||
    buffer.writeln(centerText(emptyLine, width));
 | 
			
		||||
    buffer.writeln(centerText(horizontalLine, width));
 | 
			
		||||
    
 | 
			
		||||
    return buffer.toString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Menghasilkan struk dalam format teks berdasarkan data transaksi
 | 
			
		||||
  static Future<String> generateStrukText({
 | 
			
		||||
    required List<ReceiptItem> items,
 | 
			
		||||
| 
						 | 
				
			
			@ -55,13 +36,11 @@ class StrukTextGenerator {
 | 
			
		|||
    final storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
 | 
			
		||||
    final adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
 | 
			
		||||
    final adminPhone = prefs.getString('admin_phone') ?? '08123456789';
 | 
			
		||||
    final logoPath = prefs.getString('store_logo_path'); // Load logo path
 | 
			
		||||
    
 | 
			
		||||
    print('Nama toko: $storeName');
 | 
			
		||||
    print('Alamat toko: $storeAddress');
 | 
			
		||||
    print('Nama admin: $adminName');
 | 
			
		||||
    print('Telepon admin: $adminPhone');
 | 
			
		||||
    print('Logo path: $logoPath');
 | 
			
		||||
    
 | 
			
		||||
    // Format tanggal
 | 
			
		||||
    final dateFormatter = DateFormat('dd/MM/yyyy');
 | 
			
		||||
| 
						 | 
				
			
			@ -77,9 +56,8 @@ class StrukTextGenerator {
 | 
			
		|||
    // Bangun struk dalam format teks
 | 
			
		||||
    final buffer = StringBuffer();
 | 
			
		||||
    
 | 
			
		||||
    // Header toko dengan logo ASCII - menggunakan lebar 32 karakter untuk kompatibilitas printer termal
 | 
			
		||||
    buffer.write(createAsciiLogo(storeName, 32));
 | 
			
		||||
    buffer.writeln('');
 | 
			
		||||
    // Header toko - menggunakan lebar 32 karakter untuk kompatibilitas printer termal
 | 
			
		||||
    buffer.writeln(centerText(storeName, 32));
 | 
			
		||||
    buffer.writeln(centerText(storeAddress, 32));
 | 
			
		||||
    buffer.writeln(centerText('Admin: $adminName', 32));
 | 
			
		||||
    buffer.writeln(centerText('Telp: $adminPhone', 32));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
import 'dart:io';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart' show instantiateImageCodec;
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
 | 
			
		||||
/// Utility to validate image files
 | 
			
		||||
class ImageValidator {
 | 
			
		||||
  /// Validate if a file is a valid image by trying to decode it
 | 
			
		||||
  static Future<bool> validateImageFile(String filePath) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final file = File(filePath);
 | 
			
		||||
      if (!await file.exists()) {
 | 
			
		||||
        print('File does not exist: $filePath');
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      final bytes = await file.readAsBytes();
 | 
			
		||||
      print('File size: ${bytes.length} bytes');
 | 
			
		||||
      
 | 
			
		||||
      if (bytes.isEmpty) {
 | 
			
		||||
        print('File is empty: $filePath');
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Try to decode as image
 | 
			
		||||
      final codec = await instantiateImageCodec(bytes);
 | 
			
		||||
      final frameInfo = await codec.getNextFrame();
 | 
			
		||||
      final image = frameInfo.image;
 | 
			
		||||
      
 | 
			
		||||
      print('Image dimensions: ${image.width}x${image.height}');
 | 
			
		||||
      await image.dispose();
 | 
			
		||||
      await codec.dispose();
 | 
			
		||||
      
 | 
			
		||||
      return true;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Failed to validate image file $filePath: $e');
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /// Validate images in the documents directory
 | 
			
		||||
  static Future<void> validateStoredImages() async {
 | 
			
		||||
    try {
 | 
			
		||||
      final dir = await getApplicationDocumentsDirectory();
 | 
			
		||||
      final logoDir = Directory('${dir.path}/logos');
 | 
			
		||||
      
 | 
			
		||||
      if (!await logoDir.exists()) {
 | 
			
		||||
        print('Logo directory does not exist');
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      final files = logoDir.listSync();
 | 
			
		||||
      print('Found ${files.length} files in logo directory');
 | 
			
		||||
      
 | 
			
		||||
      for (final file in files) {
 | 
			
		||||
        if (file is File) {
 | 
			
		||||
          print('Validating ${file.path}...');
 | 
			
		||||
          final isValid = await validateImageFile(file.path);
 | 
			
		||||
          print('Validation result: $isValid');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Error validating stored images: $e');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,53 +0,0 @@
 | 
			
		|||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'package:flutter/services.dart' show rootBundle;
 | 
			
		||||
 | 
			
		||||
/// Fungsi untuk menyimpan path logo toko ke shared preferences
 | 
			
		||||
Future<void> saveStoreLogoPath(String logoPath) async {
 | 
			
		||||
  final prefs = await SharedPreferences.getInstance();
 | 
			
		||||
  await prefs.setString('store_logo_path', logoPath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Fungsi untuk menghapus path logo toko dari shared preferences
 | 
			
		||||
Future<void> removeStoreLogoPath() async {
 | 
			
		||||
  final prefs = await SharedPreferences.getInstance();
 | 
			
		||||
  await prefs.remove('store_logo_path');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Fungsi untuk mengambil path logo toko dari shared preferences
 | 
			
		||||
Future<String?> getStoreLogoPath() async {
 | 
			
		||||
  final prefs = await SharedPreferences.getInstance();
 | 
			
		||||
  return prefs.getString('store_logo_path');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Fungsi untuk menyalin logo dari asset ke direktori dokumen aplikasi
 | 
			
		||||
/// dan menyimpan path-nya ke shared preferences
 | 
			
		||||
Future<void> copyAndSaveStoreLogoFromAsset(String assetPath) async {
 | 
			
		||||
  try {
 | 
			
		||||
    // Dapatkan direktori dokumen aplikasi
 | 
			
		||||
    final dir = await getApplicationDocumentsDirectory();
 | 
			
		||||
    final logoDir = Directory('${dir.path}/logos');
 | 
			
		||||
    
 | 
			
		||||
    // Buat direktori jika belum ada
 | 
			
		||||
    if (!await logoDir.exists()) {
 | 
			
		||||
      await logoDir.create(recursive: true);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Path file logo di direktori dokumen
 | 
			
		||||
    final logoFile = File('${logoDir.path}/store_logo.png');
 | 
			
		||||
    
 | 
			
		||||
    // Baca data dari asset
 | 
			
		||||
    final data = await rootBundle.load(assetPath);
 | 
			
		||||
    
 | 
			
		||||
    // Tulis data ke file di direktori dokumen
 | 
			
		||||
    await logoFile.writeAsBytes(data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
 | 
			
		||||
    
 | 
			
		||||
    // Simpan path ke shared preferences
 | 
			
		||||
    await saveStoreLogoPath(logoFile.path);
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    print('Error copying logo from asset: $e');
 | 
			
		||||
    // Jika gagal, hapus path yang mungkin tersimpan
 | 
			
		||||
    await removeStoreLogoPath();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,158 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
class PrintingStatusCard extends StatefulWidget {
 | 
			
		||||
  final bool isVisible;
 | 
			
		||||
  final VoidCallback? onDismiss;
 | 
			
		||||
 | 
			
		||||
  const PrintingStatusCard({
 | 
			
		||||
    Key? key,
 | 
			
		||||
    required this.isVisible,
 | 
			
		||||
    this.onDismiss,
 | 
			
		||||
  }) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<PrintingStatusCard> createState() => _PrintingStatusCardState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _PrintingStatusCardState extends State<PrintingStatusCard>
 | 
			
		||||
    with SingleTickerProviderStateMixin {
 | 
			
		||||
  late AnimationController _controller;
 | 
			
		||||
  late Animation<double> _scaleAnimation;
 | 
			
		||||
  late Animation<double> _fadeAnimation;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _controller = AnimationController(
 | 
			
		||||
      duration: const Duration(milliseconds: 300),
 | 
			
		||||
      vsync: this,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    _scaleAnimation = Tween<double>(
 | 
			
		||||
      begin: 0.0,
 | 
			
		||||
      end: 1.0,
 | 
			
		||||
    ).animate(CurvedAnimation(
 | 
			
		||||
      parent: _controller,
 | 
			
		||||
      curve: Curves.elasticOut,
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    _fadeAnimation = Tween<double>(
 | 
			
		||||
      begin: 0.0,
 | 
			
		||||
      end: 1.0,
 | 
			
		||||
    ).animate(CurvedAnimation(
 | 
			
		||||
      parent: _controller,
 | 
			
		||||
      curve: Curves.easeInOut,
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    // Start animation when widget is visible
 | 
			
		||||
    if (widget.isVisible) {
 | 
			
		||||
      _controller.forward();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void didUpdateWidget(covariant PrintingStatusCard oldWidget) {
 | 
			
		||||
    super.didUpdateWidget(oldWidget);
 | 
			
		||||
    if (widget.isVisible && !oldWidget.isVisible) {
 | 
			
		||||
      _controller.forward();
 | 
			
		||||
    } else if (!widget.isVisible && oldWidget.isVisible) {
 | 
			
		||||
      _controller.reverse();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _controller.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return AnimatedBuilder(
 | 
			
		||||
      animation: _controller,
 | 
			
		||||
      builder: (context, child) {
 | 
			
		||||
        return Positioned(
 | 
			
		||||
          top: 100,
 | 
			
		||||
          left: MediaQuery.of(context).size.width * 0.1,
 | 
			
		||||
          right: MediaQuery.of(context).size.width * 0.1,
 | 
			
		||||
          child: IgnorePointer(
 | 
			
		||||
            ignoring: !widget.isVisible,
 | 
			
		||||
            child: Opacity(
 | 
			
		||||
              opacity: _fadeAnimation.value,
 | 
			
		||||
              child: Transform.scale(
 | 
			
		||||
                scale: _scaleAnimation.value,
 | 
			
		||||
                child: Card(
 | 
			
		||||
                  elevation: 12,
 | 
			
		||||
                  shape: RoundedRectangleBorder(
 | 
			
		||||
                    borderRadius: BorderRadius.circular(20),
 | 
			
		||||
                  ),
 | 
			
		||||
                  child: Container(
 | 
			
		||||
                    padding: const EdgeInsets.all(20),
 | 
			
		||||
                    decoration: BoxDecoration(
 | 
			
		||||
                      borderRadius: BorderRadius.circular(20),
 | 
			
		||||
                      gradient: const LinearGradient(
 | 
			
		||||
                        begin: Alignment.topLeft,
 | 
			
		||||
                        end: Alignment.bottomRight,
 | 
			
		||||
                        colors: [
 | 
			
		||||
                          Color(0xFF6A11CB),
 | 
			
		||||
                          Color(0xFF2575FC),
 | 
			
		||||
                        ],
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                    child: Row(
 | 
			
		||||
                      mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                      children: [
 | 
			
		||||
                        const SizedBox(width: 8),
 | 
			
		||||
                        const Icon(
 | 
			
		||||
                          Icons.print,
 | 
			
		||||
                          color: Colors.white,
 | 
			
		||||
                          size: 36,
 | 
			
		||||
                        ),
 | 
			
		||||
                        const SizedBox(width: 16),
 | 
			
		||||
                        Expanded(
 | 
			
		||||
                          child: Column(
 | 
			
		||||
                            crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
                            children: [
 | 
			
		||||
                              const Text(
 | 
			
		||||
                                'Mencetak Struk',
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                  fontSize: 20,
 | 
			
		||||
                                  fontWeight: FontWeight.bold,
 | 
			
		||||
                                  color: Colors.white,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                              const SizedBox(height: 4),
 | 
			
		||||
                              const Text(
 | 
			
		||||
                                'Mohon tunggu...',
 | 
			
		||||
                                style: TextStyle(
 | 
			
		||||
                                  fontSize: 16,
 | 
			
		||||
                                  color: Colors.white70,
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                              const SizedBox(height: 12),
 | 
			
		||||
                              LinearProgressIndicator(
 | 
			
		||||
                                backgroundColor: Colors.white30,
 | 
			
		||||
                                color: Colors.white,
 | 
			
		||||
                                minHeight: 6,
 | 
			
		||||
                                value: null,
 | 
			
		||||
                              ),
 | 
			
		||||
                            ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                        IconButton(
 | 
			
		||||
                          icon: const Icon(Icons.close, color: Colors.white70),
 | 
			
		||||
                          onPressed: widget.onDismiss,
 | 
			
		||||
                        ),
 | 
			
		||||
                      ],
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,5 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
import 'package:image_picker/image_picker.dart';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
/// Widget untuk dialog konfigurasi informasi toko
 | 
			
		||||
class StoreInfoConfigDialog extends StatefulWidget {
 | 
			
		||||
| 
						 | 
				
			
			@ -19,10 +17,6 @@ class _StoreInfoConfigDialogState extends State<StoreInfoConfigDialog> {
 | 
			
		|||
  final TextEditingController _storeAddressController = TextEditingController();
 | 
			
		||||
  final TextEditingController _adminNameController = TextEditingController();
 | 
			
		||||
  final TextEditingController _adminPhoneController = TextEditingController();
 | 
			
		||||
  
 | 
			
		||||
  // Variabel untuk logo
 | 
			
		||||
  String? _logoPath;
 | 
			
		||||
  final ImagePicker _picker = ImagePicker();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +34,6 @@ class _StoreInfoConfigDialogState extends State<StoreInfoConfigDialog> {
 | 
			
		|||
      _storeAddressController.text = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
 | 
			
		||||
      _adminNameController.text = prefs.getString('admin_name') ?? 'Budi Santoso';
 | 
			
		||||
      _adminPhoneController.text = prefs.getString('admin_phone') ?? '08123456789';
 | 
			
		||||
      _logoPath = prefs.getString('store_logo_path'); // Load logo path
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,12 +46,6 @@ class _StoreInfoConfigDialogState extends State<StoreInfoConfigDialog> {
 | 
			
		|||
      await prefs.setString('store_address', _storeAddressController.text);
 | 
			
		||||
      await prefs.setString('admin_name', _adminNameController.text);
 | 
			
		||||
      await prefs.setString('admin_phone', _adminPhoneController.text);
 | 
			
		||||
      // Save logo path
 | 
			
		||||
      if (_logoPath != null) {
 | 
			
		||||
        await prefs.setString('store_logo_path', _logoPath!);
 | 
			
		||||
      } else {
 | 
			
		||||
        await prefs.remove('store_logo_path');
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        Navigator.of(context).pop(true); // Kembali dengan nilai true jika berhasil disimpan
 | 
			
		||||
| 
						 | 
				
			
			@ -66,17 +53,6 @@ class _StoreInfoConfigDialogState extends State<StoreInfoConfigDialog> {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Memilih logo dari galeri
 | 
			
		||||
  Future<void> _pickImage() async {
 | 
			
		||||
    final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
 | 
			
		||||
    
 | 
			
		||||
    if (image != null && mounted) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        _logoPath = image.path;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _storeNameController.dispose();
 | 
			
		||||
| 
						 | 
				
			
			@ -96,28 +72,6 @@ class _StoreInfoConfigDialogState extends State<StoreInfoConfigDialog> {
 | 
			
		|||
          child: Column(
 | 
			
		||||
            mainAxisSize: MainAxisSize.min,
 | 
			
		||||
            children: [
 | 
			
		||||
              // Display selected logo or a placeholder
 | 
			
		||||
              GestureDetector(
 | 
			
		||||
                onTap: _pickImage,
 | 
			
		||||
                child: Container(
 | 
			
		||||
                  height: 100,
 | 
			
		||||
                  width: 100,
 | 
			
		||||
                  decoration: BoxDecoration(
 | 
			
		||||
                    border: Border.all(color: Colors.grey),
 | 
			
		||||
                    borderRadius: BorderRadius.circular(8),
 | 
			
		||||
                  ),
 | 
			
		||||
                  child: _logoPath != null
 | 
			
		||||
                      ? ClipRRect(
 | 
			
		||||
                          borderRadius: BorderRadius.circular(8),
 | 
			
		||||
                          child: Image.file(File(_logoPath!), fit: BoxFit.cover),
 | 
			
		||||
                        )
 | 
			
		||||
                      : const Icon(Icons.add_a_photo, size: 40, color: Colors.grey),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              const SizedBox(height: 8),
 | 
			
		||||
              const Text('Ketuk untuk memilih logo toko', style: TextStyle(fontSize: 12)),
 | 
			
		||||
              const SizedBox(height: 16),
 | 
			
		||||
              
 | 
			
		||||
              TextFormField(
 | 
			
		||||
                controller: _storeNameController,
 | 
			
		||||
                decoration: const InputDecoration(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:google_fonts/google_fonts.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'package:intl/intl.dart';
 | 
			
		||||
 | 
			
		||||
/// Widget untuk menampilkan informasi toko dan admin
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +19,6 @@ class _StoreInfoWidgetState extends State<StoreInfoWidget> {
 | 
			
		|||
  String storeAddress = 'Jl. Merdeka No. 123';
 | 
			
		||||
  String adminName = 'Budi Santoso';
 | 
			
		||||
  String adminPhone = '08123456789';
 | 
			
		||||
  String? _logoPath; // Path to the store logo
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +35,6 @@ class _StoreInfoWidgetState extends State<StoreInfoWidget> {
 | 
			
		|||
      storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
 | 
			
		||||
      adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
 | 
			
		||||
      adminPhone = prefs.getString('admin_phone') ?? '08123456789';
 | 
			
		||||
      _logoPath = prefs.getString('store_logo_path'); // Load logo path
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -61,17 +58,6 @@ class _StoreInfoWidgetState extends State<StoreInfoWidget> {
 | 
			
		|||
        color: Colors.white,
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            // Display store logo if available
 | 
			
		||||
            if (_logoPath != null && _logoPath!.isNotEmpty)
 | 
			
		||||
              Container(
 | 
			
		||||
                margin: const EdgeInsets.only(bottom: 8.0),
 | 
			
		||||
                child: Image.file(
 | 
			
		||||
                  File(_logoPath!),
 | 
			
		||||
                  height: 60,
 | 
			
		||||
                  width: 60,
 | 
			
		||||
                  fit: BoxFit.contain,
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            Text(
 | 
			
		||||
              storeName,
 | 
			
		||||
              style: courierPrime.copyWith(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue