v1.2.0: foto produk di kasir (grid + keranjang) + fix MIME jpg->jpeg
This commit is contained in:
@@ -8,6 +8,7 @@ import '../models/product.dart';
|
||||
import '../models/sale.dart';
|
||||
import '../state/app_state.dart';
|
||||
import '../services/printer_service.dart';
|
||||
import 'produk_screen.dart' show productImageUrl;
|
||||
import '../utils/colors.dart';
|
||||
import '../utils/format.dart';
|
||||
import '../utils/rupiah_input.dart';
|
||||
@@ -1138,19 +1139,7 @@ class _CartSheetContent extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 18,
|
||||
backgroundColor: kAccent.withValues(alpha: 0.15),
|
||||
child: Text(
|
||||
line.product.name.isNotEmpty
|
||||
? line.product.name[0].toUpperCase()
|
||||
: '?',
|
||||
style: TextStyle(
|
||||
color: kAccent,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 13),
|
||||
),
|
||||
),
|
||||
_KasirAvatar(product: line.product, radius: 18),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@@ -1490,19 +1479,7 @@ class _ProductCard extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 18,
|
||||
backgroundColor: kAccent.withValues(alpha: 0.15),
|
||||
child: Text(
|
||||
product.name.isNotEmpty
|
||||
? product.name[0].toUpperCase()
|
||||
: '?',
|
||||
style: TextStyle(
|
||||
color: kAccent,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
_KasirAvatar(product: product, radius: 18),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -1579,6 +1556,34 @@ class _ProductCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Avatar produk di kasir: foto jika ada, fallback huruf pertama nama.
|
||||
class _KasirAvatar extends StatelessWidget {
|
||||
final Product product;
|
||||
final double radius;
|
||||
const _KasirAvatar({required this.product, this.radius = 18});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final url = productImageUrl(product.imagePath);
|
||||
return CircleAvatar(
|
||||
radius: radius,
|
||||
backgroundColor: kAccent.withValues(alpha: 0.15),
|
||||
backgroundImage: url != null ? NetworkImage(url) : null,
|
||||
child: url == null
|
||||
? Text(
|
||||
product.name.isNotEmpty
|
||||
? product.name[0].toUpperCase()
|
||||
: '?',
|
||||
style: TextStyle(
|
||||
color: kAccent,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: radius * 0.75),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QtyButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@@ -40,12 +40,14 @@ Future<String?> pickAndUploadProductImage(BuildContext context,
|
||||
final raw = picked.name.substring(dot + 1).toLowerCase();
|
||||
if (['jpg', 'jpeg', 'png', 'webp'].contains(raw)) ext = raw;
|
||||
}
|
||||
// MIME resmi: jpg -> image/jpeg (bukan image/jpg, ditolak server 415)
|
||||
final mime = ext == 'jpg' ? 'image/jpeg' : 'image/$ext';
|
||||
final path =
|
||||
'prod-${DateTime.now().millisecondsSinceEpoch}.$ext';
|
||||
await Supabase.instance.client.storage
|
||||
.from(kProductImageBucket)
|
||||
.uploadBinary(path, bytes,
|
||||
fileOptions: FileOptions(contentType: 'image/$ext', upsert: true));
|
||||
fileOptions: FileOptions(contentType: mime, upsert: true));
|
||||
return path;
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
|
||||
Reference in New Issue
Block a user