feat: add camera OCR scanner, DB query, and collection dialogs
- Add camera, google_mlkit_text_recognition, image_picker, permission_handler deps
- Create ScannerScreen with live camera preview and OCR text recognition
- Auto-detect Hot Wheels IDs (e.g. JKF21) from captured photos using regex
- Add manual entry fallback dialog for typing HW ID by hand
- Update HomeScreen with scan FAB button and full DB query flow
- Query hotwheels table with .eq('hw_id', scannedCode).maybeSingle()
- Show 'Already in Collection' alert if car exists
- Show 'Add to Collection?' confirmation with insert if car is new
This commit is contained in:
parent
200ea11098
commit
2c3c6d990b
9 changed files with 611 additions and 6 deletions
122
lib/main.dart
122
lib/main.dart
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
import 'scanner_screen.dart';
|
||||||
|
|
||||||
// ── Supabase credentials ──────────────────────────────────────────────
|
// ── Supabase credentials ──────────────────────────────────────────────
|
||||||
const _supabaseUrl = 'https://yaopcyubateifnicpywp.supabase.co';
|
const _supabaseUrl = 'https://yaopcyubateifnicpywp.supabase.co';
|
||||||
// TODO: Paste your anon key from Supabase Dashboard → Settings → API.
|
const _supabaseAnonKey = 'sb_publishable_a7czIl7-TGeBJvid9z2XZA_3ElImliL';
|
||||||
const _supabaseAnonKey = 'YOUR_ANON_KEY';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
@ -298,7 +298,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Home Screen (placeholder) ─────────────────────────────────────────
|
// ── Home Screen ───────────────────────────────────────────────────────
|
||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
|
|
@ -319,11 +319,121 @@ class HomeScreen extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text(
|
child: Column(
|
||||||
'Signed in as ${user?.email ?? 'unknown'}',
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
children: [
|
||||||
|
Text(
|
||||||
|
'Signed in as ${user?.email ?? 'unknown'}',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
const Icon(Icons.directions_car, size: 80, color: Colors.deepPurple),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Scan or enter a Hot Wheels ID\nto check your collection.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
|
icon: const Icon(Icons.camera_alt),
|
||||||
|
label: const Text('Scan'),
|
||||||
|
onPressed: () => _openScanner(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the scanner, gets the hw_id, then queries the DB.
|
||||||
|
Future<void> _openScanner(BuildContext context) async {
|
||||||
|
// 1. Navigate to the scanner screen and wait for the result.
|
||||||
|
final hwId = await Navigator.of(context).push<String>(
|
||||||
|
MaterialPageRoute(builder: (_) => const ScannerScreen()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// User cancelled / went back without selecting an ID.
|
||||||
|
if (hwId == null || !context.mounted) return;
|
||||||
|
|
||||||
|
// 2. Query the database.
|
||||||
|
try {
|
||||||
|
final data = await supabase
|
||||||
|
.from('hotwheels')
|
||||||
|
.select()
|
||||||
|
.eq('hw_id', hwId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
// ── Car already exists ──
|
||||||
|
_showAlreadyExistsDialog(context, hwId);
|
||||||
|
} else {
|
||||||
|
// ── Car is new — offer to add it ──
|
||||||
|
_showAddDialog(context, hwId);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('DB error: $e'), backgroundColor: Colors.red),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows an alert: this car is already in the collection.
|
||||||
|
void _showAlreadyExistsDialog(BuildContext context, String hwId) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
icon: const Icon(Icons.check_circle, color: Colors.green, size: 48),
|
||||||
|
title: const Text('Already in Collection!'),
|
||||||
|
content: Text('$hwId is already in your shared garage.'),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
|
child: const Text('Got it'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows a confirmation dialog to add a new car.
|
||||||
|
void _showAddDialog(BuildContext context, String hwId) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
icon: const Icon(Icons.add_circle_outline, color: Colors.deepPurple, size: 48),
|
||||||
|
title: const Text('New Car Found!'),
|
||||||
|
content: Text('$hwId is not in your collection yet.\nAdd it now?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
await supabase.from('hotwheels').insert({
|
||||||
|
'hw_id': hwId,
|
||||||
|
'user_id': supabase.auth.currentUser!.id,
|
||||||
|
});
|
||||||
|
if (!ctx.mounted) return;
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('$hwId added to your collection! 🎉')),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!ctx.mounted) return;
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Failed to add: $e'), backgroundColor: Colors.red),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Add to Collection'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
252
lib/scanner_screen.dart
Normal file
252
lib/scanner_screen.dart
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:camera/camera.dart';
|
||||||
|
import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
|
||||||
|
|
||||||
|
/// Screen that uses the camera to scan text (OCR) from a Hot Wheels package
|
||||||
|
/// and extract the hw_id (e.g. "JKF21").
|
||||||
|
///
|
||||||
|
/// The detected ID is returned via Navigator.pop(context, hwId).
|
||||||
|
class ScannerScreen extends StatefulWidget {
|
||||||
|
const ScannerScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScannerScreen> createState() => _ScannerScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScannerScreenState extends State<ScannerScreen> {
|
||||||
|
CameraController? _cameraController;
|
||||||
|
late final TextRecognizer _textRecognizer;
|
||||||
|
bool _isBusy = false;
|
||||||
|
bool _cameraReady = false;
|
||||||
|
String? _lastDetected;
|
||||||
|
|
||||||
|
// Matches typical Hot Wheels model IDs: 2–5 uppercase letters followed by
|
||||||
|
// 2–4 digits, e.g. JKF21, HCV73, GRX33, FYD83.
|
||||||
|
final _hwIdPattern = RegExp(r'\b([A-Z]{2,5}\d{2,4})\b');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_textRecognizer = TextRecognizer();
|
||||||
|
_initCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initCamera() async {
|
||||||
|
final cameras = await availableCameras();
|
||||||
|
if (cameras.isEmpty) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('No camera available')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the first back-facing camera.
|
||||||
|
final backCamera = cameras.firstWhere(
|
||||||
|
(c) => c.lensDirection == CameraLensDirection.back,
|
||||||
|
orElse: () => cameras.first,
|
||||||
|
);
|
||||||
|
|
||||||
|
_cameraController = CameraController(
|
||||||
|
backCamera,
|
||||||
|
ResolutionPreset.high,
|
||||||
|
enableAudio: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _cameraController!.initialize();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _cameraReady = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Capture a photo, run OCR, and look for a Hot Wheels ID.
|
||||||
|
Future<void> _captureAndScan() async {
|
||||||
|
if (_isBusy || _cameraController == null || !_cameraController!.value.isInitialized) return;
|
||||||
|
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final xFile = await _cameraController!.takePicture();
|
||||||
|
final inputImage = InputImage.fromFilePath(xFile.path);
|
||||||
|
final recognized = await _textRecognizer.processImage(inputImage);
|
||||||
|
|
||||||
|
// Search all recognized text blocks for something matching the HW ID pattern.
|
||||||
|
String? found;
|
||||||
|
for (final block in recognized.blocks) {
|
||||||
|
for (final line in block.lines) {
|
||||||
|
final match = _hwIdPattern.firstMatch(line.text.toUpperCase());
|
||||||
|
if (match != null) {
|
||||||
|
found = match.group(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found != null) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the temp image.
|
||||||
|
try {
|
||||||
|
await File(xFile.path).delete();
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (found != null) {
|
||||||
|
setState(() => _lastDetected = found);
|
||||||
|
} else {
|
||||||
|
// Show all detected text so user knows what was seen.
|
||||||
|
final allText = recognized.blocks.map((b) => b.text).join('\n');
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
allText.isEmpty
|
||||||
|
? 'No text detected — try again closer.'
|
||||||
|
: 'No HW ID found. Detected:\n$allText',
|
||||||
|
),
|
||||||
|
duration: const Duration(seconds: 4),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Scan error: $e'), backgroundColor: Colors.red),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_cameraController?.dispose();
|
||||||
|
_textRecognizer.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Scan Hot Wheels ID')),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
// ── Camera preview ──
|
||||||
|
Expanded(
|
||||||
|
child: _cameraReady
|
||||||
|
? ClipRect(
|
||||||
|
child: SizedBox.expand(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
child: SizedBox(
|
||||||
|
width: _cameraController!.value.previewSize!.height,
|
||||||
|
height: _cameraController!.value.previewSize!.width,
|
||||||
|
child: CameraPreview(_cameraController!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ── Detected ID confirmation area ──
|
||||||
|
if (_lastDetected != null)
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
color: Colors.green.shade50,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check_circle, color: Colors.green),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Detected: $_lastDetected',
|
||||||
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(_lastDetected),
|
||||||
|
child: const Text('Use'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ── Bottom controls ──
|
||||||
|
SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Manual entry button
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton.icon(
|
||||||
|
icon: const Icon(Icons.keyboard),
|
||||||
|
label: const Text('Enter manually'),
|
||||||
|
onPressed: () => _showManualEntry(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
// Capture / scan button
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
icon: _isBusy
|
||||||
|
? const SizedBox(
|
||||||
|
height: 18,
|
||||||
|
width: 18,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.camera_alt),
|
||||||
|
label: Text(_isBusy ? 'Scanning...' : 'Scan'),
|
||||||
|
onPressed: _isBusy ? null : _captureAndScan,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback: let the user type the HW ID manually.
|
||||||
|
Future<void> _showManualEntry(BuildContext context) async {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
|
||||||
|
final result = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: const Text('Enter HW ID'),
|
||||||
|
content: TextField(
|
||||||
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
|
textCapitalization: TextCapitalization.characters,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'e.g. JKF21',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
final value = controller.text.trim().toUpperCase();
|
||||||
|
if (value.isNotEmpty) Navigator.of(ctx).pop(value);
|
||||||
|
},
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.dispose();
|
||||||
|
|
||||||
|
if (result != null && context.mounted) {
|
||||||
|
Navigator.of(context).pop(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,14 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <gtk/gtk_plugin.h>
|
#include <gtk/gtk_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
|
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
||||||
gtk_plugin_register_with_registrar(gtk_registrar);
|
gtk_plugin_register_with_registrar(gtk_registrar);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
file_selector_linux
|
||||||
gtk
|
gtk
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,13 @@ import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import app_links
|
import app_links
|
||||||
|
import file_selector_macos
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||||
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
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"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
224
pubspec.lock
224
pubspec.lock
|
|
@ -57,6 +57,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
camera:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: camera
|
||||||
|
sha256: "4142a19a38e388d3bab444227636610ba88982e36dff4552d5191a86f65dc437"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.4"
|
||||||
|
camera_android_camerax:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_android_camerax
|
||||||
|
sha256: "8516fe308bc341a5067fb1a48edff0ddfa57c0d3cdcc9dbe7ceca3ba119e2577"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.30"
|
||||||
|
camera_avfoundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_avfoundation
|
||||||
|
sha256: "11b4aee2f5e5e038982e152b4a342c749b414aa27857899d20f4323e94cb5f0b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.23+2"
|
||||||
|
camera_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_platform_interface
|
||||||
|
sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.12.0"
|
||||||
|
camera_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_web
|
||||||
|
sha256: "57f49a635c8bf249d07fb95eb693d7e4dda6796dedb3777f9127fb54847beba7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5+3"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -97,6 +137,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5+2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -153,6 +201,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
file_selector_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_linux
|
||||||
|
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.4"
|
||||||
|
file_selector_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_macos
|
||||||
|
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.5"
|
||||||
|
file_selector_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_platform_interface
|
||||||
|
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
file_selector_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_windows
|
||||||
|
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+5"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -174,6 +254,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.33"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -200,6 +288,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
google_mlkit_commons:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: google_mlkit_commons
|
||||||
|
sha256: "7e9a6d6e66b44aa8cfe944bda9bc3346c52486dd890ca49e5bc98845cda40d7f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.0"
|
||||||
|
google_mlkit_text_recognition:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: google_mlkit_text_recognition
|
||||||
|
sha256: e7609cec8de3022680a36ead8a8bafa9fd2360ea018a728feaad12dcb0e3c177
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.0"
|
||||||
gotrue:
|
gotrue:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -240,6 +344,70 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
|
image_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_picker
|
||||||
|
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
image_picker_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_android
|
||||||
|
sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.13+14"
|
||||||
|
image_picker_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_for_web
|
||||||
|
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
image_picker_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_ios
|
||||||
|
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.13+6"
|
||||||
|
image_picker_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_linux
|
||||||
|
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
|
image_picker_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_macos
|
||||||
|
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2+1"
|
||||||
|
image_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_platform_interface
|
||||||
|
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.11.1"
|
||||||
|
image_picker_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_windows
|
||||||
|
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
jwt_decode:
|
jwt_decode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -392,6 +560,54 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
permission_handler:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: permission_handler
|
||||||
|
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.4.0"
|
||||||
|
permission_handler_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_android
|
||||||
|
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.1.0"
|
||||||
|
permission_handler_apple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_apple
|
||||||
|
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.4.7"
|
||||||
|
permission_handler_html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_html
|
||||||
|
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3+5"
|
||||||
|
permission_handler_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_platform_interface
|
||||||
|
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.0"
|
||||||
|
permission_handler_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_windows
|
||||||
|
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -549,6 +765,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ dependencies:
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
supabase_flutter: ^2.8.0
|
supabase_flutter: ^2.8.0
|
||||||
|
camera: ^0.11.1
|
||||||
|
google_mlkit_text_recognition: ^0.14.0
|
||||||
|
image_picker: ^1.1.2
|
||||||
|
permission_handler: ^11.3.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,17 @@
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <app_links/app_links_plugin_c_api.h>
|
#include <app_links/app_links_plugin_c_api.h>
|
||||||
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
|
#include <permission_handler_windows/permission_handler_windows_plugin.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) {
|
||||||
AppLinksPluginCApiRegisterWithRegistrar(
|
AppLinksPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
||||||
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
app_links
|
app_links
|
||||||
|
file_selector_windows
|
||||||
|
permission_handler_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue