Update codingan

This commit is contained in:
M.Nizar Fahrurrozi 2026-07-28 12:10:36 +07:00
parent 3d4741b535
commit d5b6d8c4b2
6 changed files with 533 additions and 206 deletions

View File

@ -3,8 +3,14 @@ import 'package:provider/provider.dart';
import '../services/mqtt_service.dart'; import '../services/mqtt_service.dart';
import '../services/supabase_service.dart'; import '../services/supabase_service.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
import '../constants/app_colors.dart'; import '../constants/app_colors.dart';
import '../services/recording_service.dart'; // Memori Abadi Timer Tetap Dipakai import '../services/recording_service.dart'; // Memori Abadi Timer Tetap Dipakai
import 'dart:io';
import 'dart:convert';
import 'package:archive/archive_io.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:http/http.dart' as http;
class RecordingScreen extends StatefulWidget { class RecordingScreen extends StatefulWidget {
const RecordingScreen({super.key}); const RecordingScreen({super.key});
@ -17,7 +23,9 @@ class _RecordingScreenState extends State<RecordingScreen> {
final TextEditingController _intervalController = TextEditingController(); final TextEditingController _intervalController = TextEditingController();
List<Map<String, dynamic>> _gallery = []; List<Map<String, dynamic>> _gallery = [];
bool _isLoading = true; bool _isLoading = true;
bool _isSelectionMode = false;
final Set<int> _selectedIndices = {}; // Menyimpan index foto yang dicentang
final recordingService = RecordingService(); final recordingService = RecordingService();
late final RealtimeChannel _channel; late final RealtimeChannel _channel;
@ -33,7 +41,7 @@ class _RecordingScreenState extends State<RecordingScreen> {
schema: 'public', schema: 'public',
table: 'foto_dataset', table: 'foto_dataset',
callback: (payload) { callback: (payload) {
_loadData(); _loadData();
}, },
) )
.subscribe(); .subscribe();
@ -50,12 +58,12 @@ class _RecordingScreenState extends State<RecordingScreen> {
setState(() => _isLoading = true); setState(() => _isLoading = true);
try { try {
final interval = await SupabaseService().getIntervalSetting(); final interval = await SupabaseService().getIntervalSetting();
final images = await SupabaseService().getFotoDataset(); final images = await SupabaseService().getFotoDataset();
if (mounted) { if (mounted) {
setState(() { setState(() {
_intervalController.text = interval.toString(); _intervalController.text = interval.toString();
_gallery = images; _gallery = images.reversed.toList();
_isLoading = false; _isLoading = false;
}); });
} }
} catch (e) { } catch (e) {
@ -94,7 +102,8 @@ class _RecordingScreenState extends State<RecordingScreen> {
start start
? 'Perekaman dimulai — interval $intervalVal menit' ? 'Perekaman dimulai — interval $intervalVal menit'
: 'Perekaman dihentikan', : 'Perekaman dihentikan',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
), ),
backgroundColor: start ? AppColors.success : AppColors.error, backgroundColor: start ? AppColors.success : AppColors.error,
), ),
@ -105,20 +114,39 @@ class _RecordingScreenState extends State<RecordingScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
floatingActionButton: _selectedIndices.isNotEmpty
? FloatingActionButton.extended(
onPressed: () => _downloadDatasetZip(),
backgroundColor: AppColors.primary,
icon: const Icon(Icons.download, color: Colors.white),
label: Text(
'Download ZIP (${_selectedIndices.length})',
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
)
: null,
backgroundColor: AppColors.background, backgroundColor: AppColors.background,
body: SafeArea( body: SafeArea(
child: _isLoading child: _isLoading
? const Center(child: CircularProgressIndicator(color: AppColors.primary)) ? const Center(
child: CircularProgressIndicator(color: AppColors.primary))
: Padding( : Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('Perekaman', const Text(
style: TextStyle(color: AppColors.textMain, fontSize: 32, fontWeight: FontWeight.bold), 'Perekaman',
style: TextStyle(
color: AppColors.textMain,
fontSize: 32,
fontWeight: FontWeight.bold),
), ),
const Text('Dataset Citra Kopi', const Text(
style: TextStyle(color: AppColors.textSecondary, fontSize: 16), 'Dataset Citra Kopi',
style: TextStyle(
color: AppColors.textSecondary, fontSize: 16),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@ -133,79 +161,110 @@ class _RecordingScreenState extends State<RecordingScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.cardBg, color: AppColors.cardBg,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), border: Border.all(
color: AppColors.textSecondary
.withValues(alpha: 0.15)),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('Interval Waktu (menit)', const Text(
style: TextStyle(color: AppColors.textMain, fontWeight: FontWeight.bold), 'Interval Waktu (menit)',
style: TextStyle(
color: AppColors.textMain,
fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
TextField( TextField(
controller: _intervalController, controller: _intervalController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
style: const TextStyle(color: AppColors.textMain), style:
const TextStyle(color: AppColors.textMain),
decoration: InputDecoration( decoration: InputDecoration(
hintText: '30', hintText: '30',
hintStyle: const TextStyle(color: AppColors.textSecondary), hintStyle: const TextStyle(
color: AppColors.textSecondary),
filled: true, filled: true,
fillColor: AppColors.background, fillColor: AppColors.background,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
borderSide: BorderSide(color: AppColors.textSecondary.withValues(alpha: 0.3)), borderSide: BorderSide(
color: AppColors.textSecondary
.withValues(alpha: 0.3)),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
borderSide: const BorderSide(color: AppColors.primary), borderSide: const BorderSide(
color: AppColors.primary),
), ),
), ),
), ),
const SizedBox(height: 15), const SizedBox(height: 15),
if (isRecording) if (isRecording)
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.success.withValues(alpha: 0.15), color: AppColors.success
.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.success), border:
Border.all(color: AppColors.success),
), ),
child: const Row( child: const Row(
children: [ children: [
Icon(Icons.circle, color: AppColors.success, size: 10), Icon(Icons.circle,
color: AppColors.success, size: 10),
SizedBox(width: 8), SizedBox(width: 8),
Text('Perekaman berjalan...', Text('Perekaman berjalan...',
style: TextStyle(color: AppColors.success, fontSize: 12, fontWeight: FontWeight.bold)), style: TextStyle(
color: AppColors.success,
fontSize: 12,
fontWeight: FontWeight.bold)),
], ],
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: isRecording ? null : () => _controlRecording(true), onPressed: isRecording
icon: const Icon(Icons.play_arrow, color: Colors.white), ? null
label: const Text('Mulai', style: TextStyle(color: Colors.white)), : () => _controlRecording(true),
icon: const Icon(Icons.play_arrow,
color: Colors.white),
label: const Text('Mulai',
style:
TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary, backgroundColor: AppColors.primary,
disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.3), disabledBackgroundColor: AppColors
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), .primary
.withValues(alpha: 0.3),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(12)),
), ),
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: !isRecording ? null : () => _controlRecording(false), onPressed: !isRecording
icon: const Icon(Icons.stop, color: Colors.white), ? null
label: const Text('Berhenti', style: TextStyle(color: Colors.white)), : () => _controlRecording(false),
icon: const Icon(Icons.stop,
color: Colors.white),
label: const Text('Berhenti',
style:
TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.error, backgroundColor: AppColors.error,
disabledBackgroundColor: AppColors.error.withValues(alpha: 0.3), disabledBackgroundColor: AppColors.error
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), .withValues(alpha: 0.3),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(12)),
), ),
), ),
), ),
@ -218,16 +277,65 @@ class _RecordingScreenState extends State<RecordingScreen> {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// KODE BARU (Sudah Termasuk Tombol Checklist & Select All):
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Galeri Citra Dataset (${_gallery.length} foto)', Expanded(
style: const TextStyle(color: AppColors.textMain, fontSize: 18, fontWeight: FontWeight.bold), child: Text(
), 'Galeri Dataset (${_gallery.length} foto)',
IconButton( style: const TextStyle(
icon: const Icon(Icons.refresh, color: AppColors.primary), color: AppColors.textMain,
onPressed: _loadData, fontSize: 18,
fontWeight: FontWeight.bold),
),
), ),
Row(
children: [
if (_isSelectionMode) ...[
IconButton(
icon: const Icon(Icons.select_all,
color: AppColors.primary),
tooltip: 'Pilih Semua',
onPressed: () {
setState(() {
if (_selectedIndices.length ==
_gallery.length) {
_selectedIndices
.clear(); // Batal pilih semua
} else {
_selectedIndices.addAll(List.generate(
_gallery.length,
(i) => i)); // Pilih semua
}
});
},
),
IconButton(
icon: const Icon(Icons.close,
color: AppColors.error),
tooltip: 'Batal Mode Seleksi',
onPressed: () => setState(() {
_isSelectionMode = false;
_selectedIndices.clear();
}),
),
] else ...[
IconButton(
icon: const Icon(Icons.checklist,
color: AppColors.primary),
tooltip: 'Pilih Foto',
onPressed: () =>
setState(() => _isSelectionMode = true),
),
IconButton(
icon: const Icon(Icons.refresh,
color: AppColors.primary),
onPressed: _loadData,
),
],
],
)
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@ -235,10 +343,13 @@ class _RecordingScreenState extends State<RecordingScreen> {
Expanded( Expanded(
child: _gallery.isEmpty child: _gallery.isEmpty
? const Center( ? const Center(
child: Text('Belum ada data citra', style: TextStyle(color: AppColors.textSecondary)), child: Text('Belum ada data citra',
style: TextStyle(
color: AppColors.textSecondary)),
) )
: GridView.builder( : GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
crossAxisSpacing: 10, crossAxisSpacing: 10,
mainAxisSpacing: 10, mainAxisSpacing: 10,
@ -246,98 +357,222 @@ class _RecordingScreenState extends State<RecordingScreen> {
itemCount: _gallery.length, itemCount: _gallery.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = _gallery[index]; final item = _gallery[index];
final nomorFoto = index + 1; final nomorFoto = _gallery.length - index;
// PENYARING WAKTU: Ubah 'T' jadi Spasi agar kodingan lamamu aman
String rawTime = item['timestamp'] ?? item['created_at'] ?? '';
String cleanTime = rawTime.replaceAll('T', ' ');
final tanggal = cleanTime.contains(' ') ? cleanTime.split(' ')[0] : cleanTime; // PENYARING WAKTU: Ubah 'T' jadi Spasi agar kodingan lamamu aman
final jam = cleanTime.contains(' ') ? cleanTime.split(' ')[1] : ''; String rawTime = item['timestamp'] ??
final jamSingkat = jam.length >= 5 ? jam.substring(0, 5) : jam; item['created_at'] ??
'';
String cleanTime = rawTime.replaceAll('T', ' ');
final tanggal = cleanTime.contains(' ')
? cleanTime.split(' ')[0]
: cleanTime;
final jam = cleanTime.contains(' ')
? cleanTime.split(' ')[1]
: '';
final jamSingkat =
jam.length >= 5 ? jam.substring(0, 5) : jam;
return GestureDetector( return GestureDetector(
onTap: () => Navigator.pushNamed( // --- FITUR BARU: Logika Tahan & Klik ---
context, onLongPress: () {
'/image_result', setState(() {
arguments: { _isSelectionMode = true;
'id': item['id'].toString(), _selectedIndices.add(index);
'date': tanggal, });
'time': jamSingkat, },
// PENYARING SENSOR 0: Jika 0, ubah jadi strip '-' onTap: () {
'temp': (item['suhu'] == 0 || item['suhu'] == null) ? '-' : '${item['suhu']} °C', if (_isSelectionMode) {
'humidity': (item['kelembapan'] == 0 || item['kelembapan'] == null) ? '-' : '${item['kelembapan']} %', setState(() {
'light': (item['intensitas'] == 0 || item['intensitas'] == null) ? '-' : '${item['intensitas']} Lux', if (_selectedIndices.contains(index)) {
'location': 'Gudang Pengering', _selectedIndices.remove(index);
'intake': item['kipas1'] ?? 'OFF', } else {
'exhaust': item['kipas2'] ?? 'OFF', _selectedIndices.add(index);
'url_foto': item['url_foto'] ?? '', }
}, });
), } else {
child: Container( // KODE LAMAMU TETAP AMAN DI SINI (Navigasi ke Detail)
decoration: BoxDecoration( Navigator.pushNamed(
color: AppColors.cardBg, context,
borderRadius: BorderRadius.circular(15), '/image_result',
border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), arguments: {
), 'id': item['id'].toString(),
child: Column( 'date': tanggal,
crossAxisAlignment: CrossAxisAlignment.stretch, 'time': jamSingkat,
children: [ 'temp': (item['suhu'] == 0 ||
Expanded( item['suhu'] == null)
flex: 3, ? '-'
child: ClipRRect( : '${item['suhu']} °C',
borderRadius: const BorderRadius.vertical(top: Radius.circular(14)), 'humidity': (item['kelembapan'] ==
child: item['url_foto'] != null 0 ||
? Image.network( item['kelembapan'] == null)
item['url_foto'], ? '-'
fit: BoxFit.cover, : '${item['kelembapan']} %',
errorBuilder: (_, __, ___) => const Center( 'light': (item['intensitas'] == 0 ||
child: Icon(Icons.broken_image, color: AppColors.textSecondary, size: 40), item['intensitas'] == null)
), ? '-'
loadingBuilder: (_, child, progress) { : '${item['intensitas']} Lux',
if (progress == null) return child; 'location': 'Gudang Pengering',
return const Center( 'intake': item['kipas1'] ?? 'OFF',
child: CircularProgressIndicator(color: AppColors.primary, strokeWidth: 2), 'exhaust': item['kipas2'] ?? 'OFF',
); 'url_foto': item['url_foto'] ?? '',
}, },
) );
: const Center( }
child: Icon(Icons.image, color: AppColors.textSecondary, size: 40), },
), child: Stack(
children: [
// KODE WADAH FOTO LAMAMU (Hanya ditambah opacity saat dicentang)
Opacity(
opacity:
_selectedIndices.contains(index)
? 0.6
: 1.0,
child: Container(
decoration: BoxDecoration(
color: AppColors.cardBg,
borderRadius:
BorderRadius.circular(15),
border: Border.all(
color: _selectedIndices
.contains(index)
? AppColors.primary
: AppColors.textSecondary
.withValues(alpha: 0.15),
width: _selectedIndices
.contains(index)
? 2.0
: 1.0,
),
), ),
), child: Column(
Container( crossAxisAlignment:
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), CrossAxisAlignment.stretch,
decoration: const BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(15)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Container( Expanded(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), flex: 3,
decoration: BoxDecoration( child: ClipRRect(
color: AppColors.primary.withValues(alpha: 0.15), borderRadius:
borderRadius: BorderRadius.circular(6), const BorderRadius
border: Border.all(color: AppColors.primary.withValues(alpha: 0.5)), .vertical(
), top: Radius.circular(
child: Text('#$nomorFoto', 14)),
style: const TextStyle(color: AppColors.primary, fontSize: 10, fontWeight: FontWeight.bold), child: item['url_foto'] !=
null
? Image.network(
item['url_foto'],
fit: BoxFit.cover,
errorBuilder:
(_, __, ___) =>
const Center(
child: Icon(
Icons
.broken_image,
color: AppColors
.textSecondary,
size: 40),
),
)
: const Center(
child: Icon(
Icons.image,
color: AppColors
.textSecondary,
size: 40),
),
), ),
), ),
Column( Container(
crossAxisAlignment: CrossAxisAlignment.end, padding:
children: [ const EdgeInsets.symmetric(
Text(tanggal, style: const TextStyle(color: AppColors.textSecondary, fontSize: 8)), horizontal: 8,
Text(jamSingkat, style: const TextStyle(color: AppColors.textMain, fontSize: 10, fontWeight: FontWeight.bold)), vertical: 6),
], decoration: const BoxDecoration(
color: Colors.transparent,
borderRadius:
BorderRadius.vertical(
bottom:
Radius.circular(
15)),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Container(
padding: const EdgeInsets
.symmetric(
horizontal: 6,
vertical: 2),
decoration: BoxDecoration(
color: AppColors.primary
.withValues(
alpha: 0.15),
borderRadius:
BorderRadius
.circular(6),
),
child: Text(
'#$nomorFoto',
style: const TextStyle(
color: AppColors
.primary,
fontSize: 10,
fontWeight:
FontWeight
.bold),
),
),
Column(
crossAxisAlignment:
CrossAxisAlignment
.end,
children: [
Text(tanggal,
style: const TextStyle(
color: AppColors
.textSecondary,
fontSize: 8)),
Text(jamSingkat,
style: const TextStyle(
color: AppColors
.textMain,
fontSize: 10,
fontWeight:
FontWeight
.bold)),
],
),
],
),
), ),
], ],
), ),
), ),
], ),
), // --- FITUR BARU: Ikon Centang ---
if (_isSelectionMode)
Positioned(
top: 8,
right: 8,
child: Icon(
_selectedIndices.contains(index)
? Icons.check_circle
: Icons.radio_button_unchecked,
color:
_selectedIndices.contains(index)
? AppColors.primary
: Colors.white,
shadows: const [
Shadow(
blurRadius: 2.0,
color: Colors.black45)
],
),
),
],
), ),
); );
}, },
@ -349,4 +584,100 @@ class _RecordingScreenState extends State<RecordingScreen> {
), ),
); );
} }
}
// =====================================================================
// --- FITUR BARU: Fungsi Membuat ZIP dan Ekspor ---
// =====================================================================
Future<void> _downloadDatasetZip() async {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(color: AppColors.primary),
const SizedBox(height: 20),
Text('Mengunduh & Membungkus ${_selectedIndices.length} Data...',
textAlign: TextAlign.center),
],
),
),
);
try {
final archive = Archive();
// Membuat header (judul kolom) untuk file Excel/CSV
String csvContent =
"Nama_File,Tanggal,Jam,Suhu(C),Kelembapan(%),Intensitas_Cahaya(Lux),Kipas_1,Kipas_2\n";
for (int index in _selectedIndices) {
final item = _gallery[index];
// Membersihkan format waktu
String rawTime = item['timestamp'] ?? item['created_at'] ?? '';
String cleanTime = rawTime.replaceAll('T', ' ');
final tanggal =
cleanTime.contains(' ') ? cleanTime.split(' ')[0] : cleanTime;
final jam = cleanTime.contains(' ') ? cleanTime.split(' ')[1] : '';
final jamSingkat = jam.length >= 5 ? jam.substring(0, 5) : jam;
// Nama file gambar
String fileName =
'IMG_${tanggal}_${jamSingkat.replaceAll(':', '-')}.jpg';
// Unduh gambar dari Supabase
if (item['url_foto'] != null &&
item['url_foto'].toString().isNotEmpty) {
final response = await http.get(Uri.parse(item['url_foto']));
if (response.statusCode == 200) {
archive.addFile(ArchiveFile(
fileName, response.bodyBytes.length, response.bodyBytes));
}
}
// Tambah baris data ke Excel/CSV
csvContent +=
"$fileName,$tanggal,$jamSingkat,${item['suhu']},${item['kelembapan']},${item['intensitas']},${item['kipas1'] ?? 'OFF'},${item['kipas2'] ?? 'OFF'}\n";
}
// Masukkan file CSV ke dalam ZIP
final csvBytes = utf8.encode(csvContent);
archive.addFile(
ArchiveFile("Data_Sensor_Dataset.csv", csvBytes.length, csvBytes));
// Proses kompresi file ZIP
final zipBytes = ZipEncoder().encode(archive);
// Simpan sementara di memori HP
final tempDir = await getTemporaryDirectory();
final zipPath = '${tempDir.path}/Dataset_Kopi_IoT.zip';
final zipFile = File(zipPath);
await zipFile.writeAsBytes(zipBytes!);
if (mounted) Navigator.pop(context); // Tutup loading dialog
// Buka popup Share/Save untuk disimpan ke HP pengguna
await Share.shareXFiles(
[XFile(zipPath)],
text:
'Ini adalah Dataset Kopi berformat ZIP. Ekstrak untuk melihat foto dan tabel Excel.',
);
// Reset mode seleksi
setState(() {
_isSelectionMode = false;
_selectedIndices.clear();
});
} catch (e) {
if (mounted) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal membuat ZIP: $e'),
backgroundColor: AppColors.error),
);
}
}
}
}

