Add stats and ia assistant (to be tested!)

This commit is contained in:
Thomas Fransolet 2026-03-13 15:09:23 +01:00
parent 1fddd6363f
commit 9a46b443b6
21 changed files with 665 additions and 6 deletions

View File

@ -42,8 +42,8 @@ apply plugin: 'com.google.gms.google-services'*/
android {
namespace "be.unov.myinfomate.tablet"
compileSdkVersion 34
compileSdkVersion 36
ndkVersion "27.0.12077973"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -77,6 +77,10 @@ android {
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
ndk {
abiFilters "arm64-v8a"
}
}
signingConfigs {

View File

@ -3,3 +3,6 @@ android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
SDK_REGISTRY_TOKEN=sk.eyJ1IjoidGZyYW5zb2xldCIsImEiOiJjbHRpcTF1d28wYmxmMmxwNjRleXpodGY0In0.qr-jo1vAb08bL3WRDEB4pw
android.bundle.enableUncompressedNativeLibs=false
android.experimental.enable16kApk=true
android.useNewNativePlugin=true

View File

@ -0,0 +1,325 @@
import 'package:flutter/material.dart';
import 'package:tablet_app/Models/AssistantResponse.dart';
import 'package:tablet_app/Models/tabletContext.dart';
import 'package:tablet_app/Services/assistantService.dart';
import 'package:tablet_app/constants.dart';
class AssistantChatView extends StatefulWidget {
final TabletAppContext tabletAppContext;
final String? configurationId;
final void Function(String sectionId, String sectionTitle)? onNavigateToSection;
const AssistantChatView({
Key? key,
required this.tabletAppContext,
this.configurationId,
this.onNavigateToSection,
}) : super(key: key);
@override
State<AssistantChatView> createState() => _AssistantChatViewState();
}
class _AssistantChatViewState extends State<AssistantChatView> {
late AssistantService _assistantService;
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
final List<Widget> _bubbles = [];
bool _isLoading = false;
@override
void initState() {
super.initState();
_assistantService = AssistantService(tabletAppContext: widget.tabletAppContext);
}
Future<void> _send() async {
final text = _controller.text.trim();
if (text.isEmpty || _isLoading) return;
_controller.clear();
setState(() {
_bubbles.add(_ChatBubble(text: text, isUser: true));
_isLoading = true;
});
_scrollToBottom();
try {
final response = await _assistantService.chat(
message: text,
configurationId: widget.configurationId,
);
setState(() {
_bubbles.add(_AssistantMessage(
response: response,
onNavigate: widget.onNavigateToSection,
));
});
} catch (_) {
setState(() {
_bubbles.add(_ChatBubble(text: "Une erreur est survenue, réessayez.", isUser: false));
});
} finally {
setState(() => _isLoading = false);
_scrollToBottom();
}
}
void _scrollToBottom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
});
}
@override
Widget build(BuildContext context) {
return Container(
width: 480,
height: double.infinity,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.horizontal(left: Radius.circular(20)),
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 12, offset: Offset(-4, 0)),
],
),
child: Column(
children: [
// Header
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
decoration: BoxDecoration(
color: kMainGrey,
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20)),
),
child: Row(
children: [
const Icon(Icons.chat_bubble_outline, color: Colors.white, size: 22),
const SizedBox(width: 10),
const Expanded(
child: Text(
"Assistant",
style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w600),
),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: () => Navigator.of(context).pop(),
),
],
),
),
// Messages
Expanded(
child: _bubbles.isEmpty
? Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Text(
"Bonjour ! Posez-moi vos questions sur cette visite.",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey[500], fontSize: 18),
),
),
)
: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
itemCount: _bubbles.length,
itemBuilder: (_, i) => _bubbles[i],
),
),
// Loading
if (_isLoading)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Row(
children: [
SizedBox(
width: 22, height: 22,
child: CircularProgressIndicator(strokeWidth: 2, color: kMainGrey),
),
const SizedBox(width: 10),
Text("...", style: TextStyle(color: Colors.grey[400], fontSize: 16)),
],
),
),
// Input
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
textCapitalization: TextCapitalization.sentences,
style: const TextStyle(fontSize: 16),
decoration: InputDecoration(
hintText: "Votre question...",
filled: true,
fillColor: Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
),
onSubmitted: (_) => _send(),
),
),
const SizedBox(width: 10),
CircleAvatar(
radius: 26,
backgroundColor: kMainGrey,
child: IconButton(
icon: const Icon(Icons.send, color: Colors.white, size: 20),
onPressed: _send,
),
),
],
),
),
],
),
);
}
}
class _ChatBubble extends StatelessWidget {
final String text;
final bool isUser;
const _ChatBubble({required this.text, required this.isUser});
@override
Widget build(BuildContext context) {
return Align(
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.65),
decoration: BoxDecoration(
color: isUser ? kMainGrey : Colors.grey[100],
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(18),
topRight: const Radius.circular(18),
bottomLeft: isUser ? const Radius.circular(18) : const Radius.circular(4),
bottomRight: isUser ? const Radius.circular(4) : const Radius.circular(18),
),
),
child: Text(
text,
style: TextStyle(
color: isUser ? Colors.white : kSecondGrey,
fontSize: 15,
),
),
),
);
}
}
class _AssistantMessage extends StatelessWidget {
final AssistantResponse response;
final void Function(String sectionId, String sectionTitle)? onNavigate;
const _AssistantMessage({required this.response, this.onNavigate});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text bubble
_ChatBubble(text: response.reply, isUser: false),
// Cards
if (response.cards != null && response.cards!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 6, left: 4, right: 32),
child: Column(
children: response.cards!
.map((card) => _AiCardWidget(card: card))
.toList(),
),
),
// Navigation button
if (response.navigation != null && onNavigate != null)
Padding(
padding: const EdgeInsets.only(top: 8, left: 4),
child: ElevatedButton.icon(
onPressed: () => onNavigate!(
response.navigation!.sectionId,
response.navigation!.sectionTitle,
),
icon: const Icon(Icons.arrow_forward, size: 18),
label: Text(response.navigation!.sectionTitle),
style: ElevatedButton.styleFrom(
backgroundColor: kMainGrey,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
),
),
),
],
),
);
}
}
class _AiCardWidget extends StatelessWidget {
final AiCard card;
const _AiCardWidget({required this.card});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[200]!),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2)),
],
),
child: Row(
children: [
if (card.icon != null) ...[
Text(card.icon!, style: const TextStyle(fontSize: 20)),
const SizedBox(width: 10),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
card.title,
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14, color: kSecondGrey),
),
if (card.subtitle.isNotEmpty)
Text(
card.subtitle,
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
),
],
),
),
],
),
);
}
}

