Add stats and ia assistant (to be tested!)
This commit is contained in:
parent
1fddd6363f
commit
9a46b443b6
@ -42,8 +42,8 @@ apply plugin: 'com.google.gms.google-services'*/
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "be.unov.myinfomate.tablet"
|
namespace "be.unov.myinfomate.tablet"
|
||||||
|
compileSdkVersion 36
|
||||||
compileSdkVersion 34
|
ndkVersion "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
@ -77,6 +77,10 @@ android {
|
|||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters "arm64-v8a"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@ -3,3 +3,6 @@ android.useAndroidX=true
|
|||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.enableR8=true
|
android.enableR8=true
|
||||||
SDK_REGISTRY_TOKEN=sk.eyJ1IjoidGZyYW5zb2xldCIsImEiOiJjbHRpcTF1d28wYmxmMmxwNjRleXpodGY0In0.qr-jo1vAb08bL3WRDEB4pw
|
SDK_REGISTRY_TOKEN=sk.eyJ1IjoidGZyYW5zb2xldCIsImEiOiJjbHRpcTF1d28wYmxmMmxwNjRleXpodGY0In0.qr-jo1vAb08bL3WRDEB4pw
|
||||||
|
android.bundle.enableUncompressedNativeLibs=false
|
||||||
|
android.experimental.enable16kApk=true
|
||||||
|
android.useNewNativePlugin=true
|
||||||
325
lib/Components/assistant_chat_view.dart
Normal file
325
lib/Components/assistant_chat_view.dart
Normal 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]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/Models/AssistantResponse.dart
Normal file
56
lib/Models/AssistantResponse.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ import 'package:manager_api_new/api.dart';
|
|||||||
//import 'package:mqtt_client/mqtt_browser_client.dart';
|
//import 'package:mqtt_client/mqtt_browser_client.dart';
|
||||||
import 'package:mqtt_client/mqtt_server_client.dart';
|
import 'package:mqtt_client/mqtt_server_client.dart';
|
||||||
import 'package:tablet_app/client.dart';
|
import 'package:tablet_app/client.dart';
|
||||||
|
import 'package:tablet_app/Services/statisticsService.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
|
||||||
@ -18,6 +19,8 @@ class TabletAppContext with ChangeNotifier{
|
|||||||
String? instanceId;
|
String? instanceId;
|
||||||
Size? puzzleSize;
|
Size? puzzleSize;
|
||||||
String? localPath;
|
String? localPath;
|
||||||
|
ApplicationInstanceDTO? applicationInstanceDTO;
|
||||||
|
StatisticsService? statisticsService;
|
||||||
|
|
||||||
TabletAppContext({this.id, this.deviceId, this.host, this.configuration, this.language, this.instanceId, this.clientAPI});
|
TabletAppContext({this.id, this.deviceId, this.host, this.configuration, this.language, this.instanceId, this.clientAPI});
|
||||||
|
|
||||||
|
|||||||
@ -150,6 +150,11 @@ class _AgendaView extends State<AgendaView> {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
print("${eventAgenda.name}");
|
print("${eventAgenda.name}");
|
||||||
|
(Provider.of<AppContext>(context, listen: false).getContext() as TabletAppContext)
|
||||||
|
.statisticsService?.track(
|
||||||
|
VisitEventType.agendaEventTap,
|
||||||
|
metadata: {'eventId': eventAgenda.name, 'eventTitle': eventAgenda.name},
|
||||||
|
);
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import 'package:manager_api_new/api.dart';
|
|||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/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/Components/loading_common.dart';
|
||||||
import 'package:tablet_app/Helpers/DatabaseHelper.dart';
|
import 'package:tablet_app/Helpers/DatabaseHelper.dart';
|
||||||
import 'package:tablet_app/Helpers/ImageCustomProvider.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 'package:image/image.dart' as IMG;
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:tablet_app/Components/assistant_chat_view.dart';
|
||||||
import '../Quizz/quizz_view.dart';
|
import '../Quizz/quizz_view.dart';
|
||||||
import 'language_selection.dart';
|
import 'language_selection.dart';
|
||||||
|
|
||||||
@ -194,6 +196,48 @@ 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)
|
/*if(configurationDTO.weatherCity != null && configurationDTO.weatherCity!.length > 2 && configurationDTO.weatherResult != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
@ -277,6 +321,35 @@ class _MainViewWidget extends State<MainViewWidget> {
|
|||||||
|
|
||||||
await getCurrentConfiguration(appContext);
|
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);
|
print(sectionsLocal);
|
||||||
|
|
||||||
if(isInit) {
|
if(isInit) {
|
||||||
|
|||||||
@ -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:tablet_app/Screens/Map/map_context.dart';
|
||||||
import 'package:html/parser.dart' show parse;
|
import 'package:html/parser.dart' show parse;
|
||||||
import 'package:tablet_app/Screens/Menu/menu_view.dart';
|
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/app_context.dart';
|
||||||
import 'package:tablet_app/constants.dart';
|
import 'package:tablet_app/constants.dart';
|
||||||
|
|
||||||
@ -46,6 +47,35 @@ class SectionPageDetail extends StatefulWidget {
|
|||||||
|
|
||||||
class _SectionPageDetailState extends State<SectionPageDetail> {
|
class _SectionPageDetailState extends State<SectionPageDetail> {
|
||||||
bool init = false;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@ -69,6 +69,14 @@ class _GoogleMapViewState extends State<GoogleMapView> {
|
|||||||
mapContext.setSelectedPoint(point);
|
mapContext.setSelectedPoint(point);
|
||||||
//mapContext.setSelectedPointForNavigate(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));
|
infoWindow: InfoWindow.noText));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,6 +69,11 @@ class _MenuView extends State<MenuView> {
|
|||||||
SectionDTO section = subSections[index];
|
SectionDTO section = subSections[index];
|
||||||
var rawSectionData = rawSubSectionsData[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(() {
|
setState(() {
|
||||||
//selectedSection = section;
|
//selectedSection = section;
|
||||||
//selectedSection = menuDTO.sections![index];
|
//selectedSection = menuDTO.sections![index];
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class _PuzzleView extends State<PuzzleView> {
|
|||||||
|
|
||||||
int allInPlaceCount = 0;
|
int allInPlaceCount = 0;
|
||||||
bool isFinished = false;
|
bool isFinished = false;
|
||||||
|
DateTime? _puzzleStartTime;
|
||||||
GlobalKey _widgetKey = GlobalKey();
|
GlobalKey _widgetKey = GlobalKey();
|
||||||
Size? realWidgetSize;
|
Size? realWidgetSize;
|
||||||
List<Widget> pieces = [];
|
List<Widget> pieces = [];
|
||||||
@ -40,6 +41,7 @@ class _PuzzleView extends State<PuzzleView> {
|
|||||||
puzzleDTO = widget.section;
|
puzzleDTO = widget.section;
|
||||||
puzzleDTO.rows = puzzleDTO.rows != null ? puzzleDTO.rows : 3;
|
puzzleDTO.rows = puzzleDTO.rows != null ? puzzleDTO.rows : 3;
|
||||||
puzzleDTO.cols = puzzleDTO.cols != null ? puzzleDTO.cols : 3;
|
puzzleDTO.cols = puzzleDTO.cols != null ? puzzleDTO.cols : 3;
|
||||||
|
_puzzleStartTime = DateTime.now();
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
Size size = MediaQuery.of(context).size;
|
Size size = MediaQuery.of(context).size;
|
||||||
@ -191,6 +193,11 @@ class _PuzzleView extends State<PuzzleView> {
|
|||||||
Size size = MediaQuery.of(context).size;
|
Size size = MediaQuery.of(context).size;
|
||||||
final appContext = Provider.of<AppContext>(context, listen: false);
|
final appContext = Provider.of<AppContext>(context, listen: false);
|
||||||
TabletAppContext tabletAppContext = appContext.getContext();
|
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;
|
TranslationAndResourceDTO? messageFin = puzzleDTO.messageFin != null && puzzleDTO.messageFin!.length > 0 ? puzzleDTO.messageFin!.where((message) => message.language!.toUpperCase() == tabletAppContext.language!.toUpperCase()).firstOrNull : null;
|
||||||
|
|
||||||
if(messageFin != null) {
|
if(messageFin != null) {
|
||||||
|
|||||||
@ -407,6 +407,12 @@ class _QuizzView extends State<QuizzView> {
|
|||||||
{
|
{
|
||||||
showResult = true;
|
showResult = true;
|
||||||
_controllerCenter!.play(); // TODO Maybe show only confetti on super score ..
|
_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 {
|
} else {
|
||||||
sliderController!.nextPage(duration: new Duration(milliseconds: 650), curve: Curves.fastOutSlowIn);
|
sliderController!.nextPage(duration: new Duration(milliseconds: 650), curve: Curves.fastOutSlowIn);
|
||||||
}
|
}
|
||||||
|
|||||||
43
lib/Services/assistantService.dart
Normal file
43
lib/Services/assistantService.dart
Normal 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}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
lib/Services/statisticsService.dart
Normal file
52
lib/Services/statisticsService.dart
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,6 +26,12 @@ class Client {
|
|||||||
DeviceApi? _deviceApi;
|
DeviceApi? _deviceApi;
|
||||||
DeviceApi? get deviceApi => _deviceApi;
|
DeviceApi? get deviceApi => _deviceApi;
|
||||||
|
|
||||||
|
ApplicationInstanceApi? _applicationInstanceApi;
|
||||||
|
ApplicationInstanceApi? get applicationInstanceApi => _applicationInstanceApi;
|
||||||
|
|
||||||
|
StatsApi? _statsApi;
|
||||||
|
StatsApi? get statsApi => _statsApi;
|
||||||
|
|
||||||
Client(String path) {
|
Client(String path) {
|
||||||
_apiClient = ApiClient(
|
_apiClient = ApiClient(
|
||||||
basePath: path); // "http://192.168.31.96"
|
basePath: path); // "http://192.168.31.96"
|
||||||
@ -37,5 +43,7 @@ class Client {
|
|||||||
_sectionApi = SectionApi(_apiClient);
|
_sectionApi = SectionApi(_apiClient);
|
||||||
_resourceApi = ResourceApi(_apiClient);
|
_resourceApi = ResourceApi(_apiClient);
|
||||||
_deviceApi = DeviceApi(_apiClient);
|
_deviceApi = DeviceApi(_apiClient);
|
||||||
|
_applicationInstanceApi = ApplicationInstanceApi(_apiClient);
|
||||||
|
_statsApi = StatsApi(_apiClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14,10 +14,11 @@ import just_audio
|
|||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite
|
import sqflite_darwin
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_player_avfoundation
|
import video_player_avfoundation
|
||||||
import wakelock_plus
|
import wakelock_plus
|
||||||
|
import webview_flutter_wkwebview
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
@ -33,4 +34,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||||
|
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
12
macos/Flutter/ephemeral/Flutter-Generated.xcconfig
Normal file
12
macos/Flutter/ephemeral/Flutter-Generated.xcconfig
Normal 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
|
||||||
13
macos/Flutter/ephemeral/flutter_export_environment.sh
Normal file
13
macos/Flutter/ephemeral/flutter_export_environment.sh
Normal 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"
|
||||||
@ -80,7 +80,7 @@ dependencies:
|
|||||||
#win32: ^4.1.2
|
#win32: ^4.1.2
|
||||||
#archive: ^3.6.1
|
#archive: ^3.6.1
|
||||||
manager_api_new:
|
manager_api_new:
|
||||||
path: manager_api_new
|
path: ../manager-app/manager_api_new
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
#include <firebase_storage/firebase_storage_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 <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
FirebaseStoragePluginCApiRegisterWithRegistrar(
|
FirebaseStoragePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseStoragePluginCApi"));
|
||||||
|
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
||||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
firebase_core
|
firebase_core
|
||||||
firebase_storage
|
firebase_storage
|
||||||
|
flutter_inappwebview_windows
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user