View File

@ -8,6 +8,7 @@ import Foundation
import app_links import app_links
import open_file_mac import open_file_mac
import printing import printing
import share_plus
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
@ -15,6 +16,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin")) PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
} }

View File

@ -42,7 +42,7 @@ packages:
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
archive: archive:
dependency: transitive dependency: "direct main"
description: description:
name: archive name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
@ -125,10 +125,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: code_assets name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.2.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -149,10 +149,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.5+2" version: "0.3.5+4"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -189,10 +189,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: equatable name: equatable
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.8" version: "2.1.0"
event_bus: event_bus:
dependency: transitive dependency: transitive
description: description:
@ -225,14 +225,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
ffi_leak_tracker:
dependency: transitive
description:
name: ffi_leak_tracker
sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -300,18 +292,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: functions_client name: functions_client
sha256: "94074d62167ae634127ef6095f536835063a7dc80f2b1aa306d2346ff9023996" sha256: e9685e9ab852a8b8e0579c867f6c9155da27317b294b3df796a536b8b8e0d253
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.6.4"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
google_fonts: google_fonts:
dependency: "direct main" dependency: "direct main"
description: description:
@ -324,10 +308,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: gotrue name: gotrue
sha256: "7a4172601553e61716f5c3dd243aa3297e13308e07eb85b7853c941ba585dcf5" sha256: "360bba9606e58d5acc59a033ab64ae3cf571a6868f7a7267cb8e9a204b7bf1b2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.20.0" version: "2.26.0"
gtk: gtk:
dependency: transitive dependency: transitive
description: description:
@ -340,10 +324,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: hooks name: hooks
sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "2.0.2"
html: html:
dependency: transitive dependency: transitive
description: description:
@ -380,10 +364,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: intl name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.20.2" version: "0.20.3"
jni: jni:
dependency: transitive dependency: transitive
description: description:
@ -496,14 +480,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.11.11" version: "10.11.11"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
url: "https://pub.dev"
source: hosted
version: "0.17.6"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -516,10 +492,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: objective_c name: objective_c
sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.3.0" version: "9.4.1"
open_file: open_file:
dependency: "direct main" dependency: "direct main"
description: description:
@ -532,18 +508,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: open_file_android name: open_file_android
sha256: "58141fcaece2f453a9684509a7275f231ac0e3d6ceb9a5e6de310a7dff9084aa" sha256: f48a342b0347bf82bebf68c752f5cfce162965ce8449c5ecdafc406d294aa63d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.1.0"
open_file_ios: open_file_ios:
dependency: transitive dependency: transitive
description: description:
name: open_file_ios name: open_file_ios
sha256: a5acd07ba1f304f807a97acbcc489457e1ad0aadff43c467987dd9eef814098f sha256: "0e3e67dcd12f69888128f533cacc092039627dd4f1d7a95a2826b814ae4ea8aa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.1.0"
open_file_linux: open_file_linux:
dependency: transitive dependency: transitive
description: description:
@ -556,18 +532,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: open_file_mac name: open_file_mac
sha256: cd293f6750de6438ab2390513c99128ade8c974825d4d8128886d1cda8c64d01 sha256: "1abe00e65f792b9baa1e15fdf4d44af5e813b1696ac307d71535543122a01e30"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.1.0"
open_file_platform_interface: open_file_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: open_file_platform_interface name: open_file_platform_interface
sha256: "101b424ca359632699a7e1213e83d025722ab668b9fd1412338221bf9b0e5757" sha256: dc302e4bd5c1d77acec4240bd0dbc1d9f2ee6882b1ce52bca5298369da7acf12
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.1.0"
open_file_web: open_file_web:
dependency: transitive dependency: transitive
description: description:
@ -592,6 +568,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
passkeys_platform_interface:
dependency: transitive
description:
name: passkeys_platform_interface
sha256: "9610bd136b3382500390912ddd8517ee99505228b1af7b507b9c907f7e99a47d"
url: "https://pub.dev"
source: hosted
version: "2.8.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -612,10 +596,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.1.6"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
@ -636,18 +620,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
@ -708,10 +692,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: postgrest name: postgrest
sha256: "9d61b3d4a88fcf9424d400127c54d49ed1b56ec30838fc0a33a64f31d4e694cc" sha256: "10e3f195e131eec944fa872644aed89b33b5be998f3fc77c87325db3927bc646"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.0" version: "2.8.0"
printing: printing:
dependency: "direct main" dependency: "direct main"
description: description:
@ -748,10 +732,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: realtime_client name: realtime_client
sha256: "7dfccf372d2f55aacfeefb6186f65a06f3ffae383fe042dbeef9d85d33487576" sha256: "0cfbe0047e2591eba348938e9b4e620d5b1a8df6c374757e8eb8645252b8387f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.3" version: "2.11.0"
record_use: record_use:
dependency: transitive dependency: transitive
description: description:
@ -776,8 +760,16 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.28.0" version: "0.28.0"
share_plus:
dependency: "direct main"
description:
name: share_plus
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
url: "https://pub.dev"
source: hosted
version: "7.2.2"
share_plus_platform_interface: share_plus_platform_interface:
dependency: "direct overridden" dependency: transitive
description: description:
name: share_plus_platform_interface name: share_plus_platform_interface
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496" sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
@ -865,10 +857,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: storage_client name: storage_client
sha256: "4801e8ca219a35e51cbb30589aba5306667ae8935b792504595a45273cef0b18" sha256: "221263cfbe0c01575b7d7fe7543e44abff380eb4d38d936372844a1caa85ba12"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.6.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -889,18 +881,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: supabase name: supabase
sha256: "40e5a8833c8834e140ef53b60a6181849667eba9ca125acb7f8e24c6a769d418" sha256: "3879996256325fcc9abb03b62b12c07e4eaae2e008e1bf043cc1525184159a43"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.10.6" version: "2.14.0"
supabase_flutter: supabase_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: supabase_flutter name: supabase_flutter
sha256: c02ce58abcaf86cb8055ad40bfd98bbf5b93fed3b5b56b8220d88ed03842818b sha256: c9916c1cd512ebf3107ec0f83c9dca13d2e1e789629c2a5df896586bff46ce5f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.12.4" version: "2.16.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -945,10 +937,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.29" version: "6.3.30"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -985,10 +977,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.4.3"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@ -1046,13 +1038,13 @@ packages:
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
win32: win32:
dependency: "direct overridden" dependency: transitive
description: description:
name: win32 name: win32
sha256: ba7d5750e3441caa1bbe31d9e516348fcf8dfcb32aa29ef87a844a59f4d1f1d0 sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "5.15.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -1081,10 +1073,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: yet_another_json_isolate name: yet_another_json_isolate
sha256: fe45897501fa156ccefbfb9359c9462ce5dec092f05e8a56109db30be864f01e sha256: eaa26beb5990b25a49d942374fd5a0c5aa67a837e03b14b4c26134aaa1ed01a9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
sdks: sdks:
dart: ">=3.10.3 <4.0.0" dart: ">=3.11.0 <4.0.0"
flutter: ">=3.38.4" flutter: ">=3.41.0"

View File

@ -44,10 +44,8 @@ dependencies:
open_file: ^3.3.2 open_file: ^3.3.2
path_provider: ^2.1.2 path_provider: ^2.1.2
google_fonts: ^6.2.1 google_fonts: ^6.2.1
archive: ^3.6.1 # Untuk membuat file ZIP
dependency_overrides: share_plus: ^7.2.2 # Untuk memunculkan popup "Save to Device / Bagikan"
win32: ^6.1.0
share_plus_platform_interface: ^3.4.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -8,6 +8,7 @@
#include <app_links/app_links_plugin_c_api.h> #include <app_links/app_links_plugin_c_api.h>
#include <printing/printing_plugin.h> #include <printing/printing_plugin.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>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("AppLinksPluginCApi")); registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
PrintingPluginRegisterWithRegistrar( PrintingPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PrintingPlugin")); registry->GetRegistrarForPlugin("PrintingPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View File

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links app_links
printing printing
share_plus
url_launcher_windows url_launcher_windows
) )