memperbarui beberapa fitur

This commit is contained in:
rhanarmt 2026-05-14 21:59:17 +07:00
parent 16594b95e6
commit b0ed5950c2
8 changed files with 1550 additions and 520 deletions

View File

@ -38,13 +38,11 @@ class DashboardController extends ChangeNotifier {
MLService.getBahanDigunakanHariIni(), MLService.getBahanDigunakanHariIni(),
MLService.getDashboardSummary(), MLService.getDashboardSummary(),
MLService.getProducts(), MLService.getProducts(),
MLService.getReportCritical(),
]); ]);
final bahanDigunakan = results[0] as Map<String, dynamic>; final bahanDigunakan = results[0] as Map<String, dynamic>;
final summary = results[1] as Map<String, dynamic>; final summary = results[1] as Map<String, dynamic>;
final products = results[2] as List; final products = results[2] as List;
final critical = results[3] as Map<String, dynamic>;
_applyBahanDigunakanHariIni(bahanDigunakan); _applyBahanDigunakanHariIni(bahanDigunakan);
@ -56,18 +54,13 @@ class DashboardController extends ChangeNotifier {
} }
totalProduk = products.length; totalProduk = products.length;
_applyLowStockProducts(products);
if (critical['status'] == true) {
_applyLowStockItems(critical['data']);
} else {
_applyDummyLowStock();
}
} catch (e) { } catch (e) {
errorMessage = 'Gagal memuat dashboard: $e'; errorMessage = 'Gagal memuat dashboard: $e';
bahanDigunakanError = 'Gagal memuat bahan digunakan hari ini'; bahanDigunakanError = 'Gagal memuat bahan digunakan hari ini';
_resetBahanDigunakanHariIni(); _resetBahanDigunakanHariIni();
_applyDummyPenggunaan(); _applyDummyPenggunaan();
_applyDummyLowStock(); lowStockItems.clear();
} finally { } finally {
isLoading = false; isLoading = false;
isBahanDigunakanLoading = false; isBahanDigunakanLoading = false;
@ -109,25 +102,31 @@ class DashboardController extends ChangeNotifier {
} }
} }
void _applyLowStockItems(dynamic data) { void _applyLowStockProducts(List<dynamic> products) {
lowStockItems.clear(); lowStockItems.clear();
if (data is! List || data.isEmpty) {
_applyDummyLowStock();
return;
}
for (final item in data) { for (final item in products) {
final stockValue = StockStatusUtils.parseStock(item['stok']); if (item is! Map) continue;
final stockValue = StockStatusUtils.parseStock(item['current_stock']);
final statusKey = StockStatusUtils.statusFromStock(stockValue); final statusKey = StockStatusUtils.statusFromStock(stockValue);
final unit = item['unit'] ?? 'kg'; if (statusKey != StockStatusUtils.statusKritis) continue;
final stockLabel = item['stok']?.toString() ?? '0';
final category = item['category']?.toString().toLowerCase() ?? '';
final unit =
item['unit']?.toString() ?? (category == 'barang' ? 'pcs' : 'kg');
lowStockItems.add({ lowStockItems.add({
'name': item['nama_bahan']?.toString() ?? '-', 'name': item['name']?.toString() ?? '-',
'stock': '$stockLabel $unit', 'stock': '${_formatStock(stockValue)} $unit',
'statusKey': statusKey,
'status': StockStatusUtils.label(statusKey), 'status': StockStatusUtils.label(statusKey),
'statusColor': StockStatusUtils.color(statusKey), 'statusColor': StockStatusUtils.color(statusKey),
}); });
} }
lowStockItems.sort(
(a, b) => a['name'].toString().compareTo(b['name'].toString()),
);
} }
void _applyDummyPenggunaan() { void _applyDummyPenggunaan() {
@ -142,34 +141,11 @@ class DashboardController extends ChangeNotifier {
]); ]);
} }
void _applyDummyLowStock() { String _formatStock(double value) {
lowStockItems if (value % 1 == 0) {
..clear() return value.toInt().toString();
..addAll([ }
{
'name': 'Tepung Terigu', return value.toStringAsFixed(1);
'stock': '5 kg',
'status': StockStatusUtils.label(StockStatusUtils.statusFromStock(5)),
'statusColor': StockStatusUtils.color(
StockStatusUtils.statusFromStock(5),
),
},
{
'name': 'Gula Pasir',
'stock': '8 kg',
'status': StockStatusUtils.label(StockStatusUtils.statusFromStock(8)),
'statusColor': StockStatusUtils.color(
StockStatusUtils.statusFromStock(8),
),
},
{
'name': 'Mentega',
'stock': '3 kg',
'status': StockStatusUtils.label(StockStatusUtils.statusFromStock(3)),
'statusColor': StockStatusUtils.color(
StockStatusUtils.statusFromStock(3),
),
},
]);
} }
} }