View File

@ -0,0 +1,56 @@
class AiCard {
final String title;
final String subtitle;
final String? icon;
const AiCard({required this.title, required this.subtitle, this.icon});
factory AiCard.fromJson(Map<String, dynamic> json) => AiCard(
title: json['title'] as String? ?? '',
subtitle: json['subtitle'] as String? ?? '',
icon: json['icon'] as String?,
);
}
class AssistantNavigationAction {
final String sectionId;
final String sectionTitle;
final String sectionType;
const AssistantNavigationAction({
required this.sectionId,
required this.sectionTitle,
required this.sectionType,
});
factory AssistantNavigationAction.fromJson(Map<String, dynamic> json) =>
AssistantNavigationAction(
sectionId: json['sectionId'] as String? ?? '',
sectionTitle: json['sectionTitle'] as String? ?? '',
sectionType: json['sectionType'] as String? ?? '',
);
}
class AssistantResponse {
final String reply;
final List<AiCard>? cards;
final AssistantNavigationAction? navigation;
const AssistantResponse({
required this.reply,
this.cards,
this.navigation,
});
factory AssistantResponse.fromJson(Map<String, dynamic> json) =>
AssistantResponse(
reply: json['reply'] as String? ?? '',
cards: (json['cards'] as List<dynamic>?)
?.map((e) => AiCard.fromJson(e as Map<String, dynamic>))
.toList(),
navigation: json['navigation'] != null
? AssistantNavigationAction.fromJson(
json['navigation'] as Map<String, dynamic>)
: null,
);
}

View File

