wip customCachedResources

This commit is contained in:
Thomas Fransolet 2024-01-19 17:18:02 +01:00
parent 7a5342dc24
commit 29e4863493
16 changed files with 467 additions and 23 deletions

View File

@ -1,5 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="be.musee.de.la.fraise.tablet_app">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:label="MyMuseum"
android:usesCleartextTraffic="true"

View File

@ -1,3 +1,4 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -9,8 +10,9 @@ import 'package:tablet_app/app_context.dart';
class AudioPlayerFloatingContainer extends StatefulWidget {
const AudioPlayerFloatingContainer({Key? key, required this.audioBytes, required this.resourceURl, required this.isAuto}) : super(key: key);
const AudioPlayerFloatingContainer({Key? key, required this.file, required this.audioBytes, required this.resourceURl, required this.isAuto}) : super(key: key);
final File? file;
final Uint8List? audioBytes;
final String resourceURl;
final bool isAuto;
@ -21,7 +23,7 @@ class AudioPlayerFloatingContainer extends StatefulWidget {
class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContainer> {
AudioPlayer player = AudioPlayer();
late Uint8List audiobytes;
Uint8List? audiobytes = null;
bool isplaying = false;
bool audioplayed = false;
int currentpos = 0;
@ -37,6 +39,10 @@ class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContai
audiobytes = widget.audioBytes!;
}
if(widget.file != null) {
audiobytes = await fileToUint8List(widget.file!);
}
player.durationStream.listen((Duration? d) { //get the duration of audio
if(d != null) {
maxduration = d.inSeconds;
@ -101,10 +107,17 @@ class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContai
});
});*/
if(audiobytes != null) {
print("GOT AUDIOBYYYTES - LOCALLY SOSO");
await player.setAudioSource(LoadedSource(audiobytes!));
} else {
print("GET SOUND BY URL");
await player.dynamicSet(url: widget.resourceURl);
}
if(widget.isAuto) {
//player.play(BytesSource(audiobytes));
//await player.setAudioSource(LoadedSource(audiobytes));
//
player.play();
setState(() {
isplaying = true;
@ -122,6 +135,11 @@ class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContai
super.dispose();
}
Future<Uint8List> fileToUint8List(File file) async {
List<int> bytes = await file.readAsBytes();
return Uint8List.fromList(bytes);
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);

View File

@ -0,0 +1,122 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:manager_api/api.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:tablet_app/Components/audio_player.dart';
import 'package:tablet_app/Components/video_viewer.dart';
import 'package:tablet_app/Components/video_viewer_youtube.dart';
import 'package:tablet_app/Models/tabletContext.dart';
import 'package:tablet_app/app_context.dart';
class CachedCustomResource extends StatelessWidget {
final ResourceDTO resourceDTO;
final bool isAuto;
final bool webView;
final BoxFit fit;
CachedCustomResource({
required this.resourceDTO,
required this.isAuto,
required this.webView,
this.fit = BoxFit.cover,
});
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
TabletAppContext tabletAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
Color primaryColor = new Color(int.parse(tabletAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16));
if(resourceDTO.type == ResourceType.ImageUrl || resourceDTO.type == ResourceType.VideoUrl)
{
// Image Url or Video Url don't care, just get resource
if(resourceDTO.type == ResourceType.ImageUrl) {
return CachedNetworkImage(
imageUrl: resourceDTO.url!,
fit: BoxFit.fill,
progressIndicatorBuilder: (context, url, downloadProgress) =>
CircularProgressIndicator(value: downloadProgress.progress, color: primaryColor),
errorWidget: (context, url, error) => Icon(Icons.error),
);
} else {
if(resourceDTO.url == null) {
return Center(child: Text("Error loading video"));
} else {
return VideoViewerYoutube(videoUrl: resourceDTO.url!, isAuto: isAuto, webView: webView);
}
}
} else {
// Check if exist on local storage, if no, just show it via url
print("Check local storage in cached custom resource");
return FutureBuilder<File?>(
future: _checkIfLocalResourceExists(tabletAppContext),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// Loader ou indicateur de chargement pendant la vérification
return CircularProgressIndicator();
} else if (snapshot.hasError || snapshot.data == null) {
// Si la ressource locale n'existe pas ou s'il y a une erreur
switch(resourceDTO.type) {
case ResourceType.Image :
return CachedNetworkImage(
imageUrl: resourceDTO.url!,
fit: fit,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
);
case ResourceType.Video :
return VideoViewer(file: null, videoUrl: resourceDTO.url!);
case ResourceType.Audio :
return AudioPlayerFloatingContainer(file: null, audioBytes: null, resourceURl: resourceDTO.url!, isAuto: isAuto);
default:
return Text("Not supported type");
}
} else {
switch(resourceDTO.type) {
case ResourceType.Image :
return Image.file(
snapshot.data!,
fit: fit,
);
case ResourceType.Video :
return VideoViewer(file: snapshot.data!, videoUrl: resourceDTO.url!);
case ResourceType.Audio :
return AudioPlayerFloatingContainer(file: snapshot.data!, audioBytes: null, resourceURl: resourceDTO.url!, isAuto: isAuto);
default:
return Text("Not supported type");
}
// Utilisation de l'image locale
}
},
);
}
}
Future<File?> _checkIfLocalResourceExists(TabletAppContext tabletAppContext) async {
Directory? appDocumentsDirectory = await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
Directory configurationDirectory = Directory('$localPath/${tabletAppContext.configuration!.id}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
if(fileList.any((fileL) => fileL.uri.pathSegments.last.contains(resourceDTO.id!))) {
File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(resourceDTO.id!)).path);
return file;
}
return null;
}
Future<String> get localPath async {
Directory? appDocumentsDirectory = await getDownloadsDirectory();
return appDocumentsDirectory!.path;
}
}

