Fitur foto produk: image_picker + upload Supabase Storage (bucket product-images), thumbnail di daftar produk
This commit is contained in:
@@ -9,6 +9,17 @@
|
|||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
|
||||||
|
<!-- image_picker: package visibility untuk intent kamera/galeri (Android 11+) -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.GET_CONTENT" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="Dutra Store"
|
android:label="Dutra Store"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class Product {
|
|||||||
final int stock;
|
final int stock;
|
||||||
final int minStock;
|
final int minStock;
|
||||||
final double hargaPokok;
|
final double hargaPokok;
|
||||||
|
final String? imagePath; // path file di Supabase Storage (bucket product-images)
|
||||||
|
|
||||||
Product({
|
Product({
|
||||||
required this.id,
|
required this.id,
|
||||||
@@ -19,6 +20,7 @@ class Product {
|
|||||||
required this.stock,
|
required this.stock,
|
||||||
this.minStock = 5,
|
this.minStock = 5,
|
||||||
this.hargaPokok = 0,
|
this.hargaPokok = 0,
|
||||||
|
this.imagePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Product.manual(String name, double price) {
|
factory Product.manual(String name, double price) {
|
||||||
@@ -48,6 +50,7 @@ class Product {
|
|||||||
hargaPokok: json['harga_pokok'] != null
|
hargaPokok: json['harga_pokok'] != null
|
||||||
? (json['harga_pokok'] as num).toDouble()
|
? (json['harga_pokok'] as num).toDouble()
|
||||||
: 0,
|
: 0,
|
||||||
|
imagePath: json['image_path'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,5 +63,6 @@ class Product {
|
|||||||
'stock': stock,
|
'stock': stock,
|
||||||
'min_stock': minStock,
|
'min_stock': minStock,
|
||||||
'harga_pokok': hargaPokok,
|
'harga_pokok': hargaPokok,
|
||||||
|
'image_path': imagePath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
||||||
import '../models/product.dart';
|
import '../models/product.dart';
|
||||||
import '../state/app_state.dart';
|
import '../state/app_state.dart';
|
||||||
@@ -10,6 +12,144 @@ import '../utils/format.dart';
|
|||||||
import '../utils/rupiah_input.dart';
|
import '../utils/rupiah_input.dart';
|
||||||
import 'scan_screen.dart';
|
import 'scan_screen.dart';
|
||||||
|
|
||||||
|
const String kProductImageBucket = 'product-images';
|
||||||
|
|
||||||
|
/// URL publik gambar produk dari Supabase Storage (null jika tidak ada).
|
||||||
|
String? productImageUrl(String? path) {
|
||||||
|
if (path == null || path.isEmpty) return null;
|
||||||
|
return Supabase.instance.client.storage.from(kProductImageBucket).getPublicUrl(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pilih gambar (galeri/kamera), kompres otomatis, lalu upload ke Storage.
|
||||||
|
/// Mengembalikan path file di bucket, atau null jika batal/gagal.
|
||||||
|
Future<String?> pickAndUploadProductImage(BuildContext context,
|
||||||
|
{ImageSource source = ImageSource.gallery}) async {
|
||||||
|
final picker = ImagePicker();
|
||||||
|
try {
|
||||||
|
final picked = await picker.pickImage(
|
||||||
|
source: source,
|
||||||
|
maxWidth: 800,
|
||||||
|
maxHeight: 800,
|
||||||
|
imageQuality: 80,
|
||||||
|
);
|
||||||
|
if (picked == null) return null;
|
||||||
|
final bytes = await picked.readAsBytes();
|
||||||
|
var ext = 'jpg';
|
||||||
|
final dot = picked.name.lastIndexOf('.');
|
||||||
|
if (dot > -1) {
|
||||||
|
final raw = picked.name.substring(dot + 1).toLowerCase();
|
||||||
|
if (['jpg', 'jpeg', 'png', 'webp'].contains(raw)) ext = raw;
|
||||||
|
}
|
||||||
|
final path =
|
||||||
|
'prod-${DateTime.now().millisecondsSinceEpoch}.$ext';
|
||||||
|
await Supabase.instance.client.storage
|
||||||
|
.from(kProductImageBucket)
|
||||||
|
.uploadBinary(path, bytes,
|
||||||
|
fileOptions: FileOptions(contentType: 'image/$ext', upsert: true));
|
||||||
|
return path;
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text('Upload gambar gagal: $e'),
|
||||||
|
backgroundColor: kDanger,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widget preview + tombol pilih/hapus gambar untuk form produk.
|
||||||
|
Widget productImageField({
|
||||||
|
required BuildContext context,
|
||||||
|
required void Function(String? path) onChanged,
|
||||||
|
required String? currentPath,
|
||||||
|
bool enabled = true,
|
||||||
|
}) {
|
||||||
|
final url = productImageUrl(currentPath);
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 72,
|
||||||
|
height: 72,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kAccent.withValues(alpha: 0.10),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: kBorder),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: url != null
|
||||||
|
? Image.network(url, fit: BoxFit.cover, errorBuilder: (_, __, ___) =>
|
||||||
|
const Center(child: Icon(Icons.broken_image_outlined, color: kMuted, size: 26)))
|
||||||
|
: const Center(
|
||||||
|
child: Icon(Icons.image_outlined, color: kMuted, size: 26)),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text('Foto produk',
|
||||||
|
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
url != null ? 'Foto tersimpan' : 'Belum ada foto (opsional)',
|
||||||
|
style: const TextStyle(fontSize: 11, color: kMuted),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
|
children: [
|
||||||
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.photo_camera_outlined, size: 16),
|
||||||
|
label: const Text('Kamera', style: TextStyle(fontSize: 12)),
|
||||||
|
onPressed: !enabled
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final p = await pickAndUploadProductImage(context,
|
||||||
|
source: ImageSource.camera);
|
||||||
|
if (p != null) onChanged(p);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.photo_library_outlined, size: 16),
|
||||||
|
label: const Text('Galeri', style: TextStyle(fontSize: 12)),
|
||||||
|
onPressed: !enabled
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final p = await pickAndUploadProductImage(context);
|
||||||
|
if (p != null) onChanged(p);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (currentPath != null && currentPath.isNotEmpty)
|
||||||
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.delete_outline,
|
||||||
|
size: 16, color: kDanger),
|
||||||
|
label: const Text('Hapus',
|
||||||
|
style: TextStyle(fontSize: 12, color: kDanger)),
|
||||||
|
side: const BorderSide(color: kBorder),
|
||||||
|
onPressed: !enabled
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
// Best-effort hapus file lama di storage
|
||||||
|
Supabase.instance.client.storage
|
||||||
|
.from(kProductImageBucket)
|
||||||
|
.remove([currentPath]).ignore();
|
||||||
|
onChanged(null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> confirmResellerWarning(BuildContext context, double general, double reseller) async {
|
Future<bool> confirmResellerWarning(BuildContext context, double general, double reseller) async {
|
||||||
if (reseller <= 0 || reseller <= general) return true;
|
if (reseller <= 0 || reseller <= general) return true;
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
@@ -186,6 +326,7 @@ void _showAddForm(BuildContext context, AppState state) {
|
|||||||
final stock = TextEditingController();
|
final stock = TextEditingController();
|
||||||
final minStockCtrl = TextEditingController(text: '5');
|
final minStockCtrl = TextEditingController(text: '5');
|
||||||
final hargaPokokCtrl = TextEditingController();
|
final hargaPokokCtrl = TextEditingController();
|
||||||
|
String? imagePath; // path gambar di Supabase Storage
|
||||||
final focusNodes = <FocusNode>[
|
final focusNodes = <FocusNode>[
|
||||||
FocusNode(),
|
FocusNode(),
|
||||||
FocusNode(),
|
FocusNode(),
|
||||||
@@ -274,6 +415,16 @@ void _showAddForm(BuildContext context, AppState state) {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
StatefulBuilder(builder: (ctx, setSheetState) {
|
||||||
|
return productImageField(
|
||||||
|
context: context,
|
||||||
|
currentPath: imagePath,
|
||||||
|
onChanged: (p) {
|
||||||
|
imagePath = p;
|
||||||
|
setSheetState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
TextField(
|
TextField(
|
||||||
controller: barcode,
|
controller: barcode,
|
||||||
focusNode: focusNodes[0],
|
focusNode: focusNodes[0],
|
||||||
@@ -387,7 +538,7 @@ void _showAddForm(BuildContext context, AppState state) {
|
|||||||
.toDouble();
|
.toDouble();
|
||||||
if (n.isEmpty || pr <= 0) return;
|
if (n.isEmpty || pr <= 0) return;
|
||||||
if (!await confirmResellerWarning(context, pr, prR)) return;
|
if (!await confirmResellerWarning(context, pr, prR)) return;
|
||||||
final err = await state.addProduct(n, bc, pr, prR, st, ms, hp);
|
final err = await state.addProduct(n, bc, pr, prR, st, ms, hp, imagePath: imagePath);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
final messenger = ScaffoldMessenger.of(context);
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
if (err == null) {
|
if (err == null) {
|
||||||
@@ -443,6 +594,7 @@ void _showEditDialog(BuildContext context, AppState state, Product product) {
|
|||||||
text: product.hargaPokok > 0
|
text: product.hargaPokok > 0
|
||||||
? RupiahInputFormatter.formatNumber(product.hargaPokok.toInt())
|
? RupiahInputFormatter.formatNumber(product.hargaPokok.toInt())
|
||||||
: '');
|
: '');
|
||||||
|
String? imagePath = product.imagePath; // path gambar saat ini (null = tidak ada)
|
||||||
final focusNodes = <FocusNode>[
|
final focusNodes = <FocusNode>[
|
||||||
FocusNode(),
|
FocusNode(),
|
||||||
FocusNode(),
|
FocusNode(),
|
||||||
@@ -532,6 +684,16 @@ void _showEditDialog(BuildContext context, AppState state, Product product) {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
StatefulBuilder(builder: (ctx, setSheetState) {
|
||||||
|
return productImageField(
|
||||||
|
context: context,
|
||||||
|
currentPath: imagePath,
|
||||||
|
onChanged: (p) {
|
||||||
|
imagePath = p;
|
||||||
|
setSheetState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
TextField(
|
TextField(
|
||||||
controller: barcode,
|
controller: barcode,
|
||||||
focusNode: focusNodes[0],
|
focusNode: focusNodes[0],
|
||||||
@@ -608,7 +770,7 @@ void _showEditDialog(BuildContext context, AppState state, Product product) {
|
|||||||
.toDouble();
|
.toDouble();
|
||||||
if (n.isEmpty || pr <= 0) return;
|
if (n.isEmpty || pr <= 0) return;
|
||||||
if (!await confirmResellerWarning(context, pr, prR)) return;
|
if (!await confirmResellerWarning(context, pr, prR)) return;
|
||||||
final err = await state.updateProduct(product.id, n, bc, pr, prR, st, ms, hp);
|
final err = await state.updateProduct(product.id, n, bc, pr, prR, st, ms, hp, imagePath: imagePath);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
final messenger = ScaffoldMessenger.of(context);
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
if (err == null) {
|
if (err == null) {
|
||||||
@@ -663,11 +825,16 @@ class _ProductRow extends StatelessWidget {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 22,
|
radius: 22,
|
||||||
backgroundColor: kAccent.withValues(alpha: 0.15),
|
backgroundColor: kAccent.withValues(alpha: 0.15),
|
||||||
child: Text(
|
backgroundImage: productImageUrl(p.imagePath) != null
|
||||||
p.name.isNotEmpty ? p.name[0].toUpperCase() : '?',
|
? NetworkImage(productImageUrl(p.imagePath)!)
|
||||||
style:
|
: null,
|
||||||
TextStyle(color: kAccent, fontWeight: FontWeight.w700),
|
child: productImageUrl(p.imagePath) == null
|
||||||
),
|
? Text(
|
||||||
|
p.name.isNotEmpty ? p.name[0].toUpperCase() : '?',
|
||||||
|
style:
|
||||||
|
TextStyle(color: kAccent, fontWeight: FontWeight.w700),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
@@ -502,7 +502,8 @@ class AppState extends ChangeNotifier {
|
|||||||
|
|
||||||
/* ---------- Produk ---------- */
|
/* ---------- Produk ---------- */
|
||||||
Future<String?> addProduct(String name, String barcode, double price,
|
Future<String?> addProduct(String name, String barcode, double price,
|
||||||
double priceReseller, int stock, int minStock, double hargaPokok) async {
|
double priceReseller, int stock, int minStock, double hargaPokok,
|
||||||
|
{String? imagePath}) async {
|
||||||
try {
|
try {
|
||||||
final trimmed = barcode.trim();
|
final trimmed = barcode.trim();
|
||||||
if (trimmed.isNotEmpty &&
|
if (trimmed.isNotEmpty &&
|
||||||
@@ -518,6 +519,7 @@ class AppState extends ChangeNotifier {
|
|||||||
stock: stock,
|
stock: stock,
|
||||||
minStock: minStock,
|
minStock: minStock,
|
||||||
hargaPokok: hargaPokok,
|
hargaPokok: hargaPokok,
|
||||||
|
imagePath: imagePath,
|
||||||
);
|
);
|
||||||
await _withTimeout(
|
await _withTimeout(
|
||||||
_supabase.from('products').insert(product.toJson()..['store_id'] = _currentStoreId),
|
_supabase.from('products').insert(product.toJson()..['store_id'] = _currentStoreId),
|
||||||
@@ -534,7 +536,8 @@ class AppState extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> updateProduct(String id, String name, String barcode,
|
Future<String?> updateProduct(String id, String name, String barcode,
|
||||||
double price, double priceReseller, int stock, int minStock, double hargaPokok) async {
|
double price, double priceReseller, int stock, int minStock, double hargaPokok,
|
||||||
|
{String? imagePath}) async {
|
||||||
try {
|
try {
|
||||||
final trimmed = barcode.trim();
|
final trimmed = barcode.trim();
|
||||||
if (trimmed.isNotEmpty &&
|
if (trimmed.isNotEmpty &&
|
||||||
@@ -551,6 +554,7 @@ class AppState extends ChangeNotifier {
|
|||||||
'min_stock': minStock,
|
'min_stock': minStock,
|
||||||
'harga_pokok': hargaPokok,
|
'harga_pokok': hargaPokok,
|
||||||
'store_id': _currentStoreId,
|
'store_id': _currentStoreId,
|
||||||
|
'image_path': imagePath,
|
||||||
}).eq('id', id),
|
}).eq('id', id),
|
||||||
'Update product',
|
'Update product',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,10 +6,14 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <gtk/gtk_plugin.h>
|
#include <gtk/gtk_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
|
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
||||||
gtk_plugin_register_with_registrar(gtk_registrar);
|
gtk_plugin_register_with_registrar(gtk_registrar);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
file_selector_linux
|
||||||
gtk
|
gtk
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Foundation
|
|||||||
|
|
||||||
import app_links
|
import app_links
|
||||||
import connectivity_plus
|
import connectivity_plus
|
||||||
|
import file_selector_macos
|
||||||
import mobile_scanner
|
import mobile_scanner
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
@@ -18,6 +19,7 @@ import url_launcher_macos
|
|||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||||
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
|||||||
104
pubspec.lock
104
pubspec.lock
@@ -217,6 +217,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
file_selector_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_linux
|
||||||
|
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.4"
|
||||||
|
file_selector_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_macos
|
||||||
|
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.5"
|
||||||
|
file_selector_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_platform_interface
|
||||||
|
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
file_selector_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_windows
|
||||||
|
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+5"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -238,6 +270,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.34"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -304,6 +344,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.0"
|
||||||
|
image_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_picker
|
||||||
|
sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
image_picker_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_android
|
||||||
|
sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.13+17"
|
||||||
|
image_picker_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_for_web
|
||||||
|
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
image_picker_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_ios
|
||||||
|
sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.13+3"
|
||||||
|
image_picker_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_linux
|
||||||
|
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
|
image_picker_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_macos
|
||||||
|
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2+1"
|
||||||
|
image_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_platform_interface
|
||||||
|
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.11.1"
|
||||||
|
image_picker_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_windows
|
||||||
|
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
jni:
|
jni:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ dependencies:
|
|||||||
esc_pos_utils_plus: ^2.0.4
|
esc_pos_utils_plus: ^2.0.4
|
||||||
uuid: ^4.0.0
|
uuid: ^4.0.0
|
||||||
package_info_plus: ^8.0.0
|
package_info_plus: ^8.0.0
|
||||||
|
image_picker: ^1.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <app_links/app_links_plugin_c_api.h>
|
#include <app_links/app_links_plugin_c_api.h>
|
||||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||||
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <print_bluetooth_thermal/print_bluetooth_thermal_plugin_c_api.h>
|
#include <print_bluetooth_thermal/print_bluetooth_thermal_plugin_c_api.h>
|
||||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
@@ -17,6 +18,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
||||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
PrintBluetoothThermalPluginCApiRegisterWithRegistrar(
|
PrintBluetoothThermalPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("PrintBluetoothThermalPluginCApi"));
|
registry->GetRegistrarForPlugin("PrintBluetoothThermalPluginCApi"));
|
||||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
app_links
|
app_links
|
||||||
connectivity_plus
|
connectivity_plus
|
||||||
|
file_selector_windows
|
||||||
print_bluetooth_thermal
|
print_bluetooth_thermal
|
||||||
share_plus
|
share_plus
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
|
|||||||
Reference in New Issue
Block a user