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:cashumit/screens/transaction_screen.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cashumit/screens/receipt_screen.dart';
|
import 'package:cashumit/screens/receipt_screen.dart';
|
||||||
import 'package:cashumit/utils/store_logo_utils.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:cashumit/providers/receipt_provider.dart';
|
import 'package:cashumit/providers/receipt_provider.dart';
|
||||||
|
|
||||||
|
@ -10,9 +9,6 @@ void main() async {
|
||||||
// Ensure WidgetsFlutterBinding is initialized for async operations
|
// Ensure WidgetsFlutterBinding is initialized for async operations
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// Initialize the store logo from asset
|
|
||||||
await copyAndSaveStoreLogoFromAsset('assets/images/store_logo.png');
|
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -30,13 +30,11 @@ class EscPosPrintService {
|
||||||
final storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
|
final storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
|
||||||
final adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
|
final adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
|
||||||
final adminPhone = prefs.getString('admin_phone') ?? '08123456789';
|
final adminPhone = prefs.getString('admin_phone') ?? '08123456789';
|
||||||
final logoPath = prefs.getString('store_logo_path'); // Load logo path
|
|
||||||
|
|
||||||
print('Nama toko: $storeName');
|
print('Nama toko: $storeName');
|
||||||
print('Alamat toko: $storeAddress');
|
print('Alamat toko: $storeAddress');
|
||||||
print('Nama admin: $adminName');
|
print('Nama admin: $adminName');
|
||||||
print('Telepon admin: $adminPhone');
|
print('Telepon admin: $adminPhone');
|
||||||
print('Logo path: $logoPath');
|
|
||||||
|
|
||||||
// Format tanggal
|
// Format tanggal
|
||||||
final dateFormatter = DateFormat('dd/MM/yyyy HH:mm');
|
final dateFormatter = DateFormat('dd/MM/yyyy HH:mm');
|
||||||
|
@ -64,93 +62,14 @@ class EscPosPrintService {
|
||||||
// Mulai dengan inisialisasi printer
|
// Mulai dengan inisialisasi printer
|
||||||
List<int> bytes = [];
|
List<int> bytes = [];
|
||||||
|
|
||||||
// Tambahkan logo jika ada path-nya
|
// Tambahkan nama toko sebagai header
|
||||||
if (logoPath != null && logoPath.isNotEmpty) {
|
bytes += generator.text(storeName,
|
||||||
try {
|
styles: PosStyles(
|
||||||
// Membaca file gambar dari path lokal dengan timeout
|
bold: true,
|
||||||
final file = File(logoPath);
|
height: PosTextSize.size1,
|
||||||
bool fileExists = false;
|
width: PosTextSize.size1,
|
||||||
try {
|
align: PosAlign.center,
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bytes += generator.text(storeAddress,
|
bytes += generator.text(storeAddress,
|
||||||
|
|
|
@ -17,25 +17,6 @@ class StrukTextGenerator {
|
||||||
return char * width;
|
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
|
/// Menghasilkan struk dalam format teks berdasarkan data transaksi
|
||||||
static Future<String> generateStrukText({
|
static Future<String> generateStrukText({
|
||||||
required List<ReceiptItem> items,
|
required List<ReceiptItem> items,
|
||||||
|
@ -55,13 +36,11 @@ class StrukTextGenerator {
|
||||||
final storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
|
final storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
|
||||||
final adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
|
final adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
|
||||||
final adminPhone = prefs.getString('admin_phone') ?? '08123456789';
|
final adminPhone = prefs.getString('admin_phone') ?? '08123456789';
|
||||||
final logoPath = prefs.getString('store_logo_path'); // Load logo path
|
|
||||||
|
|
||||||
print('Nama toko: $storeName');
|
print('Nama toko: $storeName');
|
||||||
print('Alamat toko: $storeAddress');
|
print('Alamat toko: $storeAddress');
|
||||||
print('Nama admin: $adminName');
|
print('Nama admin: $adminName');
|
||||||
print('Telepon admin: $adminPhone');
|
print('Telepon admin: $adminPhone');
|
||||||
print('Logo path: $logoPath');
|
|
||||||
|
|
||||||
// Format tanggal
|
// Format tanggal
|
||||||
final dateFormatter = DateFormat('dd/MM/yyyy');
|
final dateFormatter = DateFormat('dd/MM/yyyy');
|
||||||
|
@ -77,9 +56,8 @@ class StrukTextGenerator {
|
||||||
// Bangun struk dalam format teks
|
// Bangun struk dalam format teks
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
// Header toko dengan logo ASCII - menggunakan lebar 32 karakter untuk kompatibilitas printer termal
|
// Header toko - menggunakan lebar 32 karakter untuk kompatibilitas printer termal
|
||||||
buffer.write(createAsciiLogo(storeName, 32));
|
buffer.writeln(centerText(storeName, 32));
|
||||||
buffer.writeln('');
|
|
||||||
buffer.writeln(centerText(storeAddress, 32));
|
buffer.writeln(centerText(storeAddress, 32));
|
||||||
buffer.writeln(centerText('Admin: $adminName', 32));
|
buffer.writeln(centerText('Admin: $adminName', 32));
|
||||||
buffer.writeln(centerText('Telp: $adminPhone', 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:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
/// Widget untuk dialog konfigurasi informasi toko
|
/// Widget untuk dialog konfigurasi informasi toko
|
||||||
class StoreInfoConfigDialog extends StatefulWidget {
|
class StoreInfoConfigDialog extends StatefulWidget {
|
||||||
|
@ -19,10 +17,6 @@ class _StoreInfoConfigDialogState extends State<StoreInfoConfigDialog> {
|
||||||
final TextEditingController _storeAddressController = TextEditingController();
|
final TextEditingController _storeAddressController = TextEditingController();
|
||||||
final TextEditingController _adminNameController = TextEditingController();
|
final TextEditingController _adminNameController = TextEditingController();
|
||||||
final TextEditingController _adminPhoneController = TextEditingController();
|
final TextEditingController _adminPhoneController = TextEditingController();
|
||||||
|
|
||||||
// Variabel untuk logo
|
|
||||||
String? _logoPath;
|
|
||||||
final ImagePicker _picker = ImagePicker();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -40,7 +34,6 @@ class _StoreInfoConfigDialogState extends State<StoreInfoConfigDialog> {
|
||||||
_storeAddressController.text = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
|
_storeAddressController.text = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
|
||||||
_adminNameController.text = prefs.getString('admin_name') ?? 'Budi Santoso';
|
_adminNameController.text = prefs.getString('admin_name') ?? 'Budi Santoso';
|
||||||
_adminPhoneController.text = prefs.getString('admin_phone') ?? '08123456789';
|
_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('store_address', _storeAddressController.text);
|
||||||
await prefs.setString('admin_name', _adminNameController.text);
|
await prefs.setString('admin_name', _adminNameController.text);
|
||||||
await prefs.setString('admin_phone', _adminPhoneController.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) {
|
if (mounted) {
|
||||||
Navigator.of(context).pop(true); // Kembali dengan nilai true jika berhasil disimpan
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_storeNameController.dispose();
|
_storeNameController.dispose();
|
||||||
|
@ -96,28 +72,6 @@ class _StoreInfoConfigDialogState extends State<StoreInfoConfigDialog> {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
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(
|
TextFormField(
|
||||||
controller: _storeNameController,
|
controller: _storeNameController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
/// Widget untuk menampilkan informasi toko dan admin
|
/// Widget untuk menampilkan informasi toko dan admin
|
||||||
|
@ -20,7 +19,6 @@ class _StoreInfoWidgetState extends State<StoreInfoWidget> {
|
||||||
String storeAddress = 'Jl. Merdeka No. 123';
|
String storeAddress = 'Jl. Merdeka No. 123';
|
||||||
String adminName = 'Budi Santoso';
|
String adminName = 'Budi Santoso';
|
||||||
String adminPhone = '08123456789';
|
String adminPhone = '08123456789';
|
||||||
String? _logoPath; // Path to the store logo
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -37,7 +35,6 @@ class _StoreInfoWidgetState extends State<StoreInfoWidget> {
|
||||||
storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
|
storeAddress = prefs.getString('store_address') ?? 'Jl. Merdeka No. 123';
|
||||||
adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
|
adminName = prefs.getString('admin_name') ?? 'Budi Santoso';
|
||||||
adminPhone = prefs.getString('admin_phone') ?? '08123456789';
|
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,
|
color: Colors.white,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
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(
|
Text(
|
||||||
storeName,
|
storeName,
|
||||||
style: courierPrime.copyWith(
|
style: courierPrime.copyWith(
|
||||||
|
|
Loading…
Reference in New Issue