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:
Lukas Müllner 2026-02-23 07:12:24 +01:00
parent 200ea11098
commit 2c3c6d990b
9 changed files with 611 additions and 6 deletions

View file

@ -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
View 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: 25 uppercase letters followed by
// 24 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);
}
}
}

View file

@ -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);

View file

@ -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
) )

View file

@ -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"))
} }

View file

@ -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:

View file

@ -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:

View file

@ -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"));
} }

View file

@ -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
) )