feat: export/import JSON des sessions, stats et armurerie via partage systeme
This commit is contained in:
@@ -80,6 +80,19 @@ flutter test --coverage
|
||||
- **Distribution régionale** : répartition des tirs par quadrant
|
||||
- Filtrage par période : session, semaine, mois, toutes les sessions
|
||||
|
||||
### Sauvegarde (export / import JSON)
|
||||
- Bouton **Exporter** en bas de l'écran Statistiques : génère un fichier JSON
|
||||
(sessions + cibles + impacts + calibration, armurerie + entretien, et un
|
||||
instantané des statistiques calculées) puis ouvre la feuille de partage du
|
||||
système (`share_plus`) pour l'envoyer où l'on veut
|
||||
- Option « Inclure les photos des cibles » : photos encodées en base64 dans le
|
||||
JSON (sauvegarde complète mais fichier lourd) ; sans elles le fichier reste léger
|
||||
- Bouton **Importer** (`file_selector`) : aperçu du contenu avant confirmation,
|
||||
puis fusion avec les données existantes — même identifiant = mise à jour,
|
||||
donc réimporter deux fois ne crée pas de doublon et rien n'est effacé
|
||||
- Les statistiques ne sont pas réimportées : elles sont recalculées à partir des
|
||||
sessions. Elles figurent dans le fichier pour être exploitables telles quelles
|
||||
|
||||
### Historique des sessions
|
||||
- Sauvegarde des sessions avec date, score, notes
|
||||
- Visualisation des sessions passées
|
||||
@@ -121,3 +134,4 @@ session_list_item.dart Item de liste représentant une session
|
||||
history_chart.dart Graphique d'évolution des 10 dernières sessions
|
||||
statistics_screen.dart Écran statistiques avec filtrage par période
|
||||
heat_map_widget.dart Heat map avec gradient bleu→rouge
|
||||
backup_service.dart Export/import JSON des sessions, stats et armurerie
|
||||
|
||||
@@ -590,6 +590,16 @@ class DatabaseHelper {
|
||||
);
|
||||
}
|
||||
|
||||
/// Toutes les entrées de maintenance, armes confondues (export/sauvegarde).
|
||||
Future<List<MaintenanceEntry>> getAllMaintenance() async {
|
||||
final db = await database;
|
||||
final maps = await db.query(
|
||||
AppConstants.maintenanceTable,
|
||||
orderBy: 'date DESC',
|
||||
);
|
||||
return List.generate(maps.length, (i) => MaintenanceEntry.fromMap(maps[i]));
|
||||
}
|
||||
|
||||
Future<List<MaintenanceEntry>> getMaintenanceForWeapon(String weaponId) async {
|
||||
final db = await database;
|
||||
final maps = await db.query(
|
||||
|
||||
@@ -131,6 +131,29 @@ class SessionRepository {
|
||||
return await _databaseHelper.getStatistics();
|
||||
}
|
||||
|
||||
/// Enregistre une session déjà construite (import de sauvegarde).
|
||||
/// Les identifiants existants sont écrasés : réimporter deux fois la même
|
||||
/// sauvegarde ne crée pas de doublons.
|
||||
Future<void> saveSession(Session session) async {
|
||||
await _databaseHelper.insertSession(session);
|
||||
}
|
||||
|
||||
/// Copie une image dans le dossier des cibles de l'app (import de sauvegarde).
|
||||
Future<String> saveImageBytes(List<int> bytes, String extension) async {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final imagesDir = Directory(path.join(appDir.path, 'target_images'));
|
||||
|
||||
if (!await imagesDir.exists()) {
|
||||
await imagesDir.create(recursive: true);
|
||||
}
|
||||
|
||||
final fileName = '${_uuid.v4()}$extension';
|
||||
final destPath = path.join(imagesDir.path, fileName);
|
||||
await File(destPath).writeAsBytes(bytes);
|
||||
|
||||
return destPath;
|
||||
}
|
||||
|
||||
String generateId() {
|
||||
return _uuid.v4();
|
||||
}
|
||||
@@ -170,6 +193,11 @@ class SessionRepository {
|
||||
return await _databaseHelper.getAllWeapons();
|
||||
}
|
||||
|
||||
/// Enregistre une arme déjà construite (import de sauvegarde).
|
||||
Future<void> saveWeapon(Weapon weapon) async {
|
||||
await _databaseHelper.insertWeapon(weapon);
|
||||
}
|
||||
|
||||
Future<void> updateWeapon(Weapon weapon) async {
|
||||
await _databaseHelper.updateWeapon(weapon);
|
||||
}
|
||||
@@ -208,6 +236,16 @@ class SessionRepository {
|
||||
return await _databaseHelper.getMaintenanceForWeapon(weaponId);
|
||||
}
|
||||
|
||||
/// Tout l'historique de maintenance de l'armurerie (export de sauvegarde).
|
||||
Future<List<MaintenanceEntry>> getAllMaintenance() async {
|
||||
return await _databaseHelper.getAllMaintenance();
|
||||
}
|
||||
|
||||
/// Enregistre une entrée de maintenance déjà construite (import).
|
||||
Future<void> saveMaintenanceEntry(MaintenanceEntry entry) async {
|
||||
await _databaseHelper.insertMaintenance(entry);
|
||||
}
|
||||
|
||||
Future<void> deleteMaintenanceEntry(String id) async {
|
||||
await _databaseHelper.deleteMaintenance(id);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@ import '../../data/repositories/session_repository.dart';
|
||||
import 'weapon_detail_screen.dart';
|
||||
|
||||
class WeaponListScreen extends StatefulWidget {
|
||||
const WeaponListScreen({super.key});
|
||||
/// Incrémenté par la navigation à chaque ouverture de l'onglet Armurerie
|
||||
/// (l'écran est gardé vivant par l'IndexedStack et ne se rafraîchit pas
|
||||
/// seul : sans ça, un import de sauvegarde resterait invisible).
|
||||
final int refreshTick;
|
||||
|
||||
const WeaponListScreen({super.key, this.refreshTick = 0});
|
||||
|
||||
@override
|
||||
State<WeaponListScreen> createState() => _WeaponListScreenState();
|
||||
@@ -23,6 +28,14 @@ class _WeaponListScreenState extends State<WeaponListScreen> {
|
||||
_loadWeapons();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(WeaponListScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.refreshTick != widget.refreshTick) {
|
||||
_loadWeapons();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadWeapons() async {
|
||||
final repository = context.read<SessionRepository>();
|
||||
final weapons = await repository.getWeapons();
|
||||
|
||||
@@ -15,7 +15,12 @@ import '../session/session_provider.dart';
|
||||
import 'widgets/stats_card.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
/// Incrémenté par la navigation à chaque ouverture de l'onglet Accueil
|
||||
/// (l'écran est gardé vivant par l'IndexedStack : sans ça, un import de
|
||||
/// sauvegarde resterait invisible sur le dashboard).
|
||||
final int refreshTick;
|
||||
|
||||
const HomeScreen({super.key, this.refreshTick = 0});
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
@@ -35,6 +40,14 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
_loadStats();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(HomeScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.refreshTick != widget.refreshTick) {
|
||||
_loadStats();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fl_chart/fl_chart.dart'; // La librairie pour les courbes
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import '../../core/widgets/metric_info_button.dart';
|
||||
import '../../data/models/session.dart';
|
||||
import '../../data/repositories/session_repository.dart';
|
||||
import '../../services/backup_service.dart';
|
||||
import '../../services/statistics_service.dart';
|
||||
|
||||
class StatisticsScreen extends StatefulWidget {
|
||||
@@ -43,6 +48,10 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
|
||||
bool _showingB = false; // false = on affiche A, true = on affiche B
|
||||
bool get _compareMode => _compareA != null && _compareB != null;
|
||||
|
||||
// --- Sauvegarde (export/import JSON) ---
|
||||
bool _isBackupBusy = false;
|
||||
final GlobalKey _exportButtonKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -374,6 +383,11 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
|
||||
_buildBiasWarning(),
|
||||
],
|
||||
|
||||
const SizedBox(height: 25),
|
||||
|
||||
// 5. SAUVEGARDE : export/import de toutes les données
|
||||
_buildBackupSection(),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
@@ -882,6 +896,263 @@ class _StatisticsScreenState extends State<StatisticsScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// --- SAUVEGARDE : EXPORT / IMPORT JSON ---
|
||||
|
||||
Widget _buildBackupSection() {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.save_alt, color: theme.textTheme.titleMedium?.color, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Sauvegarde',
|
||||
style: TextStyle(
|
||||
color: theme.textTheme.titleMedium?.color,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Exporte toutes tes sessions, tes stats et ton armurerie dans un '
|
||||
'fichier JSON, à envoyer où tu veux. L\'import fusionne le fichier '
|
||||
'avec tes données actuelles (rien n\'est effacé).',
|
||||
style: TextStyle(
|
||||
color: theme.textTheme.bodySmall?.color?.withValues(alpha: 0.6),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
key: _exportButtonKey,
|
||||
onPressed: _isBackupBusy ? null : _exportBackup,
|
||||
icon: const Icon(Icons.ios_share, size: 18),
|
||||
label: const Text('Exporter'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: _isBackupBusy ? null : _importBackup,
|
||||
icon: const Icon(Icons.file_download_outlined, size: 18),
|
||||
label: const Text('Importer'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_isBackupBusy) ...[
|
||||
const SizedBox(height: 12),
|
||||
const LinearProgressIndicator(minHeight: 2),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BackupService _backupService() =>
|
||||
BackupService(repository: context.read<SessionRepository>());
|
||||
|
||||
Future<void> _exportBackup() async {
|
||||
final includeImages = await _askIncludeImages();
|
||||
if (includeImages == null || !mounted) return;
|
||||
|
||||
setState(() => _isBackupBusy = true);
|
||||
try {
|
||||
final file = await _backupService().exportToFile(
|
||||
includeImages: includeImages,
|
||||
);
|
||||
if (!mounted) return;
|
||||
|
||||
// sharePositionOrigin : obligatoire pour l'iPad, ignoré ailleurs.
|
||||
final box = _exportButtonKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
final origin = box != null && box.hasSize
|
||||
? box.localToGlobal(Offset.zero) & box.size
|
||||
: null;
|
||||
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [XFile(file.path, mimeType: 'application/json')],
|
||||
fileNameOverrides: [p.basename(file.path)],
|
||||
subject: 'Sauvegarde IMPACT',
|
||||
text: 'Sauvegarde de mes sessions de tir (${p.basename(file.path)})',
|
||||
sharePositionOrigin: origin,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
_showMessage('Export impossible : $e', isError: true);
|
||||
} finally {
|
||||
if (mounted) setState(() => _isBackupBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Les photos de cibles alourdissent énormément le fichier : on laisse le
|
||||
/// choix entre une sauvegarde légère (données seules) et une sauvegarde
|
||||
/// complète (photos encodées dans le JSON).
|
||||
Future<bool?> _askIncludeImages() {
|
||||
var includeImages = false;
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setDialogState) => AlertDialog(
|
||||
title: const Text('Exporter mes données'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Le fichier contiendra toutes tes sessions (cibles, impacts, '
|
||||
'scores), tes statistiques et ton armurerie.',
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
value: includeImages,
|
||||
onChanged: (v) => setDialogState(() => includeImages = v),
|
||||
title: const Text('Inclure les photos des cibles'),
|
||||
subtitle: const Text(
|
||||
'Sauvegarde complète, mais fichier beaucoup plus lourd.',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context, includeImages),
|
||||
child: const Text('Exporter'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _importBackup() async {
|
||||
const jsonGroup = XTypeGroup(
|
||||
label: 'Sauvegarde IMPACT (.json)',
|
||||
extensions: ['json'],
|
||||
// Android renvoie parfois un type générique pour un .json : on accepte
|
||||
// large, le contenu est validé à la lecture de toute façon.
|
||||
mimeTypes: ['application/json', 'text/plain', 'application/octet-stream'],
|
||||
uniformTypeIdentifiers: ['public.json', 'public.text'],
|
||||
);
|
||||
|
||||
final picked = await openFile(acceptedTypeGroups: const [jsonGroup]);
|
||||
if (picked == null || !mounted) return;
|
||||
|
||||
setState(() => _isBackupBusy = true);
|
||||
try {
|
||||
final service = _backupService();
|
||||
final preview = await service.readBackup(File(picked.path));
|
||||
if (!mounted) return;
|
||||
|
||||
final confirmed = await _confirmImport(preview);
|
||||
if (confirmed != true || !mounted) return;
|
||||
|
||||
final result = await service.applyBackup(preview);
|
||||
await _loadStatistics();
|
||||
if (!mounted) return;
|
||||
|
||||
final details = [
|
||||
'${result.sessions} session(s)',
|
||||
'${result.weapons} arme(s)',
|
||||
if (result.maintenance > 0) '${result.maintenance} entretien(s)',
|
||||
if (result.images > 0) '${result.images} photo(s)',
|
||||
].join(' · ');
|
||||
_showMessage(
|
||||
result.errors.isEmpty
|
||||
? 'Import terminé : $details'
|
||||
: 'Import terminé : $details — ${result.errors.length} entrée(s) ignorée(s)',
|
||||
);
|
||||
} on BackupFormatException catch (e) {
|
||||
_showMessage(e.message, isError: true);
|
||||
} catch (e) {
|
||||
_showMessage('Import impossible : $e', isError: true);
|
||||
} finally {
|
||||
if (mounted) setState(() => _isBackupBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool?> _confirmImport(BackupPreview preview) {
|
||||
final date = preview.exportedAt;
|
||||
final dateLabel = date == null
|
||||
? null
|
||||
: '${date.day.toString().padLeft(2, '0')}/'
|
||||
'${date.month.toString().padLeft(2, '0')}/${date.year}';
|
||||
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Importer cette sauvegarde ?'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (dateLabel != null) Text('Exportée le $dateLabel'),
|
||||
if (dateLabel != null) const SizedBox(height: 8),
|
||||
Text('• ${preview.sessionCount} session(s)'),
|
||||
Text('• ${preview.targetCount} cible(s), ${preview.shotCount} impact(s)'),
|
||||
Text('• ${preview.weaponCount} arme(s), ${preview.maintenanceCount} entretien(s)'),
|
||||
Text(preview.hasImages
|
||||
? '• Photos des cibles incluses'
|
||||
: '• Sans photos de cibles'),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'Les données actuelles sont conservées. Une session déjà '
|
||||
'présente est simplement mise à jour.',
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Importer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showMessage(String message, {bool isError = false}) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: isError ? Colors.red.shade700 : null,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HeatMapPainter extends CustomPainter {
|
||||
|
||||
@@ -37,23 +37,27 @@ class _MainNavigationHolderState extends State<MainNavigationHolder> {
|
||||
// rafraîchissent pas seuls).
|
||||
int _statsTick = 0;
|
||||
int _historyTick = 0;
|
||||
int _homeTick = 0;
|
||||
int _garageTick = 0;
|
||||
|
||||
void selectTab(int index) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
if (index == mainTabHome) _homeTick++;
|
||||
if (index == mainTabHistory) _historyTick++;
|
||||
if (index == mainTabStats) _statsTick++;
|
||||
if (index == mainTabGarage) _garageTick++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screens = [
|
||||
const HomeScreen(),
|
||||
HomeScreen(refreshTick: _homeTick),
|
||||
HistoryScreen(refreshTick: _historyTick),
|
||||
StatisticsScreen(refreshTick: _statsTick),
|
||||
const WeaponListScreen(),
|
||||
WeaponListScreen(refreshTick: _garageTick),
|
||||
];
|
||||
return Scaffold(
|
||||
body: IndexedStack(
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
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) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -7,12 +7,14 @@ import Foundation
|
||||
|
||||
import device_info_plus
|
||||
import file_selector_macos
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
}
|
||||
|
||||
@@ -201,6 +201,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_selector:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_selector
|
||||
sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
file_selector_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_android
|
||||
sha256: "89243030ea4b3463fb402b44d5eeacc4ccb1c46a88870cb2a5080d693200c1ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.2+6"
|
||||
file_selector_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_ios
|
||||
sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.3+5"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -225,6 +249,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
file_selector_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_web
|
||||
sha256: "73181fbc5257776d8ecaa6a94ab3c8e920ad143b9132a6d984a9271dfc6928d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.5"
|
||||
file_selector_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -709,6 +741,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "34f00f9becd2743c1fb05363d624f9f70d37f7ccdcdda47450bc0b8c9d327b8c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.3.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: "365ef7379fc22507256adda3385152942ffce08935452bc972c2e52a0bebae41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -898,6 +946,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -70,6 +70,8 @@ dependencies:
|
||||
shared_preferences: ^2.5.5
|
||||
crypto: ^3.0.7
|
||||
camera: ^0.12.0+1
|
||||
share_plus: ^13.3.0
|
||||
file_selector: ^1.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -8,10 +8,16 @@
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
permission_handler_windows
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Reference in New Issue
Block a user