View File

@ -11,10 +11,14 @@ import 'package:tablet_app/Components/video_viewer_youtube.dart';
import 'package:tablet_app/Models/tabletContext.dart';
import 'package:tablet_app/app_context.dart';
import 'cached_custom_resource.dart';
showElementForResource(ResourceDTO resourceDTO, AppContext appContext, bool isAuto, bool webView) {
TabletAppContext tabletAppContext = appContext.getContext();
Color primaryColor = new Color(int.parse(tabletAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16));
return CachedCustomResource(resourceDTO: resourceDTO, isAuto: isAuto, webView: webView);
switch(resourceDTO.type) {
case ResourceType.Image:
case ResourceType.ImageUrl:
@ -45,7 +49,7 @@ showElementForResource(ResourceDTO resourceDTO, AppContext appContext, bool isAu
},
);*/
case ResourceType.Audio:
return AudioPlayerFloatingContainer(audioBytes: null, resourceURl: resourceDTO.url!, isAuto: isAuto);
return AudioPlayerFloatingContainer(file: null, audioBytes: null, resourceURl: resourceDTO.url!, isAuto: isAuto);
/*return FutureBuilder(
future: getAudio(resourceDTO.url, appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
@ -76,7 +80,7 @@ showElementForResource(ResourceDTO resourceDTO, AppContext appContext, bool isAu
if(resourceDTO.url == null) {
return Center(child: Text("Error loading video"));
} else {
return VideoViewer(videoUrl: resourceDTO.url!);
return VideoViewer(file: null, videoUrl: resourceDTO.url!);
}
case ResourceType.VideoUrl:
if(resourceDTO.url == null) {

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cached_video_player/cached_video_player.dart';
import 'package:flutter/material.dart';
import 'package:tablet_app/Components/loading_common.dart';
@ -6,7 +8,8 @@ import '../../constants.dart';
class VideoViewer extends StatefulWidget {
final String videoUrl;
VideoViewer({required this.videoUrl});
final File? file;
VideoViewer({required this.videoUrl, required this.file});
@override
_VideoViewer createState() => _VideoViewer();
@ -18,12 +21,20 @@ class _VideoViewer extends State<VideoViewer> {
@override
void initState() {
super.initState();
if(widget.file != null) {
_controller = CachedVideoPlayerController.file(widget.file!) // Uri.parse()
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
} else {
_controller = CachedVideoPlayerController.network(widget.videoUrl) // Uri.parse()
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
}
@override
void dispose() {

View File

@ -17,6 +17,7 @@ class TabletAppContext with ChangeNotifier{
String? deviceId;
String? instanceId;
Size? puzzleSize;
String? localPath;
TabletAppContext({this.id, this.deviceId, this.host, this.configuration, this.language, this.instanceId, this.clientAPI});

View File

@ -26,6 +26,7 @@ import 'package:tablet_app/Screens/Puzzle/puzzle_view.dart';
import 'package:tablet_app/Screens/Slider/slider_view.dart';
import 'package:tablet_app/Screens/Video/video_view.dart';
import 'package:tablet_app/Screens/Web/web_view.dart';
import 'package:tablet_app/Services/downloadService.dart';
import 'package:tablet_app/app_context.dart';
import 'package:tablet_app/constants.dart';
import 'package:intl/intl.dart';
@ -418,7 +419,32 @@ class _MainViewWidget extends State<MainViewWidget> {
),
),
]),
)
),
floatingActionButton: InkWell(
onTap: () async {
var result = await showDialog(
builder: (BuildContext dialogContext) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))
),
content: Container(
width: size.width *0.5,
height: size.height *0.5,
child: DownloadConfigurationWidget(),
),
actions: <Widget>[],
), context: context
);
print("RESULLLLT");
print(result);
},
child: Container(
width: 150,
height: 150,
color: Colors.red,
),
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/foundation.dart';
@ -6,9 +7,11 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api/api.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:tablet_app/Components/show_element_for_resource.dart';
import 'package:tablet_app/Components/video_viewer.dart';
import 'package:tablet_app/Models/tabletContext.dart';
import 'package:tablet_app/app_context.dart';
import 'package:tablet_app/constants.dart';
import 'package:photo_view/photo_view.dart';
@ -251,6 +254,29 @@ class _SliderView extends State<SliderView> {
switch(i.resourceType) {
case ResourceType.Image:
TabletAppContext tabletAppContext = appContext.getContext();
Directory configurationDirectory = Directory('${tabletAppContext.localPath}/${tabletAppContext.configuration!.id}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
var imageProvider = null;
if(fileList.any((fileL) => fileL.uri.pathSegments.last.contains(i.resourceId!))) {
File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(i.resourceId!)).path);
imageProvider = FileImage(file);
} else {
imageProvider = new CachedNetworkImageProvider(i.resourceUrl!,);
}
widgetToInclude = PhotoView(
imageProvider: imageProvider,
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.contained * 3.0,
backgroundDecoration: BoxDecoration(
color: kBackgroundSecondGrey,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(15.0),
),
);
break;
case ResourceType.ImageUrl:
widgetToInclude = PhotoView(
imageProvider: CachedNetworkImageProvider(i.resourceUrl!),/*new NetworkImage(

View File

@ -0,0 +1,219 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:provider/provider.dart';
import 'package:tablet_app/Models/tabletContext.dart';
import 'package:tablet_app/app_context.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
class DownloadConfigurationWidget extends StatefulWidget {
DownloadConfigurationWidget();
@override
State<DownloadConfigurationWidget> createState() => _DownloadConfigurationWidgetState();
}
class _DownloadConfigurationWidgetState extends State<DownloadConfigurationWidget> {
int? nbrResources;
ValueNotifier<int> currentResourceIndex = ValueNotifier<int>(0);
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
Future<bool> download(BuildContext buildContext, TabletAppContext tabletAppContext) async {
bool isAllLanguages = true;
ExportConfigurationDTO? exportConfigurationDTO;
try{
print(tabletAppContext.configuration!.id!);
print("essai");
// Retrieve all url from resource to download (get all resource from configuration en somme)
exportConfigurationDTO = await tabletAppContext.clientAPI!.configurationApi!.configurationExport(tabletAppContext.configuration!.id!);
} catch(e) {
print("Erreur lors du téléchargement de la configuration et de ses ressources !");
print(e);
return false;
}
print("COUCUO heeere");
print(exportConfigurationDTO);
print(exportConfigurationDTO.resources!.length);
exportConfigurationDTO.resources!.forEach((element) {
print(element.id);
print(element.label);
});
if(exportConfigurationDTO != null && exportConfigurationDTO.resources != null && exportConfigurationDTO.resources!.isNotEmpty) {
nbrResources = exportConfigurationDTO.resources!.where((resource) => resource.type != ResourceType.ImageUrl && resource.type != ResourceType.VideoUrl && resource.url != null).length;
print("i'm here");
print(nbrResources);
Directory? appDocumentsDirectory = await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
print(localPath);
Map<Permission, PermissionStatus> statuses = await [
Permission.storage,
].request();
print(statuses[Permission.storage]);
print(statuses);
if(statuses[Permission.storage] == PermissionStatus.granted) {
try{
Directory directory = Directory('$localPath');
List<FileSystemEntity> allConfigurations = directory.listSync();
for (var file in allConfigurations) {
print('file.uri.pathSegments');
print(file.uri.pathSegments);
print('file.uri.pathSegments.last');
print(file.uri.pathSegments.last);
}
Directory configurationDirectory = Directory('$localPath/${tabletAppContext.configuration!.id}');
if(!allConfigurations.any((configurationDirectory) => configurationDirectory.uri.pathSegments.any((element) => element == tabletAppContext.configuration!.id))) {
// create directory
configurationDirectory.createSync(recursive: true);
print('Répertoire créé avec succès.');
} else {
print('EXISTE D2J0 NIGAUD.');
}
List<FileSystemEntity> fileList = configurationDirectory.listSync();
print("HERE LIST in directory");
for (var file in fileList) {
print(file.uri.pathSegments.last);
}
// foreach ou on va tout télécharger - avec un joli etape 0 / length - on peut rendre tout lent on s'en fou ça ne ce fait qu'une fois
exportConfigurationDTO.resources!.forEach((resource) async {
if(fileList.any((fileL) => fileL.uri.pathSegments.last.contains(resource.id!))) {
print("Already exist TRIPLE NIGAUD!");
currentResourceIndex.value++;
} else {
if(resource.type != ResourceType.ImageUrl && resource.type != ResourceType.VideoUrl && resource.url != null) {
bool success = await downloadResource(tabletAppContext, resource, localPath);
if (success) {
currentResourceIndex.value++;
}
}
}
});
} catch(e) {
print("ERRORRRR");
print(e);
return false;
}
} else {
return false;
}
}
// Puis après faudra changer tous les appels à une resourceUrl avec un get local de la resource
return true;
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
TabletAppContext tabletAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
return Column(
children: [
FutureBuilder(future: download(context, tabletAppContext), builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// Loader ou indicateur de chargement pendant la vérification
return Center(child: CircularProgressIndicator());
} else {
return Text("Mise à jour finie");
}
}),
ValueListenableBuilder<int>(
valueListenable: currentResourceIndex,
builder: (context, value, _) {
return Center(
child: Text(
value.toString()+'/'+nbrResources.toString(),
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
);
}
),
],
);
}
}
Future<bool> downloadResource(TabletAppContext tabletAppContext, ResourceDTO resourceDTO, String localPath) async {
try {
// Téléchargement de la ressource depuis l'URL
http.Response response = await http.get(Uri.parse(resourceDTO.url!));
if (response.statusCode == 200) {
// Vérification de l'en-tête Content-Type
String contentType = response.headers["content-type"] ?? "";
// Déduction de l'extension en fonction du Content-Type
String extension = _getExtensionFromContentType(contentType);
print("LOCAL PATTH");
print(localPath);
File file = File('$localPath/${tabletAppContext.configuration!.id}/${resourceDTO.id}.$extension');
// Écriture du contenu téléchargé dans le fichier local
await file.writeAsBytes(response.bodyBytes);
return true;
} else {
print("Échec du téléchargement de la ressource - ${response.statusCode}");
return false;
}
} catch (e) {
print("Erreur lors du téléchargement de la ressource !");
print(e);
return false;
}
}
String _getExtensionFromContentType(String contentType) {
Map<String, String> contentTypeToExtension = {
"image/jpeg": "jpg",
"image/jpg": "jpg",
"image/png": "png",
"image/gif": "gif",
"audio/mp3": "mp3",
"video/mp4": "mp4",
"video/webm": "webm",
"video/avi": "avi",
"video/quicktime": "mov",
"application/pdf": "pdf",
"application/json": "json"
};
return contentTypeToExtension[contentType] ?? "unknown";
}

View File

@ -4,6 +4,7 @@ import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:tablet_app/client.dart';
import 'Helpers/DatabaseHelper.dart';
@ -24,6 +25,9 @@ void main() async {
localContext = await DatabaseHelper.instance.getData();
}
Directory? appDocumentsDirectory = await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
if(localContext != null && localContext.host != null) {
print("we've got an local db !");
print(localContext);
@ -38,6 +42,8 @@ void main() async {
DeviceDetailDTO? device = await localContext.clientAPI!.deviceApi!.deviceGetDetail(localContext.deviceId!);
localContext.configuration!.id = device!.configurationId;
localContext.instanceId = device.instanceId;
localContext.localPath = localPath;
if (device.configurationId == null) {
print("device.configurationId == null");
localContext.configuration = null;
@ -46,6 +52,8 @@ void main() async {
} else {
print("NO LOCAL DB !");
localContext = TabletAppContext(host: "https://api.myinfomate.be");
localContext.localPath = localPath;
}
if(kIsWeb) {

View File

@ -1,6 +1,6 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/thomasfransolet/Documents/flutter
FLUTTER_APPLICATION_PATH=/Users/thomasfransolet/Documents/Proj/MyMuseum/tablet_app
FLUTTER_ROOT=C:\PROJ\flutter
FLUTTER_APPLICATION_PATH=C:\Users\ThomasFransolet\Documents\Documents\Perso\MuseeDeLaFraise\tablet-app
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=2.0.0

View File

@ -1,7 +1,7 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/thomasfransolet/Documents/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/thomasfransolet/Documents/Proj/MyMuseum/tablet_app"
export "FLUTTER_ROOT=C:\PROJ\flutter"
export "FLUTTER_APPLICATION_PATH=C:\Users\ThomasFransolet\Documents\Documents\Perso\MuseeDeLaFraise\tablet-app"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=2.0.0"

View File

@ -152,8 +152,8 @@ class ConfigurationApi {
/// * [String] id (required):
///
/// * [String] language:
Future<MultipartFile?> configurationExport(String id, { String? language, }) async {
final response = await configurationExportWithHttpInfo(id, language: language, );
Future<ExportConfigurationDTO> configurationExport(String id,) async {
final response = await configurationExportWithHttpInfo(id,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@ -161,10 +161,10 @@ class ConfigurationApi {
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ExportConfigurationDTO',) as ExportConfigurationDTO;
}
return null;
return Future<ExportConfigurationDTO>.value(null);
}
/// Performs an HTTP 'GET /api/Configuration' operation and returns the [Response].

View File

@ -36,7 +36,7 @@ dependencies:
flare_flutter: ^3.0.2
provider: ^6.0.5
http: ^1.1.0
http: ^1.2.0
auto_size_text: ^3.0.0
fluttertoast:
device_info: ^2.0.2 # DISCONTINUED
@ -55,6 +55,8 @@ dependencies:
cached_video_player: ^2.0.4
cached_network_image: ^3.3.1
just_audio_cache: ^0.1.2
path_provider: ^2.1.2
permission_handler: ^11.2.0
openapi_generator_cli: ^4.13.1
openapi_generator: ^4.13.1

View File

@ -8,6 +8,7 @@
#include <firebase_core/firebase_core_plugin_c_api.h>
#include <firebase_storage/firebase_storage_plugin_c_api.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FirebaseStoragePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
firebase_core
firebase_storage
permission_handler_windows
url_launcher_windows
)