View File

@ -90,6 +90,7 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
), ),
if (_controller.lowStockItems.isNotEmpty)
Positioned( Positioned(
right: 4, right: 4,
top: 4, top: 4,
@ -98,16 +99,19 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
height: 22, height: 22,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.statusError, color: AppColors.statusError,
borderRadius: BorderRadius.circular(11), borderRadius: BorderRadius.circular(
11,
),
border: Border.all( border: Border.all(
color: AppColors.primaryBrown, color: AppColors.primaryBrown,
width: 2, width: 2,
), ),
), ),
child: const Center( child: Center(
child: Text( child: Text(
'3', _controller.lowStockItems.length
style: TextStyle( .toString(),
style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -278,6 +282,16 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (_controller.isLoading)
const Center(child: CircularProgressIndicator())
else if (_controller.lowStockItems.isEmpty)
Text(
'Tidak ada stok kritis',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.textTertiary,
),
)
else
..._controller.lowStockItems.map((item) { ..._controller.lowStockItems.map((item) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.only(bottom: 16),
@ -294,7 +308,8 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
item['name'], item['name'],
style: AppTextStyles.labelLarge style: AppTextStyles.labelLarge
.copyWith( .copyWith(
color: AppColors.textPrimary, color:
AppColors.textPrimary,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -302,7 +317,8 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
'Stok: ${item['stock']}', 'Stok: ${item['stock']}',
style: AppTextStyles.bodySmall style: AppTextStyles.bodySmall
.copyWith( .copyWith(
color: AppColors.textTertiary, color:
AppColors.textTertiary,
), ),
), ),
], ],
@ -315,7 +331,9 @@ class _DashboardScreenState extends State<DashboardScreen> with RouteAware {
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: item['statusColor'], color: item['statusColor'],
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(
6,
),
), ),
child: Text( child: Text(
item['status'], item['status'],

View File

@ -78,7 +78,7 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.3), color: Colors.white.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: const Icon( child: const Icon(
@ -363,7 +363,12 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
final maxStock = _controller.maxStock; final maxStock = _controller.maxStock;
final stockPercentage = (product.stock / maxStock * 100).toInt(); final stockPercentage = (product.stock / maxStock * 100).toInt();
return Container( return InkWell(
onTap: () => _showProductDetail(product),
borderRadius: BorderRadius.circular(16),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.bgWhite, color: AppColors.bgWhite,
@ -381,7 +386,7 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
decoration: BoxDecoration( decoration: BoxDecoration(
color: _controller color: _controller
.getStatusColor(product.status) .getStatusColor(product.status)
.withOpacity(0.12), .withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
), ),
child: Icon( child: Icon(
@ -413,7 +418,9 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
vertical: 3, vertical: 3,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBrown.withOpacity(0.1), color: AppColors.primaryBrown.withValues(
alpha: 0.1,
),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text( child: Text(
@ -445,12 +452,12 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
decoration: BoxDecoration( decoration: BoxDecoration(
color: _controller color: _controller
.getStatusColor(product.status) .getStatusColor(product.status)
.withOpacity(0.15), .withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(
color: _controller color: _controller
.getStatusColor(product.status) .getStatusColor(product.status)
.withOpacity(0.3), .withValues(alpha: 0.3),
width: 1, width: 1,
), ),
), ),
@ -529,26 +536,225 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
GestureDetector( Container(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${product.name} - Detail'),
duration: const Duration(seconds: 1),
),
);
},
child: Container(
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryBrown.withOpacity(0.1), color: AppColors.primaryBrown.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: Icon( child: Icon(
Icons.arrow_forward_ios, Icons.info_outline_rounded,
color: AppColors.primaryBrown, color: AppColors.primaryBrown,
size: 16, size: 20,
),
),
],
),
],
),
),
],
),
);
}
void _showProductDetail(Product product) {
final maxStock = _controller.maxStock;
final stockPercentage =
maxStock == 0 ? 0 : (product.stock / maxStock * 100).round();
final statusColor = _controller.getStatusColor(product.status);
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return Container(
margin: const EdgeInsets.all(12),
padding: EdgeInsets.only(
left: 18,
right: 18,
top: 12,
bottom: 18 + MediaQuery.of(context).padding.bottom,
),
decoration: BoxDecoration(
color: AppColors.bgWhite,
borderRadius: BorderRadius.circular(18),
boxShadow: [AppColors.shadowMedium],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColors.grey200,
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(height: 18),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(14),
),
child: Icon(
_controller.getCategoryIcon(product.category),
color: statusColor,
size: 28,
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: AppTextStyles.headlineSmall.copyWith(
color: AppColors.textPrimary,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 6),
Text(
'ID Produk: ${product.id}',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.textTertiary,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.14),
borderRadius: BorderRadius.circular(8),
),
child: Text(
_controller.getStatusLabel(product.status),
style: AppTextStyles.labelSmall.copyWith(
color: statusColor,
fontWeight: FontWeight.w700,
),
),
),
],
),
const SizedBox(height: 18),
Container(height: 1, color: AppColors.grey200),
const SizedBox(height: 16),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
_buildDetailTile(
icon: Icons.category_outlined,
label: 'Kategori',
value: product.category,
),
_buildDetailTile(
icon: Icons.sell_outlined,
label: 'Harga',
value: _formatRupiah(product.price),
),
_buildDetailTile(
icon: Icons.inventory_2_outlined,
label: 'Stok',
value: '${product.stock} ${product.unit}',
),
_buildDetailTile(
icon: Icons.straighten_outlined,
label: 'Satuan',
value: product.unit,
),
_buildDetailTile(
icon: Icons.speed_outlined,
label: 'Kapasitas',
value: '$stockPercentage%',
),
_buildDetailTile(
icon: Icons.verified_outlined,
label: 'Status',
value: _controller.getStatusLabel(product.status),
),
],
),
const SizedBox(height: 16),
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: LinearProgressIndicator(
value: maxStock == 0 ? 0 : product.stock / maxStock,
minHeight: 8,
backgroundColor: AppColors.grey200,
valueColor: AlwaysStoppedAnimation<Color>(statusColor),
),
),
const SizedBox(height: 14),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: statusColor.withValues(alpha: 0.18),
),
),
child: Text(
_stockRecommendation(product),
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.textSecondary,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close_rounded),
label: const Text('Tutup'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/transaction');
},
icon: const Icon(Icons.add_shopping_cart_rounded),
label: const Text('Tambah Stok'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.statusSuccess,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
), ),
), ),
), ),
@ -557,6 +763,79 @@ class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
], ],
), ),
); );
},
);
}
Widget _buildDetailTile({
required IconData icon,
required String label,
required String value,
}) {
return SizedBox(
width: (MediaQuery.of(context).size.width - 60) / 2,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.bgLight,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.grey200),
),
child: Row(
children: [
Icon(icon, size: 18, color: AppColors.primaryBrown),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: AppTextStyles.labelSmall.copyWith(
color: AppColors.textTertiary,
),
),
const SizedBox(height: 2),
Text(
value,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: AppTextStyles.labelSmall.copyWith(
color: AppColors.textPrimary,
fontWeight: FontWeight.w700,
),
),
],
),
),
],
),
),
);
}
String _formatRupiah(int price) {
final text = price.toString();
final buffer = StringBuffer();
for (var i = 0; i < text.length; i++) {
final reverseIndex = text.length - i;
buffer.write(text[i]);
if (reverseIndex > 1 && reverseIndex % 3 == 1) {
buffer.write('.');
}
}
return 'Rp $buffer';
}
String _stockRecommendation(Product product) {
switch (product.status) {
case 'kritis':
return 'Stok kritis. Disarankan segera tambah stok agar produksi tidak terganggu.';
case 'sedang':
return 'Stok mulai menipis. Pantau pemakaian dan siapkan pembelian berikutnya.';
default:
return 'Stok aman. Produk masih cukup untuk kebutuhan operasional.';
}
} }
@override @override

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:finalproject/services/ml_service.dart'; import 'package:finalproject/services/ml_service.dart';
import 'package:finalproject/utils/stock_status.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ReportController extends ChangeNotifier { class ReportController extends ChangeNotifier {
@ -37,15 +38,15 @@ class ReportController extends ChangeNotifier {
MLService.getReportStock(), MLService.getReportStock(),
MLService.getReportStockIn(), MLService.getReportStockIn(),
MLService.getReportPredictions(), MLService.getReportPredictions(),
MLService.getReportCritical(),
MLService.getProducts(), MLService.getProducts(),
MLService.getDashboardSummary(),
]); ]);
final stockResponse = results[0] as Map<String, dynamic>; final stockResponse = results[0] as Map<String, dynamic>;
final stockInResponse = results[1] as Map<String, dynamic>; final stockInResponse = results[1] as Map<String, dynamic>;
final predictionResponse = results[2] as Map<String, dynamic>; final predictionResponse = results[2] as Map<String, dynamic>;
final criticalResponse = results[3] as Map<String, dynamic>; final productsResponse = results[3];
final productsResponse = results[4]; final dashboardSummary = results[4] as Map<String, dynamic>;
if (stockResponse['status'] != true) { if (stockResponse['status'] != true) {
errorMessage = stockResponse['message']?.toString(); errorMessage = stockResponse['message']?.toString();
@ -65,18 +66,22 @@ class ReportController extends ChangeNotifier {
_applyPredictions(predictionResponse['data']); _applyPredictions(predictionResponse['data']);
} }
if (criticalResponse['status'] != true) {
errorMessage ??= criticalResponse['message']?.toString();
} else {
_applyCriticalItems(criticalResponse['data']);
}
if (productsResponse is List) { if (productsResponse is List) {
_productCount = productsResponse.length; _productCount = productsResponse.length;
_applyCriticalProducts(productsResponse);
} else {
_productCount = 0;
criticalItems.clear();
}
if (dashboardSummary['status'] != true) {
errorMessage ??= dashboardSummary['message']?.toString();
usageSummary.clear();
} else {
_applyUsageSummary(dashboardSummary['penggunaan_bahan']);
} }
_rebuildSummary(); _rebuildSummary();
_rebuildUsageSummary();
_rebuildDemandTrend(); _rebuildDemandTrend();
} catch (e) { } catch (e) {
errorMessage = 'Gagal memuat laporan: $e'; errorMessage = 'Gagal memuat laporan: $e';
@ -150,17 +155,50 @@ class ReportController extends ChangeNotifier {
} }
} }
void _applyCriticalItems(dynamic data) { void _applyCriticalProducts(List<dynamic> products) {
criticalItems.clear(); criticalItems.clear();
if (data is! List) return;
for (final item in data) { for (final item in products) {
if (item is! Map) continue;
final stock = StockStatusUtils.parseStock(item['current_stock']);
final status = StockStatusUtils.statusFromStock(stock);
if (status != StockStatusUtils.statusKritis) continue;
final category = item['category']?.toString().toLowerCase() ?? '';
final unit =
item['unit']?.toString() ?? (category == 'barang' ? 'pcs' : 'kg');
criticalItems.add({ criticalItems.add({
'name': item['nama_bahan']?.toString() ?? '-', 'name': item['name']?.toString() ?? '-',
'stock': _toDouble(item['stok']), 'stock': stock,
'status': item['status']?.toString() ?? 'Kritis', 'status': StockStatusUtils.label(status),
'unit': item['unit']?.toString() ?? 'kg', 'unit': unit,
}); });
} }
criticalItems.sort(
(a, b) => a['name'].toString().compareTo(b['name'].toString()),
);
}
void _applyUsageSummary(dynamic data) {
usageSummary.clear();
if (data is! List) return;
for (final item in data) {
if (item is! Map) continue;
final label = item['nama_bahan']?.toString() ?? '-';
final total = _toDouble(item['total_digunakan']);
final unit = item['satuan']?.toString() ?? 'kg';
if (total <= 0) continue;
usageSummary.add({'label': label, 'value': total, 'unit': unit});
}
usageSummary.sort(
(a, b) => (b['value'] as double).compareTo(a['value'] as double),
);
} }
void _rebuildSummary() { void _rebuildSummary() {
@ -170,35 +208,17 @@ class ReportController extends ChangeNotifier {
totalKritis = criticalItems.length; totalKritis = criticalItems.length;
} }
void _rebuildUsageSummary() {
final Map<String, double> totals = {};
for (final item in stockHistory) {
final name = item['name'] as String;
final amount = item['amount'] as double;
totals[name] = (totals[name] ?? 0) + amount;
}
final sorted =
totals.entries.toList()..sort((a, b) => b.value.compareTo(a.value));
usageSummary
..clear()
..addAll(
sorted
.take(4)
.map((entry) => {'label': entry.key, 'value': entry.value}),
);
}
void _rebuildDemandTrend() { void _rebuildDemandTrend() {
final sorted = final sorted =
predictionItems.toList()..sort( predictionItems.toList()..sort(
(a, b) => (a['date'] as DateTime).compareTo(b['date'] as DateTime), (a, b) => (a['date'] as DateTime).compareTo(b['date'] as DateTime),
); );
final latestItems =
sorted.length > 7 ? sorted.skip(sorted.length - 7) : sorted;
demandTrend demandTrend
..clear() ..clear()
..addAll(sorted.take(7).map((entry) => (entry['prediction'] as double))); ..addAll(latestItems.map((entry) => (entry['prediction'] as double)));
} }
double _toDouble(dynamic value) { double _toDouble(dynamic value) {

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ class MLService {
// API URL - Change based on environment // API URL - Change based on environment
// Untuk emulator Android: 10.0.2.2 // Untuk emulator Android: 10.0.2.2
// Untuk device fisik: 192.168.x.x atau 127.0.0.1 kalau local // Untuk device fisik: 192.168.x.x atau 127.0.0.1 kalau local
static const String baseUrl = 'http://192.168.18.30:5000'; static const String baseUrl = 'http://192.168.1.44:5000';
static const int timeoutSeconds = 30; static const int timeoutSeconds = 30;

View File

@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
archive:
dependency: transitive
description:
name: archive
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
url: "https://pub.dev"
source: hosted
version: "4.0.9"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -9,6 +17,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.12.0" version: "2.12.0"
barcode:
dependency: transitive
description:
name: barcode
sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4"
url: "https://pub.dev"
source: hosted
version: "2.2.9"
bidi:
dependency: transitive
description:
name: bidi
sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d"
url: "https://pub.dev"
source: hosted
version: "2.0.13"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -152,6 +176,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
image:
dependency: transitive
description:
name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
url: "https://pub.dev"
source: hosted
version: "4.5.4"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -240,6 +272,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -288,6 +328,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
pdf:
dependency: "direct main"
description:
name: pdf
sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416"
url: "https://pub.dev"
source: hosted
version: "3.11.3"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -304,6 +360,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
posix:
dependency: transitive
description:
name: posix
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
url: "https://pub.dev"
source: hosted
version: "6.5.0"
qr:
dependency: transitive
description:
name: qr
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -461,6 +533,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sdks: sdks:
dart: ">=3.7.0 <4.0.0" dart: ">=3.7.0 <4.0.0"
flutter: ">=3.29.0" flutter: ">=3.29.0"

View File

@ -46,6 +46,7 @@ dependencies:
# File export # File export
path_provider: ^2.1.4 path_provider: ^2.1.4
pdf: ^3.11.1
# Open file + share # Open file + share
open_filex: ^4.5.0 open_filex: ^4.5.0