diff --git a/lib/function/excel_file_picker.dart b/lib/function/excel_file_picker.dart new file mode 100644 index 0000000..d02437d --- /dev/null +++ b/lib/function/excel_file_picker.dart @@ -0,0 +1,4 @@ +// πŸ“„ μ—‘μ…€ 파일 ν•˜λ‚˜λ₯Ό 골라 λ°”μ΄νŠΈλ‘œ λŒλ €μ£ΌλŠ” μ§„μž…μ . μ‹€μ œ κ΅¬ν˜„μ€ ν”Œλž«νΌλ³„λ‘œ κ°ˆλΌμ§„λ‹€ +// (web은 file_picker의 μ›Ή κ΅¬ν˜„μ²΄μ— pickFile(s)κ°€ 아직 μ—†μ–΄μ„œ dart:html둜 직접 처리). +export 'excel_file_picker_io.dart' + if (dart.library.html) 'excel_file_picker_web.dart'; diff --git a/lib/function/excel_file_picker_io.dart b/lib/function/excel_file_picker_io.dart new file mode 100644 index 0000000..9200e47 --- /dev/null +++ b/lib/function/excel_file_picker_io.dart @@ -0,0 +1,13 @@ +// πŸ“„ [λΉ„μ›Ή ν”Œλž«νΌ] file_picker둜 μ—‘μ…€ 파일 ν•˜λ‚˜λ₯Ό κ³ λ₯Έλ‹€. +import 'dart:typed_data'; +import 'package:file_picker/file_picker.dart'; + +/// μ‚¬μš©μžκ°€ μ—‘μ…€(.xlsx) νŒŒμΌμ„ κ³ λ₯΄λ©΄ λ°”μ΄νŠΈλ₯Ό λŒλ €μ€€λ‹€. μ·¨μ†Œν•˜λ©΄ null. +Future pickExcelFileBytes() async { + final files = await FilePicker.pickFiles( + type: FileType.custom, + allowedExtensions: ['xlsx'], + ); + if (files.isEmpty) return null; + return files.first.readAsBytes(); +} diff --git a/lib/function/excel_file_picker_web.dart b/lib/function/excel_file_picker_web.dart new file mode 100644 index 0000000..cf1453a --- /dev/null +++ b/lib/function/excel_file_picker_web.dart @@ -0,0 +1,31 @@ +// πŸ“„ [μ›Ή μ „μš©] file_picker의 μ›Ή κ΅¬ν˜„μ²΄μ— pickFile(s)κ°€ 아직 μ—†μ–΄μ„œ(UnimplementedError), +// μˆ¨κ²¨μ§„ λ₯Ό 직접 λ§Œλ“€μ–΄ μ²˜λ¦¬ν•œλ‹€. +import 'dart:async'; +import 'dart:js_interop'; +import 'dart:typed_data'; +import 'package:web/web.dart' as web; + +/// μ‚¬μš©μžκ°€ μ—‘μ…€(.xlsx) νŒŒμΌμ„ κ³ λ₯΄λ©΄ λ°”μ΄νŠΈλ₯Ό λŒλ €μ€€λ‹€. μ·¨μ†Œν•˜λ©΄ null. +Future pickExcelFileBytes() async { + final completer = Completer(); + final input = web.HTMLInputElement() + ..type = 'file' + ..accept = '.xlsx' + ..style.display = 'none'; + // λ¬Έμ„œμ— μ‹€μ œλ‘œ λΆ™μ–΄μžˆμ§€ μ•ŠμœΌλ©΄ 일뢀 λΈŒλΌμš°μ €/μžλ™ν™” ν™˜κ²½μ—μ„œ change μ΄λ²€νŠΈκ°€ + // μ•ˆμ •μ μœΌλ‘œ μ•ˆ μž‘νžŒλ‹€ β€” κ·Έλž˜μ„œ body에 잠깐 λΆ™μ˜€λ‹€κ°€ λλ‚˜λ©΄ λ°˜λ“œμ‹œ μ œκ±°ν•œλ‹€. + web.document.body!.append(input); + + input.onchange = (web.Event event) { + final file = input.files?.item(0); + input.remove(); + completer.complete(file); + }.toJS; + + input.click(); + final file = await completer.future; + if (file == null) return null; + + final buffer = await file.arrayBuffer().toDart; + return buffer.toDart.asUint8List(); +} diff --git a/lib/function/teacher_student_management_controller.dart b/lib/function/teacher_student_management_controller.dart index 465cf9a..293d500 100644 --- a/lib/function/teacher_student_management_controller.dart +++ b/lib/function/teacher_student_management_controller.dart @@ -1,13 +1,115 @@ -// πŸ‘₯ κ΅μ‚¬μš© 학생 계정 관리 ν™”λ©΄μ˜ κΈ°λŠ₯(μ„œλ²„ 톡신/μƒνƒœ) λ‹΄λ‹Ή 컨트둀러. +// πŸ‘₯ κ΅μ‚¬μš© 학생 계정 관리 ν™”λ©΄μ˜ κΈ°λŠ₯(μ„œλ²„ 톡신/μƒνƒœ/μ—‘μ…€ 일괄등둝) λ‹΄λ‹Ή 컨트둀러. import 'dart:convert'; +import 'package:excel/excel.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import '../config.dart'; +/// πŸ“„ μ—‘μ…€ ν•œ μ€„μ—μ„œ 뽑아낸 학생 정보 (아직 μ„œλ²„μ— 보내기 μ „, 미리보기용). +class BulkStudentRow { + final String studentId; + final String name; + final int? grade; + + const BulkStudentRow({ + required this.studentId, + required this.name, + this.grade, + }); +} + +/// πŸ“Š 일괄 등둝 κ²°κ³Ό. [failures]λŠ” "ν•™λ²ˆ 이름: μ‹€νŒ¨μ‚¬μœ " ν˜•νƒœμ˜ λ¬Έμžμ—΄ λͺ©λ‘. +class BulkImportResult { + final int successCount; + final List failures; + + const BulkImportResult({required this.successCount, required this.failures}); +} + class TeacherStudentManagementController extends ChangeNotifier { bool _isWorking = false; bool get isWorking => _isWorking; + int _bulkTotal = 0; + int _bulkDone = 0; + int get bulkTotal => _bulkTotal; + int get bulkDone => _bulkDone; + + /// πŸ“„ μ—‘μ…€ 파일 λ°”μ΄νŠΈλ₯Ό νŒŒμ‹±ν•œλ‹€. 1행은 λ¨Έλ¦¬κΈ€λ‘œ 보고 κ±΄λ„ˆλ›°λ©°, + /// Aμ—΄=ν•™λ²ˆ, Bμ—΄=이름, Cμ—΄=ν•™λ…„(선택, 숫자) μˆœμ„œλ₯Ό κΈ°λŒ€ν•œλ‹€. + /// ν•™λ²ˆμ΄λ‚˜ 이름이 λΉ„μ–΄μžˆλŠ” 쀄은 쑰용히 λ¬΄μ‹œν•œλ‹€. + List parseExcelBytes(Uint8List bytes) { + final excel = Excel.decodeBytes(bytes); + if (excel.tables.isEmpty) return []; + + final sheet = excel.tables.values.first; + final rows = []; + + for (final row in sheet.rows.skip(1)) { + final studentId = _cellText(row.isNotEmpty ? row[0]?.value : null); + final name = _cellText(row.length > 1 ? row[1]?.value : null); + if (studentId == null || name == null) continue; + + final grade = _cellToGrade(row.length > 2 ? row[2]?.value : null); + rows.add(BulkStudentRow(studentId: studentId, name: name, grade: grade)); + } + return rows; + } + + String? _cellText(CellValue? value) { + if (value == null) return null; + String text; + if (value is IntCellValue) { + text = value.value.toString(); + } else if (value is DoubleCellValue) { + text = value.value == value.value.roundToDouble() + ? value.value.toInt().toString() + : value.value.toString(); + } else { + text = value.toString().trim(); + } + return text.isEmpty ? null : text; + } + + int? _cellToGrade(CellValue? value) { + if (value == null) return null; + if (value is IntCellValue) return value.value; + if (value is DoubleCellValue) return value.value.round(); + return int.tryParse(value.toString().trim()); + } + + /// βž• μ—‘μ…€μ—μ„œ 뽑아낸 학생 λͺ©λ‘μ„ ν•œ λͺ…μ”© μˆœμ„œλŒ€λ‘œ μ„œλ²„μ— λ“±λ‘ν•œλ‹€ (κΈ°λ³Έ λΉ„λ°€λ²ˆν˜Έ 1234). + /// ν•œ λͺ… μ‹€νŒ¨ν•΄λ„ λ‚˜λ¨Έμ§€λŠ” 계속 μ§„ν–‰ν•˜κ³ , λ§ˆμ§€λ§‰μ— 성곡/μ‹€νŒ¨λ₯Ό λͺ¨μ•„μ„œ λŒλ €μ€€λ‹€. + Future bulkAddStudents(List rows) async { + _isWorking = true; + _bulkTotal = rows.length; + _bulkDone = 0; + notifyListeners(); + + int successCount = 0; + final failures = []; + + for (final row in rows) { + final (success, message) = await _createStudentRequest( + studentId: row.studentId, + name: row.name, + password: "1234", + grade: row.grade, + ); + if (success) { + successCount++; + } else { + failures.add('${row.studentId} ${row.name}: $message'); + } + _bulkDone++; + notifyListeners(); + } + + _isWorking = false; + notifyListeners(); + return BulkImportResult(successCount: successCount, failures: failures); + } + /// βž• 학생 계정 μΆ”κ°€. (성곡여뢀, λ©”μ‹œμ§€)λ₯Ό λ°˜ν™˜ν•œλ‹€ β€” UIκ°€ μ„±κ³΅ν–ˆμ„ λ•Œλ§Œ μž…λ ₯칸을 λΉ„μš΄λ‹€. Future<(bool success, String message)> addStudentAccount({ required String studentId, @@ -17,6 +119,28 @@ class TeacherStudentManagementController extends ChangeNotifier { }) async { _isWorking = true; notifyListeners(); + try { + return await _createStudentRequest( + studentId: studentId, + name: name, + password: password, + grade: grade, + ); + } finally { + _isWorking = false; + notifyListeners(); + } + } + + /// μ‹€μ œ 계정 생성 HTTP μš”μ²­. isWorking μƒνƒœλŠ” κ±΄λ“œλ¦¬μ§€ μ•ŠλŠ”λ‹€ β€” + /// 단건 등둝(addStudentAccount)κ³Ό 일괄 등둝(bulkAddStudents) μ–‘μͺ½μ—μ„œ + /// 각자 μ•Œλ§žμ€ μ‹œμ μ— isWorking을 κ΄€λ¦¬ν•˜κΈ° μœ„ν•΄ λΆ„λ¦¬ν–ˆλ‹€. + Future<(bool success, String message)> _createStudentRequest({ + required String studentId, + required String name, + required String password, + int? grade, + }) async { try { final url = Uri.parse('$baseUrl/api/users/create'); final response = await http.post( @@ -38,9 +162,6 @@ class TeacherStudentManagementController extends ChangeNotifier { } } catch (e) { return (false, '🚨 λ„€νŠΈμ›Œν¬ μ—λŸ¬: $e'); - } finally { - _isWorking = false; - notifyListeners(); } } diff --git a/lib/ui/teacher_student_management_page.dart b/lib/ui/teacher_student_management_page.dart index a71263b..0d5a403 100644 --- a/lib/ui/teacher_student_management_page.dart +++ b/lib/ui/teacher_student_management_page.dart @@ -1,6 +1,9 @@ // πŸ‘₯ κ΅μ‚¬μš© 학생 계정 관리 ν™”λ©΄ (UI μ „μš©). μ‹ κ·œ 학생 계정 μΆ”κ°€ 폼과 κ°•μ œ μ‚­μ œ λ‹€μ΄μ–Όλ‘œκ·Έλ₯Ό κ·Έλ¦°λ‹€. // μ„œλ²„ 톡신/μƒνƒœλŠ” lib/function/teacher_student_management_controller.dartκ°€ λ‹΄λ‹Ήν•œλ‹€. +import 'dart:typed_data'; +import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; +import '../function/excel_file_picker.dart'; import '../function/teacher_student_management_controller.dart'; // ----------------------------------------------------------------------------- @@ -22,6 +25,7 @@ class _TeacherStudentManagementPageState final TextEditingController _addNameController = TextEditingController(); final TextEditingController _addPwController = TextEditingController(); int? _selectedGrade; + bool _isDragging = false; @override void dispose() { @@ -63,6 +67,183 @@ class _TeacherStudentManagementPageState ).showSnackBar(SnackBar(content: Text(message))); } + // πŸ“„ [파일 선택 λ²„νŠΌ λ™μž‘] "파일 선택"으둜 μ—‘μ…€ κ³ λ₯΄κΈ° + Future _pickExcelFile() async { + final bytes = await pickExcelFileBytes(); + if (bytes == null) return; + if (!mounted) return; + await _handleExcelBytes(bytes); + } + + // πŸ“„ μ—‘μ…€ λ°”μ΄νŠΈλ₯Ό νŒŒμ‹±ν•΄μ„œ 미리보기 λ‹€μ΄μ–Όλ‘œκ·Έλ₯Ό λ„μš΄λ‹€ (λ“œλž˜κ·Έ/νŒŒμΌμ„ νƒ 곡용). + Future _handleExcelBytes(Uint8List bytes) async { + List rows; + try { + rows = _controller.parseExcelBytes(bytes); + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('❌ μ—‘μ…€ νŒŒμΌμ„ μ½λŠ” 데 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: $e'))); + return; + } + if (rows.isEmpty) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('⚠️ μœ νš¨ν•œ 학생 데이터λ₯Ό μ°Ύμ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€. (1행은 λ¨Έλ¦¬κΈ€λ‘œ κ±΄λ„ˆλœλ‹ˆλ‹€)'), + ), + ); + return; + } + _showBulkPreviewDialog(rows); + } + + // πŸ“‹ [일괄 등둝 확인 νŒμ—…] μ—‘μ…€μ—μ„œ 뽑아낸 λͺ…단을 미리 보여주고 ν™•μ •λ°›λŠ”λ‹€. + void _showBulkPreviewDialog(List rows) { + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + title: Text('πŸ“„ ${rows.length}λͺ… 확인됨'), + content: SizedBox( + width: 400, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'μ•„λž˜ λͺ…λ‹¨μœΌλ‘œ 계정을 일괄 μƒμ„±ν•©λ‹ˆλ‹€ (초기 λΉ„λ°€λ²ˆν˜Έ: 1234).', + style: TextStyle(color: Colors.black54, fontSize: 13), + ), + const SizedBox(height: 12), + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 300), + child: ListView.builder( + shrinkWrap: true, + itemCount: rows.length, + itemBuilder: (context, i) { + final row = rows[i]; + return ListTile( + dense: true, + leading: CircleAvatar( + radius: 14, + child: Text( + '${i + 1}', + style: const TextStyle(fontSize: 11), + ), + ), + title: Text('${row.name} (${row.studentId})'), + trailing: Text( + row.grade != null ? '${row.grade}ν•™λ…„' : 'λ―Έλ°°μ •', + style: TextStyle(color: Colors.grey[600]), + ), + ); + }, + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('μ·¨μ†Œ', style: TextStyle(color: Colors.grey)), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + _runBulkImport(rows); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text('${rows.length}λͺ… 일괄 등둝'), + ), + ], + ), + ); + } + + // πŸš€ μ‹€μ œ 일괄 등둝 μ‹€ν–‰ + μ§„ν–‰ 상황 ν‘œμ‹œ + κ²°κ³Ό μš”μ•½ νŒμ—…. + Future _runBulkImport(List rows) async { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => ListenableBuilder( + listenable: _controller, + builder: (context, _) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + content: Row( + children: [ + const CircularProgressIndicator(), + const SizedBox(width: 20), + Text( + '등둝 쀑... (${_controller.bulkDone}/${_controller.bulkTotal})', + ), + ], + ), + ), + ), + ); + + final result = await _controller.bulkAddStudents(rows); + + if (!mounted) return; + Navigator.pop(context); // μ§„ν–‰ νŒμ—… λ‹«κΈ° + + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), + title: Text( + result.failures.isEmpty ? 'βœ… 일괄 등둝 μ™„λ£Œ' : '⚠️ 일괄 등둝 μ™„λ£Œ (일뢀 μ‹€νŒ¨)', + ), + content: SizedBox( + width: 400, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '성곡 ${result.successCount}λͺ… / μ‹€νŒ¨ ${result.failures.length}λͺ…', + ), + if (result.failures.isNotEmpty) ...[ + const SizedBox(height: 12), + const Text( + 'μ‹€νŒ¨ λͺ©λ‘', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 6), + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: SingleChildScrollView( + child: Text( + result.failures.join('\n'), + style: const TextStyle(fontSize: 12, color: Colors.red), + ), + ), + ), + ], + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('확인'), + ), + ], + ), + ); + } + // 🏫 [κΈ°μ‘΄ 학생 ν•™λ…„ μ§€μ •/λ³€κ²½ λ‹€μ΄μ–Όλ‘œκ·Έ] void _showUpdateGradeDialog() { final TextEditingController idController = TextEditingController(); @@ -377,6 +558,90 @@ class _TeacherStudentManagementPageState ), const SizedBox(height: 36), + // 🏷️ 인디케이터 λ°” (μ—‘μ…€ 일괄 등둝) + Row( + children: [ + Container( + width: 4, + height: 16, + decoration: BoxDecoration( + color: Colors.orange, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + const Text( + 'μ—‘μ…€λ‘œ ν•œλ²ˆμ— μΆ”κ°€ (μ‹ μž…μƒ λ“±)', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 16), + + DropTarget( + onDragEntered: (_) => setState(() => _isDragging = true), + onDragExited: (_) => setState(() => _isDragging = false), + onDragDone: (details) async { + setState(() => _isDragging = false); + if (details.files.isEmpty) return; + final bytes = await details.files.first.readAsBytes(); + if (!mounted) return; + await _handleExcelBytes(bytes); + }, + child: InkWell( + onTap: _pickExcelFile, + borderRadius: BorderRadius.circular(24), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 28), + decoration: BoxDecoration( + color: _isDragging ? Colors.orange[50] : Colors.white, + borderRadius: BorderRadius.circular(24), + border: Border.all( + color: _isDragging + ? Colors.orange + : Colors.grey.shade300, + width: _isDragging ? 2 : 1, + ), + ), + child: Column( + children: [ + Icon( + Icons.upload_file_rounded, + size: 40, + color: _isDragging + ? Colors.orange + : Colors.grey[400], + ), + const SizedBox(height: 12), + Text( + _isDragging ? '여기에 λ†“μœΌμ„Έμš”' : 'μ—‘μ…€ νŒŒμΌμ„ λ“œλž˜κ·Έν•˜κ±°λ‚˜ λˆŒλŸ¬μ„œ 선택', + style: TextStyle( + fontWeight: FontWeight.bold, + color: _isDragging + ? Colors.orange[800] + : Colors.black87, + ), + ), + const SizedBox(height: 4), + Text( + '.xlsx Β· 1행은 머리글, Aμ—΄=ν•™λ²ˆ Bμ—΄=이름 Cμ—΄=ν•™λ…„(선택)', + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ), + const SizedBox(height: 36), + // 🏷️ 인디케이터 λ°” (κΈ°μ‘΄ 학생 ν•™λ…„ μ§€μ •/λ³€κ²½) Row( children: [ diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..8fdc934 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) desktop_drop_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); + desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..721c8d7 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_drop ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8fac3c8..99d9634 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,13 +5,17 @@ import FlutterMacOS import Foundation +import desktop_drop import device_info_plus +import file_picker_darwin import firebase_core import firebase_messaging import flutter_local_notifications func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) diff --git a/pubspec.lock b/pubspec.lock index ac6a778..cd5ebe6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.75" + android_file_picker: + dependency: transitive + description: + name: android_file_picker + sha256: "1f111ed6bb33724ba782cc86357e3fd97f57051d55fc61adf33dcfc5049a1581" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.dev" + source: hosted + version: "3.6.1" args: dependency: transitive description: @@ -57,6 +73,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: f141ea4f277af142a0356955707f6556f37b03947d39d55585981a06ca437bd6 + url: "https://pub.dev" + source: hosted + version: "0.3.5+5" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -73,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.14" + desktop_drop: + dependency: "direct main" + description: + name: desktop_drop + sha256: c65d1f959ccf4bb6803f984303d03e1e0dd4c0f73a3a2eca2d1b46d48ea16999 + url: "https://pub.dev" + source: hosted + version: "0.8.2" device_info_plus: dependency: "direct main" description: @@ -89,6 +129,22 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + excel: + dependency: "direct main" + description: + name: excel + sha256: "1a15327dcad260d5db21d1f6e04f04838109b39a2f6a84ea486ceda36e468780" + url: "https://pub.dev" + source: hosted + version: "4.0.6" fake_async: dependency: transitive description: @@ -121,6 +177,46 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: b7acb5d123cb398f6b4a8a38e43777545254f8fc1fabef3ee11cbbb56aece9c8 + url: "https://pub.dev" + source: hosted + version: "12.1.2" + file_picker_darwin: + dependency: transitive + description: + name: file_picker_darwin + sha256: "6e7cae501d48fd57a27911608db718ebd898e6ccf934bf09e33a8c0d0365bec0" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + file_picker_linux: + dependency: transitive + description: + name: file_picker_linux + sha256: f0f01ed42967b7355f6f25c8b121ea531d1948e2a9b4b44f4b4de8489d7b04ae + url: "https://pub.dev" + source: hosted + version: "1.0.2" + file_picker_platform_interface: + dependency: transitive + description: + name: file_picker_platform_interface + sha256: "11ef1b5c14d9186b4788cc273dd8d5cb81bca847145060991a28234ff7916fc1" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + file_picker_web: + dependency: transitive + description: + name: file_picker_web + sha256: df472142f63c4557fdfbb375c81454ca1c251491069eefcdc6a866bc12f8750b + url: "https://pub.dev" + source: hosted + version: "3.0.3" firebase_core: dependency: "direct main" description: @@ -517,6 +613,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" vector_math: dependency: transitive description: @@ -550,7 +654,7 @@ packages: source: hosted version: "15.2.0" web: - dependency: transitive + dependency: "direct main" description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" @@ -573,6 +677,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + windows_file_picker: + dependency: transitive + description: + name: windows_file_picker + sha256: "225f58e64c15c2d7b34fb8faf3ca188831967f26b5a723d7c3a7969e41c9b5a5" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9f3c497..5b6c50b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,10 @@ dependencies: # πŸ“³ 무단 반좜 감지 μ‹œ 진동 (λ°±κ·ΈλΌμš΄λ“œ μ„œλΉ„μŠ€μ—μ„œλ„ λ™μž‘ν•˜λ„λ‘ HapticFeedback λŒ€μ‹  μ‚¬μš©) vibration: ^3.2.0 + excel: ^4.0.6 + file_picker: ^12.1.2 + desktop_drop: ^0.8.2 + web: ^1.1.1 dev_dependencies: flutter_test: @@ -63,11 +67,6 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^6.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: # The following line ensures that the Material Icons font is diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 5893c2f..acda2b1 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + DesktopDropPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopDropPlugin")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4e30109..612d637 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_drop firebase_core permission_handler_windows )