@ -3,6 +3,7 @@ import 'package:manager_api_new/api.dart';
//import 'package:mqtt_client/mqtt_browser_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
import 'package:tablet_app/client.dart';
import 'package:tablet_app/Services/statisticsService.dart';
import 'dart:convert';
@ -18,6 +19,8 @@ class TabletAppContext with ChangeNotifier{
String? instanceId;
Size? puzzleSize;
String? localPath;
ApplicationInstanceDTO? applicationInstanceDTO;
StatisticsService? statisticsService;
TabletAppContext({this.id, this.deviceId, this.host, this.configuration, this.language, this.instanceId, this.clientAPI});

View File

@ -150,6 +150,11 @@ class _AgendaView extends State<AgendaView> {
return GestureDetector(
onTap: () {
print("${eventAgenda.name}");
(Provider.of<AppContext>(context, listen: false).getContext() as TabletAppContext)
.statisticsService?.track(
VisitEventType.agendaEventTap,
metadata: {'eventId': eventAgenda.name, 'eventTitle': eventAgenda.name},
);
showDialog(
context: context,
builder: (BuildContext context) {

View File

@ -15,6 +15,7 @@ import 'package:manager_api_new/api.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:tablet_app/Services/statisticsService.dart';
import 'package:tablet_app/Components/loading_common.dart';
import 'package:tablet_app/Helpers/DatabaseHelper.dart';
import 'package:tablet_app/Helpers/ImageCustomProvider.dart';
@ -42,6 +43,7 @@ import 'package:http/http.dart' as http;
import 'package:image/image.dart' as IMG;
import 'dart:ui' as ui;
import 'package:tablet_app/Components/assistant_chat_view.dart';
import '../Quizz/quizz_view.dart';
import 'language_selection.dart';
@ -194,7 +196,49 @@ class _MainViewWidget extends State<MainViewWidget> {
}
),
)
)
),
if (tabletAppContext.applicationInstanceDTO?.isAssistant == true)
Positioned(
bottom: 24,
right: 24,
child: FloatingActionButton.extended(
backgroundColor: kMainGrey,
icon: const Icon(Icons.chat_bubble_outline, color: Colors.white),
label: const Text("Assistant", style: TextStyle(color: Colors.white, fontSize: 16)),
onPressed: () {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'Assistant',
barrierColor: Colors.black45,
transitionDuration: const Duration(milliseconds: 250),
pageBuilder: (dialogContext, __, ___) => Align(
alignment: Alignment.centerRight,
child: AssistantChatView(
tabletAppContext: tabletAppContext,
configurationId: tabletAppContext.configuration?.id,
onNavigateToSection: (sectionId, sectionTitle) {
Navigator.of(dialogContext).pop();
final index = sectionsLocal?.indexWhere((s) => s.id == sectionId) ?? -1;
if (index >= 0) {
Navigator.push(context, MaterialPageRoute(
builder: (_) => SectionPageDetail(
configurationDTO: configurationDTO,
sectionDTO: sectionsLocal![index],
textColor: textColor,
isImageBackground: isImageBackground,
elementToShow: getContent(tabletAppContext, sectionsLocal![index], isImageBackground, rawSectionsData[index]),
isFromMenu: false,
),
));
}
},
),
),
);
},
),
)
/*if(configurationDTO.weatherCity != null && configurationDTO.weatherCity!.length > 2 && configurationDTO.weatherResult != null)
Positioned(
bottom: 0,
@ -277,6 +321,35 @@ class _MainViewWidget extends State<MainViewWidget> {
await getCurrentConfiguration(appContext);
// Load applicationInstanceDTO to check if assistant/statistics are enabled
try {
if (tabletAppContext.instanceId != null) {
final instanceDTO = await tabletAppContext.clientAPI!.instanceApi!.instanceGetDetail(tabletAppContext.instanceId!);
if (instanceDTO != null && instanceDTO.applicationInstanceDTOs != null) {
final tabletInstance = instanceDTO.applicationInstanceDTOs!
.where((a) => a.appType == AppType.Tablet)
.firstOrNull;
if (tabletInstance != null) {
if (instanceDTO.isAssistant == true) {
tabletAppContext.applicationInstanceDTO = tabletInstance;
}
if (tabletInstance.isStatistic == true) {
tabletAppContext.statisticsService = StatisticsService(
clientAPI: tabletAppContext.clientAPI!,
instanceId: tabletAppContext.instanceId,
configurationId: tabletAppContext.configuration?.id,
appType: 'Tablet',
language: tabletAppContext.language,
);
}
}
}
appContext.setContext(tabletAppContext);
}
} catch (e) {
print('Failed to load applicationInstanceDTO: $e');
}
print(sectionsLocal);
if(isInit) {

View File

@ -18,6 +18,7 @@ import 'package:tablet_app/Screens/Map/geo_point_filter.dart';
import 'package:tablet_app/Screens/Map/map_context.dart';
import 'package:html/parser.dart' show parse;
import 'package:tablet_app/Screens/Menu/menu_view.dart';
import 'package:tablet_app/Services/statisticsService.dart';
import 'package:tablet_app/app_context.dart';
import 'package:tablet_app/constants.dart';
@ -46,6 +47,35 @@ class SectionPageDetail extends StatefulWidget {
class _SectionPageDetailState extends State<SectionPageDetail> {
bool init = false;
DateTime? _sectionOpenTime;
StatisticsService? _statisticsService;
@override
void initState() {
super.initState();
_sectionOpenTime = DateTime.now();
WidgetsBinding.instance.addPostFrameCallback((_) {
final tabletAppContext = Provider.of<AppContext>(context, listen: false).getContext() as TabletAppContext;
_statisticsService = tabletAppContext.statisticsService;
_statisticsService?.track(
VisitEventType.sectionView,
sectionId: widget.sectionDTO.id,
);
});
}
@override
void dispose() {
final duration = _sectionOpenTime != null
? DateTime.now().difference(_sectionOpenTime!).inSeconds
: null;
_statisticsService?.track(
VisitEventType.sectionLeave,
sectionId: widget.sectionDTO.id,
durationSeconds: duration,
);
super.dispose();
}
@override
Widget build(BuildContext context) {

View File

@ -69,6 +69,14 @@ class _GoogleMapViewState extends State<GoogleMapView> {
mapContext.setSelectedPoint(point);
//mapContext.setSelectedPointForNavigate(point);
//});
(Provider.of<AppContext>(context, listen: false).getContext() as TabletAppContext)
.statisticsService?.track(
VisitEventType.mapPoiTap,
metadata: {
'geoPointId': point.id,
'geoPointTitle': parse(textSansHTML.body!.text).documentElement!.text,
},
);
},
infoWindow: InfoWindow.noText));
}

View File

@ -69,6 +69,11 @@ class _MenuView extends State<MenuView> {
SectionDTO section = subSections[index];
var rawSectionData = rawSubSectionsData[index];
tabletAppContext.statisticsService?.track(
VisitEventType.menuItemTap,
metadata: {'targetSectionId': section.id, 'menuItemTitle': section.title?.where((t) => t.language == tabletAppContext.language).firstOrNull?.value},
);
setState(() {
//selectedSection = section;
//selectedSection = menuDTO.sections![index];

View File

@ -28,6 +28,7 @@ class _PuzzleView extends State<PuzzleView> {
int allInPlaceCount = 0;
bool isFinished = false;
DateTime? _puzzleStartTime;
GlobalKey _widgetKey = GlobalKey();
Size? realWidgetSize;
List<Widget> pieces = [];
@ -40,6 +41,7 @@ class _PuzzleView extends State<PuzzleView> {
puzzleDTO = widget.section;
puzzleDTO.rows = puzzleDTO.rows != null ? puzzleDTO.rows : 3;
puzzleDTO.cols = puzzleDTO.cols != null ? puzzleDTO.cols : 3;
_puzzleStartTime = DateTime.now();
WidgetsBinding.instance.addPostFrameCallback((_) {
Size size = MediaQuery.of(context).size;
@ -191,6 +193,11 @@ class _PuzzleView extends State<PuzzleView> {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context, listen: false);
TabletAppContext tabletAppContext = appContext.getContext();
final duration = _puzzleStartTime != null ? DateTime.now().difference(_puzzleStartTime!).inSeconds : 0;
tabletAppContext.statisticsService?.track(
VisitEventType.gameComplete,
metadata: {'gameType': 'Puzzle', 'durationSeconds': duration},
);
TranslationAndResourceDTO? messageFin = puzzleDTO.messageFin != null && puzzleDTO.messageFin!.length > 0 ? puzzleDTO.messageFin!.where((message) => message.language!.toUpperCase() == tabletAppContext.language!.toUpperCase()).firstOrNull : null;
if(messageFin != null) {

View File

@ -407,6 +407,12 @@ class _QuizzView extends State<QuizzView> {
{
showResult = true;
_controllerCenter!.play(); // TODO Maybe show only confetti on super score ..
final goodResponses = _questionsSubDTO.where((q) => q.chosen == q.responsesSubDTO!.indexWhere((r) => r.isGood!)).length;
(Provider.of<AppContext>(context, listen: false).getContext() as TabletAppContext)
.statisticsService?.track(
VisitEventType.quizComplete,
metadata: {'score': goodResponses, 'totalQuestions': _questionsSubDTO.length},
);
} else {
sliderController!.nextPage(duration: new Duration(milliseconds: 650), curve: Curves.fastOutSlowIn);
}

View File

@ -0,0 +1,43 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:tablet_app/Models/tabletContext.dart';
import 'package:tablet_app/Models/AssistantResponse.dart';
class AssistantService {
final TabletAppContext tabletAppContext;
final List<Map<String, String>> _history = [];
AssistantService({required this.tabletAppContext});
Future<AssistantResponse> chat({required String message, String? configurationId}) async {
final host = tabletAppContext.host ?? '';
final instanceId = tabletAppContext.instanceId ?? '';
final language = tabletAppContext.language ?? 'FR';
_history.add({'role': 'user', 'content': message});
final body = {
'message': message,
'instanceId': instanceId,
'appType': 'Tablet',
'configurationId': configurationId,
'language': language,
'history': _history.take(10).toList(),
};
final response = await http.post(
Uri.parse('$host/api/ai/chat'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body) as Map<String, dynamic>;
final result = AssistantResponse.fromJson(data);
_history.add({'role': 'assistant', 'content': result.reply});
return result;
} else {
throw Exception('Assistant error: ${response.statusCode}');
}
}
}

View File

@ -0,0 +1,52 @@
import 'dart:convert';
import 'dart:math';
import 'package:manager_api_new/api.dart';
import 'package:tablet_app/client.dart';
class StatisticsService {
final Client clientAPI;
final String? instanceId;
final String? configurationId;
final String? appType; // "Mobile" or "Tablet"
final String? language;
final String sessionId;
StatisticsService({
required this.clientAPI,
required this.instanceId,
required this.configurationId,
this.appType = 'Tablet',
this.language,
}) : sessionId = _generateSessionId();
static String _generateSessionId() {
final rand = Random();
final bytes = List<int>.generate(16, (_) => rand.nextInt(256));
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
}
Future<void> track(
String eventType, {
String? sectionId,
int? durationSeconds,
Map<String, dynamic>? metadata,
}) async {
try {
await clientAPI.statsApi!.statsTrackEvent(VisitEventDTO(
instanceId: instanceId ?? '',
configurationId: configurationId,
sectionId: sectionId,
sessionId: sessionId,
eventType: eventType,
appType: appType,
language: language,
durationSeconds: durationSeconds,
metadata: metadata != null ? jsonEncode(metadata) : null,
timestamp: DateTime.now(),
));
} catch (_) {
// fire-and-forget never block the UI on stats errors
}
}
}

View File

@ -26,6 +26,12 @@ class Client {
DeviceApi? _deviceApi;
DeviceApi? get deviceApi => _deviceApi;
ApplicationInstanceApi? _applicationInstanceApi;
ApplicationInstanceApi? get applicationInstanceApi => _applicationInstanceApi;
StatsApi? _statsApi;
StatsApi? get statsApi => _statsApi;
Client(String path) {
_apiClient = ApiClient(
basePath: path); // "http://192.168.31.96"
@ -37,5 +43,7 @@ class Client {
_sectionApi = SectionApi(_apiClient);
_resourceApi = ResourceApi(_apiClient);
_deviceApi = DeviceApi(_apiClient);
_applicationInstanceApi = ApplicationInstanceApi(_apiClient);
_statsApi = StatsApi(_apiClient);
}
}

View File

@ -14,10 +14,11 @@ import just_audio
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
import sqflite
import sqflite_darwin
import url_launcher_macos
import video_player_avfoundation
import wakelock_plus
import webview_flutter_wkwebview
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
@ -33,4 +34,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
}

View File

@ -0,0 +1,12 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=C:\PROJ\flutter
FLUTTER_APPLICATION_PATH=C:\Users\ThomasFransolet\Documents\Documents\Perso\GITEA\tablet-app
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=2.1.5
FLUTTER_BUILD_NUMBER=26
FLUTTER_CLI_BUILD_MODE=debug
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json

View File

@ -0,0 +1,13 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=C:\PROJ\flutter"
export "FLUTTER_APPLICATION_PATH=C:\Users\ThomasFransolet\Documents\Documents\Perso\GITEA\tablet-app"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=2.1.5"
export "FLUTTER_BUILD_NUMBER=26"
export "FLUTTER_CLI_BUILD_MODE=debug"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"

View File

@ -80,7 +80,7 @@ dependencies:
#win32: ^4.1.2
#archive: ^3.6.1
manager_api_new:
path: manager_api_new
path: ../manager-app/manager_api_new
dev_dependencies:
flutter_test:

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 <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FirebaseStoragePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi"));
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(

View File

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