From 7669f38ed860785cde04118fa504f1ac7ceb3861 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Fri, 17 Apr 2026 14:17:10 +0200 Subject: [PATCH] Refacto login screen + update video config to support multiple type + put ia translate button and other in better way (to be tested) --- .../multi_string_input_html_modal.dart | 8 +- lib/Components/text_field_container.dart | 3 +- ...nslation_input_and_resource_container.dart | 41 +- .../translation_input_container.dart | 13 +- .../SubSection/Video/video_config.dart | 126 +++++- lib/Screens/login_screen.dart | 428 ++++++++---------- 6 files changed, 343 insertions(+), 276 deletions(-) diff --git a/lib/Components/multi_string_input_html_modal.dart b/lib/Components/multi_string_input_html_modal.dart index 24234f0..5a347ce 100644 --- a/lib/Components/multi_string_input_html_modal.dart +++ b/lib/Components/multi_string_input_html_modal.dart @@ -26,9 +26,7 @@ showMultiStringInputHTML (String label, String modalLabel, bool isTitle, List _buildLanguageSection(t)), - const SizedBox(height: 8), + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...widget.newValues.map((t) => _buildLanguageSection(t)), + ], + ), + ), + ), Builder(builder: (ctx) { final instance = ctx.watch().getContext()?.instanceDTO; if (instance?.isAssistant != true) return const SizedBox.shrink(); - return Center( - child: SizedBox( - width: 370, - height: 70, - child: _isTranslating - ? const Center(child: CircularProgressIndicator()) - : RoundedButton( - text: "Traduire via IA", - icon: Icons.auto_awesome, - color: kPrimaryColor, - press: _translateWithAI, - fontSize: 20, - ), + return Padding( + padding: const EdgeInsets.only(top: 8), + child: Center( + child: SizedBox( + width: 370, + height: 70, + child: _isTranslating + ? const Center(child: CircularProgressIndicator()) + : RoundedButton( + text: "Traduire via IA", + icon: Icons.auto_awesome, + color: kPrimaryColor, + press: _translateWithAI, + fontSize: 20, + ), + ), ), ); }), diff --git a/lib/Components/translation_input_container.dart b/lib/Components/translation_input_container.dart index 1ed64a9..5ad38d1 100644 --- a/lib/Components/translation_input_container.dart +++ b/lib/Components/translation_input_container.dart @@ -191,9 +191,18 @@ class _TranslationInputContainerState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ...widget.newValues.map((t) => _buildLanguageSection(t)), - const SizedBox(height: 8), + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...widget.newValues.map((t) => _buildLanguageSection(t)), + ], + ), + ), + ), if (widget.resourceTypes == null) ...[ + const SizedBox(height: 8), Center( child: SizedBox( width: 370, diff --git a/lib/Screens/Configurations/Section/SubSection/Video/video_config.dart b/lib/Screens/Configurations/Section/SubSection/Video/video_config.dart index e7362fd..a4a1315 100644 --- a/lib/Screens/Configurations/Section/SubSection/Video/video_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Video/video_config.dart @@ -1,8 +1,17 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:manager_app/Components/string_input_container.dart'; +import 'package:manager_app/Screens/Resources/select_resource_modal.dart'; +import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; -import 'dart:convert'; + +enum _VideoSourceMode { youtube, vimeo, resource } + +_VideoSourceMode _detectMode(String? url) { + if (url == null || url.isEmpty) return _VideoSourceMode.youtube; + if (url.contains('youtube.com') || url.contains('youtu.be')) return _VideoSourceMode.youtube; + if (url.contains('vimeo.com')) return _VideoSourceMode.vimeo; + return _VideoSourceMode.resource; +} class VideoConfig extends StatefulWidget { final String? color; @@ -23,25 +32,116 @@ class VideoConfig extends StatefulWidget { class _VideoConfigState extends State { late VideoDTO resourceSource; + late _VideoSourceMode _mode; + String? _selectedResourceLabel; @override void initState() { - VideoDTO test = widget.initialValue; - resourceSource = test; + resourceSource = widget.initialValue; + _mode = _detectMode(resourceSource.source_); super.initState(); } + void _onModeChanged(_VideoSourceMode mode) { + setState(() { + _mode = mode; + resourceSource.source_ = null; + _selectedResourceLabel = null; + }); + widget.onChanged(resourceSource); + } + @override Widget build(BuildContext context) { - return StringInputContainer( - label: widget.label!, - initialValue: resourceSource.source_ == null ? '': resourceSource.source_, - onChanged: (String url) { - resourceSource.source_ = url; - widget.onChanged(resourceSource); - }, - isUrl: true, - maxLength: 100, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SegmentedButton<_VideoSourceMode>( + segments: const [ + ButtonSegment(value: _VideoSourceMode.youtube, label: Text('YouTube'), icon: Icon(Icons.smart_display)), + ButtonSegment(value: _VideoSourceMode.vimeo, label: Text('Vimeo'), icon: Icon(Icons.play_circle)), + ButtonSegment(value: _VideoSourceMode.resource, label: Text('Ressource'), icon: Icon(Icons.video_file)), + ], + selected: {_mode}, + onSelectionChanged: (selection) => _onModeChanged(selection.first), + ), + const SizedBox(height: 12), + if (_mode == _VideoSourceMode.youtube) + StringInputContainer( + label: 'URL YouTube :', + initialValue: _mode == _VideoSourceMode.youtube ? resourceSource.source_ : null, + onChanged: (url) { + resourceSource.source_ = url; + widget.onChanged(resourceSource); + }, + isUrl: true, + maxLength: 200, + ), + if (_mode == _VideoSourceMode.vimeo) + StringInputContainer( + label: 'URL Vimeo :', + initialValue: _mode == _VideoSourceMode.vimeo ? resourceSource.source_ : null, + onChanged: (url) { + resourceSource.source_ = url; + widget.onChanged(resourceSource); + }, + isUrl: true, + maxLength: 200, + ), + if (_mode == _VideoSourceMode.resource) + _ResourceVideoPicker( + selectedLabel: _selectedResourceLabel, + selectedUrl: _mode == _VideoSourceMode.resource ? resourceSource.source_ : null, + onChanged: (resource) { + setState(() { + resourceSource.source_ = resource.url; + _selectedResourceLabel = resource.label; + }); + widget.onChanged(resourceSource); + }, + ), + ], + ); + } +} + +class _ResourceVideoPicker extends StatelessWidget { + final String? selectedLabel; + final String? selectedUrl; + final ValueChanged onChanged; + + const _ResourceVideoPicker({ + required this.selectedLabel, + required this.selectedUrl, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + const Text('Vidéo :', style: TextStyle(fontSize: 18)), + const SizedBox(width: 10), + ElevatedButton.icon( + icon: const Icon(Icons.video_file), + label: Text(selectedLabel ?? (selectedUrl != null ? 'Vidéo sélectionnée' : 'Choisir')), + style: ElevatedButton.styleFrom(backgroundColor: kPrimaryColor, foregroundColor: Colors.white), + onPressed: () async { + final result = await showSelectResourceModal( + 'Sélectionner une vidéo', + 1, + [ResourceType.Video, ResourceType.VideoUrl], + context, + true, + true, + true, + ); + if (result != null && result is ResourceDTO) { + onChanged(result); + } + }, + ), + ], ); } } diff --git a/lib/Screens/login_screen.dart b/lib/Screens/login_screen.dart index d40b43d..3e0fc10 100644 --- a/lib/Screens/login_screen.dart +++ b/lib/Screens/login_screen.dart @@ -14,7 +14,6 @@ import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Helpers/FileHelper.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Models/session.dart'; -import 'package:manager_app/Screens/Main/main_screen.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/client.dart'; import 'package:manager_app/constants.dart'; @@ -43,46 +42,30 @@ class _LoginScreenState extends State { String? instanceId; String? pinCode; Storage localStorage = window.localStorage; - + void authenticateTRY(AppContext appContext, bool fromClick) async { - //print("try auth.. "); - - /*this.host = "http://localhost:5000"; - this.email = "test@email.be"; - this.password = "kljqsdkljqsd";*/ - clientAPI = Client(this.host!); - if (this.password != null || this.token != null) { - - // if () {} // Add if token exist and not null + not expired + if (this.password.isNotEmpty || this.token != null) { try { - if(fromClick) { + if (fromClick) { setState(() { isLoading = true; }); } - /*print(email); - print(password);*/ var accessToken = this.token; var instanceId = this.instanceId; var pinCode = this.pinCode; UserRole? userRole; - /*print("accessToken"); - print(accessToken);*/ - if(accessToken == null) { + + if (accessToken == null) { LoginDTO loginDTO = new LoginDTO(email: email, password: password); - /*print(email); - print(password);*/ TokenDTO? token = await clientAPI!.authenticationApi! .authenticationAuthenticateWithJson(loginDTO); - /*print("TOKENNN"); - print(token);*/ - - if(token != null) { + if (token != null) { accessToken = token.accessToken!; instanceId = token.instanceId!; pinCode = token.pinCode; @@ -101,7 +84,7 @@ class _LoginScreenState extends State { localStorage.addEntries({"email": email}.entries); localStorage.addEntries({"token": token.accessToken!}.entries); localStorage.addEntries({"instanceId": token.instanceId!}.entries); - if(token.pinCode != null) { + if (token.pinCode != null) { localStorage.addEntries({"pinCode": token.pinCode!.toString()}.entries); } } @@ -109,17 +92,14 @@ class _LoginScreenState extends State { localStorage.clear(); } } - } - if(accessToken != null) { + if (accessToken != null) { ManagerAppContext? managerAppContext = appContext.getContext(); - // Set the appContext if (managerAppContext == null) { managerAppContext = new ManagerAppContext(); } - // store user info locally managerAppContext.email = email; managerAppContext.host = host; managerAppContext.instanceId = instanceId; @@ -129,59 +109,51 @@ class _LoginScreenState extends State { managerAppContext.clientAPI = clientAPI; setAccessToken(accessToken); - InstanceDTO? instance = await managerAppContext.clientAPI!.instanceApi!.instanceGetDetail(managerAppContext.instanceId!); + InstanceDTO? instance = await managerAppContext.clientAPI!.instanceApi! + .instanceGetDetail(managerAppContext.instanceId!); managerAppContext.instanceDTO = instance; - FileHelper().writeSession(new Session(rememberMe: true, instanceId: instanceId, host: host, token: accessToken, password: password, email: email, role: userRole?.value)); + FileHelper().writeSession(new Session( + rememberMe: true, + instanceId: instanceId, + host: host, + token: accessToken, + password: password, + email: email, + role: userRole?.value)); appContext.setContext(managerAppContext); - if(instance!.isMobile!) { + if (instance!.isMobile!) { context.go('/main/mobile'); } else { - if(instance.isTablet!) { + if (instance.isTablet!) { context.go('/main/tablet'); } else { - if(instance.isWeb!) { + if (instance.isWeb!) { context.go('/main/web'); } else { - if(instance.isVR!) { + if (instance.isVR!) { context.go('/main/vr'); } } } } - if(fromClick) { + if (fromClick) { setState(() { isLoading = false; }); } - - /*Navigator.pushNamedAndRemoveUntil( - context, - '/main', // <- correspond à ta route définie - (Route route) => false, - );*/ - /*Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (context) { - return MainScreen(); - }, - ), - (Route route) => false // For pushAndRemoveUntil - );*/ } else { showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.loginError, context, null); setState(() { isLoading = false; }); } - } - catch (e) { + } catch (e) { print("error auth"); print(e); - if(fromClick) { + if (fromClick) { showNotification(Colors.orange, kWhite, AppLocalizations.of(context)!.loginError, context, null); setState(() { isLoading = false; @@ -192,39 +164,27 @@ class _LoginScreenState extends State { } void setAccessToken(String accessToken) { - //clientAPI.resourceApi.apiClient.addDefaultHeader('authorization', 'Bearer '+accessToken); - clientAPI!.apiApi!.addDefaultHeader('authorization', 'Bearer '+accessToken); - //clientAPI.resourceApi.addDefaultHeader('Bearer', accessToken); - //clientAPI.apiApi.authentication.applyToParams([], Map.from({'Bearer': accessToken})); - - /*clientAPI.apiApi.authentications.forEach((key, auth) { - if (auth is OAuth) { - auth.accessToken = accessToken; - } - });*/ + clientAPI!.apiApi!.addDefaultHeader('authorization', 'Bearer ' + accessToken); } @override void initState() { - //this.isRememberMe = widget.session.rememberMe; - this.host = "http://localhost:5000"; // "https://api.myinfomate.be"// "https://api.mymuseum.be" // "http://localhost:5000" //widget.session.host; // MDLF "http://192.168.1.19:8089" // "https://api.mymuseum.be" - //this.email = "test@email.be"; //widget.session.email; - //this.password = "kljqsdkljqsd"; //widget.session.password; + this.host = "http://localhost:5000"; - if(localStorage.containsKey("remember")) { + if (localStorage.containsKey("remember")) { this.isRememberMe = true; - if(localStorage.containsKey("email")) { - this.email = localStorage.entries.where((element) => element.key == "email").first.value; + if (localStorage.containsKey("email")) { + this.email = localStorage.entries.where((e) => e.key == "email").first.value; } - if(localStorage.containsKey("token")) { - this.token = localStorage.entries.where((element) => element.key == "token").first.value; + if (localStorage.containsKey("token")) { + this.token = localStorage.entries.where((e) => e.key == "token").first.value; this.password = "AnythingAsWeAlreadyHaveAccessToken"; } - if(localStorage.containsKey("instanceId")) { - this.instanceId = localStorage.entries.where((element) => element.key == "instanceId").first.value; + if (localStorage.containsKey("instanceId")) { + this.instanceId = localStorage.entries.where((e) => e.key == "instanceId").first.value; } - if(localStorage.containsKey("pinCode")) { - this.pinCode = localStorage.entries.where((element) => element.key == "pinCode").first.value; + if (localStorage.containsKey("pinCode")) { + this.pinCode = localStorage.entries.where((e) => e.key == "pinCode").first.value; } } super.initState(); @@ -232,11 +192,11 @@ class _LoginScreenState extends State { ManagerAppContext initInstance(ManagerAppContext managerAppContext) { var url = window.location.href; - if(!url.contains("localhost")) { + if (!url.contains("localhost")) { var urlParts = url.split('.'); - if(urlParts.length > 0) { + if (urlParts.length > 0) { var subdomain = urlParts[0].replaceAll('https://', ''); - switch(subdomain) { + switch (subdomain) { case "manager": this.pageTitle = "MyInfoMate"; break; @@ -248,8 +208,8 @@ class _LoginScreenState extends State { print("subdomain not found"); } } else { - this.email = "test@email.be"; //widget.session.email; - this.password = "kljqsdkljqsd"; //widget.session.password; + this.email = "test@email.be"; + this.password = "kljqsdkljqsd"; print("localhost.. set mymuseum instance by default"); } @@ -259,174 +219,166 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { final appContext = Provider.of(context); - Size size = MediaQuery.of(context).size; - - final GlobalKey<_LoginScreenState> loginKey = GlobalKey(); initInstance(appContext.getContext()); return Scaffold( - key: loginKey, - body: Center( - child: SingleChildScrollView( - child: Container( - height: size.height *0.7, - width: size.width *0.4, - constraints: BoxConstraints(minWidth: 400, minHeight: 500), - decoration: BoxDecoration( - color: kWhite, - borderRadius: BorderRadius.circular(8.0), - boxShadow: [ - BoxShadow( - color: kWhite.withValues(alpha: 0.3), - spreadRadius: 0.5, - blurRadius: 0.5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ), - child: Padding( - padding: const EdgeInsets.only(left: 50.0, right: 50.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - /*Padding( - padding: const EdgeInsets.all(8.0), - child: Icon(Icons.museum_outlined, color: kPrimaryColor, size: size.height*0.08), - ),*/ - Padding( - padding: const EdgeInsets.all(20.0), - child: Container( - constraints: BoxConstraints(maxHeight: 125, minHeight: 40), - child: SvgPicture.asset('assets/images/MyInfoMate_logo_only.svg') - ), - ), - Center( - child: AutoSizeText( - pageTitle, - style: new TextStyle(color: kPrimaryColor, fontSize: 27, fontFamily: "Helvetica"), - maxLines: 2, - textAlign: TextAlign.center, - ), - ), - FutureBuilder( - future: getAppVersion(), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data != null) { - return Center(child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text("${snapshot.data}", style: TextStyle(fontSize: 10)), - )); - } else { - return Text("No version found"); - } + body: LayoutBuilder( + builder: (context, constraints) { + final isMobile = constraints.maxWidth < 550; + final isTablet = constraints.maxWidth < 800; + final cardWidth = isMobile + ? constraints.maxWidth - 32.0 + : isTablet + ? 420.0 + : 440.0; + final horizontalPadding = isMobile ? 24.0 : 40.0; - } else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); - } else { - return SizedBox(); - } - } - ) - ], - ), - ), - /*RoundedInputField( - hintText: "Host", - onChanged: (value) { - host = value; - }, - initialValue: host, - icon: Icons.home - ),*/ - Form( - key: widget.key, - child: AutofillGroup( - child: Column( - children: [ - Container( - width: size.width*0.2, - constraints: BoxConstraints(minWidth: 250, maxWidth: 400), - child: RoundedInputField( - hintText: "E-mail", - autofill: AutofillHints.email, - onChanged: (value) { - email = value; - }, - icon: Icons.person, - initialValue: email, - isEmail: true, - ), - ), - Container( - width: size.width*0.2, - constraints: BoxConstraints(minWidth: 250, maxWidth: 400), - child: RoundedPasswordField( - initialValue: password, - onChanged: (value) { - password = value; - }, - ), - ), - if(kIsWeb) Padding( - padding: const EdgeInsets.all(1.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - child: Checkbox( - checkColor: kTextLightColor, - activeColor: kPrimaryColor, - value: this.isRememberMe, - onChanged: (bool? value) { - if(value != null) { - setState(() { - this.isRememberMe = value; - }); - } - }, - ), - ), - Text(AppLocalizations.of(context)!.rememberMe, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),), - ], - ), - ), - SizedBox(height: size.height * 0.015), - !isLoading ? RoundedButton( - text: AppLocalizations.of(context)!.connect, - fontSize: 16, - vertical: 15, - horizontal: 30, - press: () { - TextInput.finishAutofillContext(); - authenticateTRY(appContext, true); - }, - ): Container( - height: size.height * 0.1, - child: CommonLoader(iconSize: 40) - ), - ], - ) - ), - ), + return Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + kPrimaryColor.withValues(alpha: 0.12), + kBackgroundColor, + kBackgroundColor, ], ), ), - ), - ), - ) + child: Center( + child: SingleChildScrollView( + padding: EdgeInsets.all(isMobile ? 16.0 : 24.0), + child: Container( + width: cardWidth, + decoration: BoxDecoration( + color: kWhite, + borderRadius: BorderRadius.circular(16.0), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.08), + spreadRadius: 0, + blurRadius: 32, + offset: Offset(0, 8), + ), + ], + ), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: horizontalPadding, + vertical: 40.0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + constraints: BoxConstraints(maxHeight: 80, minHeight: 40), + child: SvgPicture.asset('assets/images/MyInfoMate_logo_only.svg'), + ), + SizedBox(height: 16), + AutoSizeText( + pageTitle, + style: TextStyle( + color: kPrimaryColor, + fontSize: 24, + fontFamily: "Helvetica", + fontWeight: FontWeight.w600, + ), + maxLines: 2, + textAlign: TextAlign.center, + ), + FutureBuilder( + future: getAppVersion(), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.data != null) { + return Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + snapshot.data!, + style: TextStyle(fontSize: 11, color: Colors.grey), + ), + ); + } + return SizedBox.shrink(); + }, + ), + SizedBox(height: 32), + Form( + key: widget.key, + child: AutofillGroup( + child: Column( + children: [ + RoundedInputField( + hintText: "E-mail", + autofill: AutofillHints.email, + onChanged: (value) => email = value, + icon: Icons.person_outline, + initialValue: email, + isEmail: true, + ), + RoundedPasswordField( + initialValue: password, + onChanged: (value) => password = value, + ), + if (kIsWeb) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Row( + children: [ + Checkbox( + checkColor: kWhite, + activeColor: kPrimaryColor, + value: isRememberMe, + onChanged: (value) { + if (value != null) { + setState(() => isRememberMe = value); + } + }, + ), + Text( + AppLocalizations.of(context)!.rememberMe, + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + ), + ], + ), + ), + SizedBox(height: 24), + !isLoading + ? SizedBox( + width: double.infinity, + child: RoundedButton( + text: AppLocalizations.of(context)!.connect, + fontSize: 16, + vertical: 15, + horizontal: 30, + press: () { + TextInput.finishAutofillContext(); + authenticateTRY(appContext, true); + }, + ), + ) + : CommonLoader(iconSize: 40), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + }, + ), ); } Future getAppVersion() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); - return "${packageInfo.version}"; //-${packageInfo.buildNumber} + return packageInfo.version; } -} \ No newline at end of file +}