From 4e9dc59df9313fd191b58cd31031e5ed97f40e01 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Wed, 18 Jun 2025 18:00:29 +0200 Subject: [PATCH] Slider ok, map wip + update gradle etc + qr scanner to mobile scanner --- .../Scanner/scanner_old.dart | 0 .../Tests/DebugOptionsWidget.dart | 0 {lib/Screens => AR-TEST}/Tests/TestAR.dart | 0 {lib/Screens => AR-TEST}/Tests/XRTest.dart | 0 {lib/Screens => AR-TEST}/Tests/cloudtest.dart | 0 {lib/Screens => AR-TEST}/Tests/localtest.dart | 0 {lib/Screens => AR-TEST}/Tests/testother.dart | 0 android/app/build.gradle | 10 +- android/app/src/main/AndroidManifest.xml | 3 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 2 +- assets/icons/marker.png | Bin 0 -> 4799 bytes lib/Components/ScannerDialog.dart | 250 +++---- lib/Components/audio_player.dart | 178 +++-- lib/Models/visitContext.dart | 2 +- lib/Screens/Home/home.dart | 2 - lib/Screens/Home/home_3.0.dart | 2 - lib/Screens/Sections/Map/filter_tree.dart | 173 +++++ .../Sections/Map/flutter_map_view.dart | 136 ++++ .../Sections/Map/geo_point_filter.dart | 390 ++++++++++ lib/Screens/Sections/Map/google_map_view.dart | 239 ++++++ lib/Screens/Sections/Map/map_box_view.dart | 236 ++++++ lib/Screens/Sections/Map/map_context.dart | 22 + lib/Screens/Sections/Map/map_page.dart | 188 +++++ lib/Screens/Sections/Map/marker_view.dart | 692 ++++++++++++++++++ lib/Screens/Sections/Map/tree_node.dart | 42 ++ .../PDF/{pdf_view.dart => pdf_page.dart} | 0 .../{slider_view.dart => slider_page.dart} | 231 +++--- lib/Screens/Visit/components/body.dart | 1 + lib/Screens/Visit/visit.dart | 1 + lib/Screens/section_page.dart | 189 +++-- pubspec.lock | 308 ++++++-- pubspec.yaml | 12 +- 33 files changed, 2866 insertions(+), 445 deletions(-) rename {lib/Screens => AR-TEST}/Scanner/scanner_old.dart (100%) rename {lib/Screens => AR-TEST}/Tests/DebugOptionsWidget.dart (100%) rename {lib/Screens => AR-TEST}/Tests/TestAR.dart (100%) rename {lib/Screens => AR-TEST}/Tests/XRTest.dart (100%) rename {lib/Screens => AR-TEST}/Tests/cloudtest.dart (100%) rename {lib/Screens => AR-TEST}/Tests/localtest.dart (100%) rename {lib/Screens => AR-TEST}/Tests/testother.dart (100%) create mode 100644 assets/icons/marker.png create mode 100644 lib/Screens/Sections/Map/filter_tree.dart create mode 100644 lib/Screens/Sections/Map/flutter_map_view.dart create mode 100644 lib/Screens/Sections/Map/geo_point_filter.dart create mode 100644 lib/Screens/Sections/Map/google_map_view.dart create mode 100644 lib/Screens/Sections/Map/map_box_view.dart create mode 100644 lib/Screens/Sections/Map/map_context.dart create mode 100644 lib/Screens/Sections/Map/map_page.dart create mode 100644 lib/Screens/Sections/Map/marker_view.dart create mode 100644 lib/Screens/Sections/Map/tree_node.dart rename lib/Screens/Sections/PDF/{pdf_view.dart => pdf_page.dart} (100%) rename lib/Screens/Sections/Slider/{slider_view.dart => slider_page.dart} (58%) diff --git a/lib/Screens/Scanner/scanner_old.dart b/AR-TEST/Scanner/scanner_old.dart similarity index 100% rename from lib/Screens/Scanner/scanner_old.dart rename to AR-TEST/Scanner/scanner_old.dart diff --git a/lib/Screens/Tests/DebugOptionsWidget.dart b/AR-TEST/Tests/DebugOptionsWidget.dart similarity index 100% rename from lib/Screens/Tests/DebugOptionsWidget.dart rename to AR-TEST/Tests/DebugOptionsWidget.dart diff --git a/lib/Screens/Tests/TestAR.dart b/AR-TEST/Tests/TestAR.dart similarity index 100% rename from lib/Screens/Tests/TestAR.dart rename to AR-TEST/Tests/TestAR.dart diff --git a/lib/Screens/Tests/XRTest.dart b/AR-TEST/Tests/XRTest.dart similarity index 100% rename from lib/Screens/Tests/XRTest.dart rename to AR-TEST/Tests/XRTest.dart diff --git a/lib/Screens/Tests/cloudtest.dart b/AR-TEST/Tests/cloudtest.dart similarity index 100% rename from lib/Screens/Tests/cloudtest.dart rename to AR-TEST/Tests/cloudtest.dart diff --git a/lib/Screens/Tests/localtest.dart b/AR-TEST/Tests/localtest.dart similarity index 100% rename from lib/Screens/Tests/localtest.dart rename to AR-TEST/Tests/localtest.dart diff --git a/lib/Screens/Tests/testother.dart b/AR-TEST/Tests/testother.dart similarity index 100% rename from lib/Screens/Tests/testother.dart rename to AR-TEST/Tests/testother.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 01ad02e..9a0a51f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -39,13 +39,21 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"*/ android { - compileSdkVersion 34 + namespace = "be.unov.mymuseum.fortsaintheribert" + compileSdkVersion 35 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + packagingOptions { + pickFirst 'lib/arm64-v8a/libc++_shared.so' + pickFirst 'lib/x86_64/libc++_shared.so' + pickFirst 'lib/x86/libc++_shared.so' + pickFirst 'lib/armeabi-v7a/libc++_shared.so' + } + kotlinOptions { jvmTarget = '1.8' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 56ae07a..5fee667 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -51,7 +51,8 @@ - + diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index cfe88f6..89e56bd 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 5ea421c..02c755b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -31,7 +31,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.2.0" apply false + id "com.android.application" version "8.1.0" apply false id "org.jetbrains.kotlin.android" version "1.9.0" apply false } diff --git a/assets/icons/marker.png b/assets/icons/marker.png new file mode 100644 index 0000000000000000000000000000000000000000..0ef8e10387ee2374089100a4013815d8141f4a97 GIT binary patch literal 4799 zcmX9?c{r5q_a|8rGrc5|r4q`LB}T+ZQOTBVjCCj)+n5j~gBUe$6cv$mDhV@|!i-m# zF(g}-FqW~CC0QPmWiZ2Ue1CsD*ZujNbDneVb3f-k*L9v}x2&&=3QG&~@bHM5-!Qe~ zt`r^~UV9+{F2W6w!?+7?sNMA|Jf)p7zq#Zf(!|Pyhv!T3VYU}Pmmj)!!#R|P2h_O# z@HV3hy?J=V9L!Bk93FZsjSeS`^#A1d+z^V@m9|I|`E@#2?1qWVjzO~4%KH;rq8Kza z7IR^R*4z@tBlz+a+~kO|!4F?h+~n^?x?0kpOt{|ANLDeS4*J;Z!7jVp@;q!{%uroe zdgkj{IUGHBy`&fEwe@xT;$W3mgmcuQgWS<3Ff&8I6NqGp0a`RU;-C5nS~nk-YNomv zDuDTSgI3S?o)0_TZA&kSkII^jt;b&WmdE5dMj;G^OvyC#!Li6ey&`a&8u)Q>9Nsx> zMYSCfs}|R_Fy;!O9w^0j)vt7Lcqg=3A{d<@FpnM$xtE(-!&h$xD6ZKwHheCandQ-x z#gKI>v|@PPia`0w1VE3)6DRc~FajQmDfDqmV5pghT;RDSkAX#mT2>kpuw5$XwC#m5 zZB{WesIX*rA+AehC8=$pOo=O4E+Xuza0Pf#-ATL=OC&1Xp%Plbk1w&>xLdMYaS-pA z`z>%1Z^#lcBK;8CZH?>de@cE^yMjN%eYvmKn{FHdgo<#S;meXuPV}na2K>4q+gU&3 z4BH&LVzjo>$h3V>pU3gmbNQyUvUHmXC?BT$?Y0?9fsnvY+X3Revv^r^>D@E5&12h% zpGREF773-7{N#i>UGlsqljShT_jpqIN?!1hhkm%%@y7mu?2|FKiQ#Nno}Z2K>MZLk zXefY60zEFAOcuikzJDOef+!1Kt^|@nl-2GUFeVGjchY}m{4nG;oN}nWrzZqff`7t) zszq#f^o!^iiTvUfe?}H^d{SO5CbpFNcGidT;?oiGlfaZxRDQ&9lDuG*0TW!YZLh!A z_3=u%jXIgwOCoqFyf>RSI6yyNMhxpO={P?K8N9X^fV!k$!9WDFCz;^i+qeMtL59d< zl;Uu4fUCbpZY%YQ#-!x!Bb67^qCrCpku$>!XVeD6(=f*xnf`UHbo;8x?rH(kq~ZZxSTglXPjYu58!>s>2>poPSjN0C`)!l$%Jk>F25R2} z5zfQGr-NxFs`k2x!^;!OrqMSmH3HFM^^iBHVNwT4y^fLNAu%ol@)m!E`syE93fp_( zQIK+VT+ZV)*LoWvF>p0}d~&~a$mpOH zri1Pw054v<@ER2pQdG%AEK2d4@bHJq=qh4 z0r*GkEN%I_Ne~hsk@b=EG8I=DGjBw;+x+4~2q;9)5%UJs$x(EbdGXn1`AL)l!fNm+ zCTz?>m3;A0QDB6%o=6CqUw$voX);C)L4Vl7TjF1izIWoC3Yb7I2`?Z{1=OZG4_h&VXNM^#T(?J+{x-g&}f0^ z8n-q*3HXwzy~{dUM|F4g73OW$h-ZU88qXk5`xktJE8RkbdwEfgd*~OcVh#fMvrmlU z^K*x6B{8)UhMrl|*U#BhWx4;IO?3o_#BcEoI|Qp)`MB=%V1?iUP9 z&5QiQeoZNn^>+D3w~d`q*)!epY$^zpoyTZs z*t(ozJ%`TBc@`;e29)`qLVQLe0wM}|KV2}U!b0bI8AO+soayV_Y-6@(q`Q!cZD6d8 z^KEVf5vZba$}z&&+cFhadg5@Ihq7c}ofg6o027JI7b~u|`UQqv#M`v`i9qN6%9{LB zZCW59;~ne&}vgVNDuGvo^16{tc7k z-Uvfsx_VMt!P&Lo-UW8xbhTYz#C^N*sT{|Uh&+PK@IRR?R2?DAHLW?Eg`GO z^1nlrQ^ng~-yZLVIEzFF(_%}72L%Tune?M6G^$g{#qOMc$Q%j9hp*dD0d3dUha?TDYygAR3%Uv&b4<2_;D6v$BZ z7u_{MC`J%JBI#v1T`!$N@%$6o%gEMXS3<6O|6HhS&94mmlS1dcB?!g1ks#NZgMsKy zqT{AShRIwGx!lWg%=5EyWjO0iW0tmC?%HeY38ZImmM2I;j>CDt+)A^Opkfr|wPR2I zC(CZ5_7WO!iZ$dQuSKD?XnNa>w_6n{e8dPNO==qc6FtCG50k1-$K(J=n1jElt?LvI&vH{r=$LxIHPP z;mj}aGvenFPPZ^kP4LQv#mSX5Lw3$QmV*UnIC7%sOSkO%ug^B#`GLKMjKFmzb<=49_d6zE$} zSevRx%~b(YQE1YSn%Z#O)U$Auwd-Pn%~AWxfKZ0rK|DD7!_Q8F<7F4S=HFC_BVgEcixd90l;cX zbNic$ee#`q4j)gkSih$gZm{ipXX_Vf>3AqK?wheL3gqh-249b|cU&?t1Dw}K_kl%| zU~cQWxjRWvmv-H3BEv1c&oQ#wVKr8-i1neIfhA1jX|t!3$ah{{yXCxO;@(DK&15bm z@wt>XjF{eunCbiU|Ii)qxfAT)tC^8w@494irwPZrPb(;Oeln!rL6>t7kUz0Rdu`*M zcfS&bIjg}cwo2@?_JS0NLJ7M@(1704B>T5knX1s^c~Qa@=8=Ft$?xBdb3c^Ut=7V9 z4Wki9HK4CoKK9aY+m5)O$HNa%wqb%NwBvK@C?TiT?h=d&!BAUaSX`F}LQkCQNr_vp z?`xl%XzY_T7g?y6vvMe1?#DrSi6=I2SgRXJ&_V*grscdua{=^Q=uO@vL3>*I3rt9> zMVhXr>k`e4hy~=CuYE*+z#=szF|^~k5gDZ$p4zNJo$SesH^2SCFjZ*kT&=B7NEvaT z-o+*-gnm2WsI=MAo!>kRFJ;yL?(oZ4Sqk`s)p&y&04$p^Xhem_>Z!Hu)Xe7vZVr)} z@)(DA3Z1|i+-hpNKUhu$s;E@v9cI5ov*2#ZN~p|x-C>Dl2KW?Ye;%-D=&-a-$$|+^ zBFpgcSsB?%dNj!yMVr}c(=%!)NhjKM9|O1Yh32V!hL*seUUgfd-2wC)#bf9E!OL+P zm;RWl99wFh;d*K3db!^8x=bg#o$Du^CW*1%maE;|(5<3fZ<@;L!TlW@9x!ymOk92- zwOJR{5eDRktUluGj&My@B;?`ikrzu~&JL0HxWgnTTF$#?cp#*ZRZpP0L+YZ`6DKMb z&>Iw&oL>%0r_vL^#oMw#^p}w<;34kVh|dfm>|36%8Ka~*0{IL0mOfb@9rSpbYZKy$ zy!`{FE^-JK*}}YLRIAQG+19d5DRZ;hYB)2mS@mD%yq#8gFB#4?KgV?=^;3FNeE_pG zKKF?C*U0MD>cbNjP3=LC#HshIog!PRr>?>r_j-n~h~^dpmfe!q zV~)$1++QQu2KN}>V1TyaJCx?z0N=r>(rLoVs@C`&EtY($2fk_g|M3CZy=}GJVhyXo zcp75P|B-mQ(!rro?;b(y_(|U23w5m);*dLl>ye}2-mGdPU9YnOR98PvCTAo!Q}ZZx zoHtf0x&ta$c}bI%Gx7HE{zn{^GdOI`(PZL zMQ*Wl0&T@Sn2t=W5EO`!df|xYbGZpt#{D{DJJ6a}B$->x5Ley3?BbH^hL}Hc7)!+u zx$eWu7p^7-Ge)D&AfgHVfx*6Vr!ZZavDx<`lw$Z9;;uQtv%V1*qmJ`pjpNn^?ZNl; z|A)s?9r1Lim_W>#B8oCH(cI&Fqrc5p&-x+h$KO33mM5&4oJBkk3||YWhL;?F)N?NW zf+KxyP4TzHfy^lbIr7Cr_wJprHR=|%_~9%E@)h?46jEM(&m88O48``Fx}=u&=MQkF z^P#Di>Ov^^_jo)XBYtq { - Barcode? result; - QRViewController? controller; - final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + final MobileScannerController controller = MobileScannerController(); + bool isProcessing = false; - // In order to get hot reload to work we need to pause the camera if the platform - // is android, or resume the camera if the platform is iOS. @override void reassemble() { super.reassemble(); if (Platform.isAndroid) { - controller!.pauseCamera(); + controller.stop(); } - controller!.resumeCamera(); + controller.start(); } @override @@ -41,194 +38,121 @@ class _ScannerDialogState extends State { Size size = MediaQuery.of(context).size; return Container( - height: size.height *0.5, - width: size.width *0.9, + height: size.height * 0.5, + width: size.width * 0.9, child: Stack( children: [ Center( child: ClipRRect( borderRadius: BorderRadius.circular(10.0), - child: _buildQrView(context), - ) - ), - Positioned( - top: 0, - right: 0, - child: Container( - width: 45, - height: 45, - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: kMainColor1, - borderRadius: BorderRadius.circular(20.0), + child: MobileScanner( + controller: controller, + //allowDuplicates: false, + onDetect: (barcodes) => _onDetect(barcodes), ), - margin: const EdgeInsets.all(8), - child: InkWell( - onTap: () async { - await controller?.toggleFlash(); - setState(() {}); - }, - child: FutureBuilder( - future: controller?.getFlashStatus(), - builder: (context, snapshot) { - return const Icon(Icons.flash_on, color: Colors.white); - }, - )), ), ), - Positioned( - bottom: 0, - right: 0, - child: Container( - width: 45, - height: 45, - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: kMainColor1, - borderRadius: BorderRadius.circular(20.0), - ), - margin: const EdgeInsets.all(8), - child: InkWell( - onTap: () async { - await controller?.flipCamera(); - setState(() {}); - }, - child: FutureBuilder( - future: controller?.getCameraInfo(), - builder: (context, snapshot) { - return const Icon(Icons.flip_camera_android, color: Colors.white); - }, - )), - ), + _buildControlButton( + icon: Icons.flash_on, + onTap: () => controller.toggleTorch(), + alignment: Alignment.topRight, + ), + _buildControlButton( + icon: Icons.flip_camera_android, + onTap: () => controller.switchCamera(), + alignment: Alignment.bottomRight, ), ], ), ); } - Widget _buildQrView(BuildContext context) { - // For this example we check how width or tall the device is and change the scanArea and overlay accordingly. - var scanArea = (MediaQuery.of(context).size.width < 400 || - MediaQuery.of(context).size.height < 400) - ? 225.0 - : 300.0; - - // To ensure the Scanner view is properly sizes after rotation - // we need to listen for Flutter SizeChanged notification and update controller - return QRView( - key: qrKey, - onQRViewCreated: _onQRViewCreated, - overlay: QrScannerOverlayShape( - borderColor: kMainColor1, - borderRadius: 10, - borderLength: 25, - borderWidth: 5, - overlayColor: Colors.black.withValues(alpha: 0.55), - cutOutSize: 225.0), - onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p), + Widget _buildControlButton({ + required IconData icon, + required VoidCallback onTap, + required Alignment alignment, + }) { + return Align( + alignment: alignment, + child: Container( + width: 45, + height: 45, + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + shape: BoxShape.rectangle, + color: kMainColor1, + borderRadius: BorderRadius.circular(20.0), + ), + child: InkWell( + onTap: onTap, + child: Icon(icon, color: Colors.white), + ), + ), ); } - _onQRViewCreated(QRViewController controller) { - setState(() { - this.controller = controller; - }); - if (Platform.isAndroid) { - controller.pauseCamera(); - } - controller.resumeCamera(); - controller.scannedDataStream.listen((scanData) { - setState(() { - result = scanData; + void _onDetect(BarcodeCapture capture) { + if (isProcessing) return; - var code = result == null ? "" : result!.code.toString(); - if(result!.format == BarcodeFormat.qrcode) { - controller.pauseCamera(); + final barcode = capture.barcodes.first; + final code = barcode.rawValue ?? ""; - RegExp regExp = RegExp(r'^(?:https:\/\/web\.myinfomate\.be\/([^\/]+)\/([^\/]+)\/([^\/]+)|([^\/]+))$'); - var match = regExp.firstMatch(code); - String? instanceId; - String? configurationId; - String? sectionId; + if (barcode.format == BarcodeFormat.qrCode && code.isNotEmpty) { + isProcessing = true; - if (match != null) { - instanceId = match.group(1); - configurationId = match.group(2); - sectionId = match.group(3) ?? match.group(4); + RegExp regExp = RegExp(r'^(?:https:\/\/web\.mymuseum\.be\/([^\/]+)\/([^\/]+)\/([^\/]+)|([^\/]+))$'); // myinfomate + var match = regExp.firstMatch(code); + String? instanceId = match?.group(1); + String? configurationId = match?.group(2); + String? sectionId = match?.group(3) ?? match?.group(4); - print('InstanceId: $instanceId'); - print('ConfigurationId: $configurationId'); - print('SectionId: $sectionId'); - } else { - print('L\'URL ne correspond pas au format attendu.'); - } - - - //print("QR CODE FOUND"); - /*ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('QR CODE FOUND - ${code.toString()}')), - );*/ - - VisitAppContext visitAppContext = widget.appContext!.getContext(); - - if(!visitAppContext.sectionIds!.contains(sectionId) || sectionId == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(TranslationHelper.getFromLocale('invalidQRCode', widget.appContext!.getContext())), backgroundColor: kMainColor2), - ); - Navigator.of(context).pop(); - - } else { - SectionDTO section = visitAppContext.currentSections!.firstWhere((cs) => cs!.id == sectionId)!; - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) { - return SectionPage(configuration: visitAppContext.configuration!, rawSection: null, visitAppContextIn: visitAppContext, sectionId: section.id!); - }, - ), - ); - } - } - }); - }); - } - - Future _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) async { - //log('${DateTime.now().toIso8601String()}_onPermissionSet $p'); - if (!p) { - var status = await Permission.camera.status; - if(!status.isGranted) { + if (match == null || sectionId == null) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('no Permission')), + SnackBar(content: Text("L'URL ne correspond pas au format attendu."), backgroundColor: kMainColor2), ); + Navigator.of(context).pop(); + return; + } - // You can request multiple permissions at once. - Map statuses = await [ - Permission.camera, - ].request(); - print(statuses[Permission.camera]); - print(status); + VisitAppContext visitAppContext = widget.appContext!.getContext(); + + if (visitAppContext.sectionIds == null || !visitAppContext.sectionIds!.contains(sectionId)) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(TranslationHelper.getFromLocale('invalidQRCode', visitAppContext)), backgroundColor: kMainColor2), + ); + Navigator.of(context).pop(); + } else { + dynamic rawSection = visitAppContext.currentSections!.firstWhere((cs) => cs!['id'] == sectionId)!; + Navigator.of(context).pop(); + Navigator.push( + context, + SlideFromRightRoute(page: SectionPage( + configuration: visitAppContext.configuration!, + rawSection: rawSection, + visitAppContextIn: visitAppContext, + sectionId: rawSection['id'], + )), + ); } } } @override void dispose() { - controller?.dispose(); + controller.dispose(); super.dispose(); } } -showScannerDialog (BuildContext context, AppContext appContext) { +showScannerDialog(BuildContext context, AppContext appContext) { showDialog( - builder: (BuildContext context) => AlertDialog( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10.0)) - ), - content: ScannerDialog(appContext: appContext), - contentPadding: EdgeInsets.zero, - ), context: context + context: context, + builder: (BuildContext context) => AlertDialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + content: ScannerDialog(appContext: appContext), + contentPadding: EdgeInsets.zero, + ), ); } - - diff --git a/lib/Components/audio_player.dart b/lib/Components/audio_player.dart index baa3cfc..bbb78ff 100644 --- a/lib/Components/audio_player.dart +++ b/lib/Components/audio_player.dart @@ -30,6 +30,7 @@ class _AudioPlayerFloatingContainerState extends State(context); VisitAppContext visitAppContext = appContext.getContext(); + Size size = MediaQuery.of(context).size; - return FloatingActionButton( - backgroundColor: Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)).withValues(alpha: 0.7), - onPressed: () async { + return InkWell( + onTap: () async { if(!isplaying && !audioplayed){ //player.play(BytesSource(audiobytes)); //await player.setUrl(widget.resourceURl); @@ -170,77 +197,84 @@ class _AudioPlayerFloatingContainerState extends State? sectionIds; // Use to valid QR code found List? beaconSections; - List? currentSections; + List? currentSections; List readSections = []; bool isContentCurrentlyShown = false; bool isScanningBeacons = false; diff --git a/lib/Screens/Home/home.dart b/lib/Screens/Home/home.dart index 0abb15b..6dcf7aa 100644 --- a/lib/Screens/Home/home.dart +++ b/lib/Screens/Home/home.dart @@ -21,8 +21,6 @@ import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/client.dart'; import 'package:mymuseum_visitapp/constants.dart'; import 'package:provider/provider.dart'; - -import '../Tests/TestAR.dart'; import 'configurations_list.dart'; class HomePage extends StatefulWidget { diff --git a/lib/Screens/Home/home_3.0.dart b/lib/Screens/Home/home_3.0.dart index 3a273c7..b24452b 100644 --- a/lib/Screens/Home/home_3.0.dart +++ b/lib/Screens/Home/home_3.0.dart @@ -25,8 +25,6 @@ import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/client.dart'; import 'package:mymuseum_visitapp/constants.dart'; import 'package:provider/provider.dart'; - -import '../Tests/TestAR.dart'; import 'configurations_list.dart'; class HomePage3 extends StatefulWidget { diff --git a/lib/Screens/Sections/Map/filter_tree.dart b/lib/Screens/Sections/Map/filter_tree.dart new file mode 100644 index 0000000..eda4ff7 --- /dev/null +++ b/lib/Screens/Sections/Map/filter_tree.dart @@ -0,0 +1,173 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import 'package:collection/collection.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/tree_node.dart'; + +class FilterTree extends StatefulWidget { + final List data; + final bool selectOneToAll; + final Function(TreeNode node, bool checked, int commonId) onChecked; + final Function(TreeNode node, int id) onClicked; + final Color? textColor; + final Color? checkBoxColor; + final EdgeInsets? childrenPadding; + + FilterTree( + {this.checkBoxColor, + this.textColor, + this.childrenPadding, + required this.onChecked, + required this.onClicked, + required this.data, + required this.selectOneToAll}); + + @override + _FilterTree createState() => _FilterTree(); +} + +class _FilterTree extends State { + @override + void initState() { + super.initState(); + } + + Map map = {}; + @override + Widget build(BuildContext context) { + return ListView.builder( + itemBuilder: (BuildContext context, int index) { + return _buildNode(widget.data[index], EdgeInsets.all(0)); + }, + itemCount: widget.data.length, + ); + } + + Widget _buildNode(TreeNode node, EdgeInsets childrenPadding) { + var nonCheckedIcon = Icon( + Icons.check_box_outline_blank, + color: widget.checkBoxColor ?? Colors.green, + ); + var checkedIcon = Icon( + Icons.check_box, + color: widget.checkBoxColor ?? Colors.green, + ); + + // Si le nœud a des enfants, retourne l'ExpansionTile avec les enfants + if (node.children.isNotEmpty) { + return Padding( + padding: childrenPadding, + child: InkWell( + onTap: () { + widget.onClicked(node, node.id); + }, + child: ExpansionTile( + iconColor: widget.checkBoxColor, + collapsedIconColor: widget.checkBoxColor, + tilePadding: EdgeInsets.fromLTRB(0, 0, 0, 0), + initiallyExpanded: node.show, + title: Container( + margin: EdgeInsets.only(left: 0), + padding: EdgeInsets.only(left: 0), + child: Text( + node.title, + style: TextStyle(color: widget.textColor ?? Colors.black), + ), + ), + leading: IconButton( + icon: node.checked ? checkedIcon : nonCheckedIcon, + onPressed: () { + setState(() { + _toggleNodeSelection(node, !node.checked); + widget.onChecked(node, node.checked, node.id); + }); + }, + ), + children: node.children + .map((child) => _buildNode( + child, widget.childrenPadding ?? EdgeInsets.all(0))) + .toList(), + ), + ), + ); + } else { + // Si le nœud n'a pas d'enfants, retourne simplement le titre sans ExpansionTile + // avec le même padding que les éléments avec des enfants + return Padding( + padding: childrenPadding, + child: InkWell( + onTap: () { + widget.onClicked(node, node.id); + }, + child: ListTile( + contentPadding: childrenPadding, // Utilisez le padding des enfants + title: Text( + node.title, + style: TextStyle(color: widget.textColor ?? Colors.black), + ), + leading: IconButton( + icon: node.checked ? checkedIcon : nonCheckedIcon, + onPressed: () { + setState(() { + _toggleNodeSelection(node, !node.checked); + widget.onChecked(node, node.checked, node.id); + }); + }, + ), + ), + ), + ); + } + } + + + void _toggleNodeSelection(TreeNode node, bool isChecked) { + node.checked = isChecked; + _updateParentNodes(node, isChecked); + for (var child in node.children) { + _toggleNodeSelection(child, isChecked); + } + } + + void _updateParentNodes(TreeNode node, bool checked) { + //First Check it has parent node + bool canContinue = false; + if (node.pid != 0) { + canContinue = true; + } + + //if it have parent node then check all childrens are checked or non checked + bool canCheck = true; + if (!checked) { + for (int i = 0; i < node.children.length; i++) { + if (node.children[i].checked != checked) { + canCheck = false; + } + } + } + + if (canCheck) { + node.checked = checked; + } + + if (canContinue) { + checkEachNode(widget.data, node, checked); + } + // if all childrens are non checked the check parent node in widget.node if it matches node id then check above parent node + } + + checkEachNode(List checkNode, TreeNode node, bool checked) { + bool canStop = false; + for (int i = 0; i < checkNode.length; i++) { + if (checkNode[i].children.isNotEmpty) { + checkEachNode(checkNode[i].children, node, checked); + } + if (checkNode[i].id == node.pid && !canStop) { + canStop = true; + _updateParentNodes(checkNode[i], checked); + break; + } + } + } +} diff --git a/lib/Screens/Sections/Map/flutter_map_view.dart b/lib/Screens/Sections/Map/flutter_map_view.dart new file mode 100644 index 0000000..7ce7408 --- /dev/null +++ b/lib/Screens/Sections/Map/flutter_map_view.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:mymuseum_visitapp/Models/visitContext.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/map_context.dart'; +import 'package:mymuseum_visitapp/app_context.dart'; +import 'package:provider/provider.dart'; +import 'package:html/parser.dart' show parse; +import 'package:latlong2/latlong.dart' as ll; + +class FlutterMapView extends StatefulWidget { + final MapDTO? mapDTO; + final List geoPoints; + final List> icons; + final String? language; + const FlutterMapView({ + Key? key, + this.mapDTO, + required this.geoPoints, + required this.icons, + this.language, + }) : super(key: key); + + @override + _FlutterMapViewState createState() => _FlutterMapViewState(); +} + +class _FlutterMapViewState extends State { + late List markersList; + late List markers; + bool filterZoneSelected = false; + MapController? mapController; + + List createPoints(mapContext) { + markersList = []; + markers = []; + + int i = 0; + widget.geoPoints.forEach((point) { + if (point.title!.where((translation) => translation.language == widget.language).isNotEmpty) { + var textSansHTML = parse(point.title!.firstWhere((translation) => translation.language == widget.language).value); + point.id = i; + point.title = point.title!.where((t) => t.language == widget.language).toList(); + markersList.add(point); + + //var icon = point.categorie == null ? BitmapDescriptor.fromBytes(widget.icons.where((i) => i['id'] == null).first['icon']) : widget.icons.any((i) => i['id'] == point.categorieId) ? BitmapDescriptor.fromBytes(widget.icons.where((i) => i['id'] == point.categorieId).first['icon']) : BitmapDescriptor.fromBytes(widget.icons.where((i) => i['id'] == null).first['icon']); //widget.selectedMarkerIcon,; + + markers.add( + Marker( + width: 80.0, + height: 80.0, + point: LatLng(double.tryParse(point.latitude!)!, double.tryParse(point.longitude!)!), + child: GestureDetector( + onTap: () { + /*final mapContext = Provider.of(context, listen: false); + mapContext.setSelectedPoint(point); + mapContext.setSelectedPointForNavigate(point);*/ + mapContext.setSelectedPoint(point); + //mapContext.setSelectedPointForNavigate(point); + }, + child: widget.icons.firstWhere((i) => i['id'] == point.categorieId, orElse: () => widget.icons.first)['icon'] != null ? Image.memory(widget.icons.firstWhere((i) => i['id'] == point.categorieId, orElse: () => widget.icons.first)['icon']) : Icon(Icons.pin_drop, color: Colors.red),//widget.icons.firstWhere((i) => i['id'] == point.categorieId, orElse: () => widget.icons.first)['icon'], + ) + ), + ); + i++; + } + }); + + return markers; + } + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final mapContext = Provider.of(context); + final appContext = Provider.of(context); + VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; + + markers = createPoints(mapContext); + + if (mapController == null) { // mapContext.getSelectedPointForNavigate() != null + /*var geoPoint = mapContext.getSelectedPointForNavigate(); + var center = LatLng(double.tryParse(geoPoint.latitude!)!, double.tryParse(geoPoint.longitude!)!);*/ + mapController = MapController(); + + //mapController!.move(center, widget.mapDTO!.zoom != null ? widget.mapDTO!.zoom!.toDouble() : 12); + mapContext.setSelectedPointForNavigate(null); // Reset after navigation + } + + return Stack( + children: [ + FlutterMap( + mapController: mapController, + options: MapOptions( + initialCenter: widget.mapDTO!.longitude != null && widget.mapDTO!.latitude != null ? ll.LatLng(double.tryParse(widget.mapDTO!.latitude!)!, double.tryParse(widget.mapDTO!.longitude!)!) : ll.LatLng(4.865105, 50.465503), //.toJson() + initialZoom: widget.mapDTO!.zoom != null ? widget.mapDTO!.zoom!.toDouble() : 12, + onTap: (Tap, lnt) => { + mapContext.setSelectedPointForNavigate(null), + mapContext.setSelectedPoint(null), + } + ), + children: [ + TileLayer( + urlTemplate: "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}", + userAgentPackageName: 'be.unov.myinfomate.tablet', + ), + MarkerLayer( + markers: markers + ), + ], + ), + Container( + child: Builder( + builder: (context) { + return Consumer( + builder: (context, mapContext, _) { + var geoPoint = mapContext.getSelectedPointForNavigate() as GeoPointDTO?; + if (geoPoint != null && mapController != null) { + print("COUCOU IL FAUT NAVIGATE FLUTTER MAP"); + mapController!.move(LatLng(double.tryParse(geoPoint.latitude!)!, double.tryParse(geoPoint.longitude!)!), mapController!.camera.zoom/*, widget.mapDTO!.zoom != null ? widget.mapDTO!.zoom!.toDouble() : 16*/); // keep actual zoom + } + return SizedBox(); + }, + ); + } + ), + ) + ], + ); + } +} diff --git a/lib/Screens/Sections/Map/geo_point_filter.dart b/lib/Screens/Sections/Map/geo_point_filter.dart new file mode 100644 index 0000000..1775550 --- /dev/null +++ b/lib/Screens/Sections/Map/geo_point_filter.dart @@ -0,0 +1,390 @@ +import 'dart:ui'; +import 'dart:math'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:mymuseum_visitapp/Helpers/translationHelper.dart'; +import 'package:mymuseum_visitapp/Models/visitContext.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/filter_tree.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/tree_node.dart'; +import 'package:mymuseum_visitapp/app_context.dart'; +import 'package:mymuseum_visitapp/constants.dart'; +import 'package:provider/provider.dart'; +import 'package:html/parser.dart' show parse; +import 'package:diacritic/diacritic.dart'; + +import 'map_context.dart'; + +class GeoPointFilter extends StatefulWidget { + final String language; + final List geoPoints; + final List categories; + final Function(List?) filteredPoints; + final MapProvider provider; + + GeoPointFilter({required this.language, required this.geoPoints, required this.categories, required this.filteredPoints, required this.provider}); + + @override + _GeoPointFilterState createState() => _GeoPointFilterState(); +} + +class _GeoPointFilterState extends State with SingleTickerProviderStateMixin { + List selectedGeoPoints = []; + late List _filteredNodes; + late ValueNotifier _searchTextNotifier; + final TextEditingController _searchController = TextEditingController(); + FocusNode focusNode = FocusNode(); + bool _isExpanded = false; + bool _showContent = false; + + late AnimationController _controller; + late Animation _widthAnimation; + late Size screenSize; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + screenSize = MediaQuery.of(context).size; + } + + @override + void initState() { + super.initState(); + _searchTextNotifier = ValueNotifier(''); + + _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300)); + _widthAnimation = Tween(begin: 40, end: 350).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeInOut), + ); + + _filteredNodes = buildTreeNodes(widget.categories, widget.geoPoints); + } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + void _toggleExpansion() { + setState(() { + if (_isExpanded) { + _showContent = false; + _isExpanded = false; + } else { + _isExpanded = true; + Future.delayed(const Duration(milliseconds: 300), () { + if (_isExpanded) { + setState(() => _showContent = true); + } + }); + } + _isExpanded ? _controller.forward() : _controller.reverse(); + + }); + } + + List buildTreeNodes(List categories, List geoPoints) { + List nodes = []; + + // Pour chaque point sans categorie, créer un noeud + for(var pointWithoutCat in geoPoints.where((gp) => gp.categorieId == null)) + { + if(pointWithoutCat.title!.where((l) => l.language == widget.language).firstOrNull != null) { + TreeNode nodeWithoutCat = TreeNode( + id: 000 + int.parse( + (pointWithoutCat.latitude ?? '').substring(0, min(pointWithoutCat.latitude!.length, 10)).replaceAll(".", "").replaceAll("-","") + (pointWithoutCat.longitude ?? '').substring(0, min(pointWithoutCat.longitude!.length, 10)).replaceAll(".", "").replaceAll("-","") + ), + title: parse(pointWithoutCat.title!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, + children: [], + checked: true, // default true + show: false, + pid: 0, + commonID: 0, + ); + nodes.add(nodeWithoutCat); + } + } + + // Pour chaque catégorie, créez un nœud parent + for (var category in categories) { + if(category.label!.where((l) => l.language == widget.language).firstOrNull != null) + { + TreeNode categoryNode = TreeNode( + id: 100 + (category.id ?? 0), + title: parse(category.label!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, + children: [], + checked: true, // default true + show: false, + pid: 0, + commonID: 0, + ); + + // Ajoutez les géopoints correspondant à cette catégorie en tant qu'enfants du nœud parent + for (var geoPoint in geoPoints.where((gp) => gp.categorieId != null)) { + if (geoPoint.categorieId == category.id && geoPoint.title!.where((l) => l.language == widget.language).firstOrNull != null) { + TreeNode geoPointNode = TreeNode( + id: 000 + int.parse( + (geoPoint.latitude ?? '').substring(0, min(geoPoint.latitude!.length, 10)).replaceAll(".", "").replaceAll("-", "") + (geoPoint.longitude ?? '').substring(0, min(geoPoint.longitude!.length, 10)).replaceAll(".", "").replaceAll("-", "") + ), + title: parse(geoPoint.title!.firstWhere((l) => l.language == widget.language).value!).documentElement!.text, + checked: true, // default true + show: false, + children: [], + pid: 0, + commonID: 0, + ); + categoryNode.children.add(geoPointNode); + } + } + + nodes.add(categoryNode); + } + } + + nodes.sort((a, b) => a.title.compareTo(b.title)); + + return nodes; + } + + void filterNodes() { + String searchText = _searchController.text; + setState(() { + _filteredNodes = searchText.isEmpty + ? buildTreeNodes(widget.categories, widget.geoPoints) + : _filterNodesBySearchText(searchText); + + // if unfocus, then + //if(searchText.isEmpty) { + // widget.filteredPoints = //todo + sendFilteredGeoPoint(); + //} + }); + } + + sendFilteredGeoPoint() { + List checkedGeoPoints = []; + // Parcourez les nœuds filtrés pour récupérer les GeoPointDTO correspondants qui sont cochés + for (var node in _filteredNodes) { + if (node.children.isNotEmpty) { + for (var childNode in node.children) { + if (childNode.checked) { + var point = widget.geoPoints.firstWhere( + (point) { + String latitudePart = (point.latitude ?? '') + .substring(0, min(point.latitude!.length, 10)) + .replaceAll(".", "") + .replaceAll("-", ""); + String longitudePart = (point.longitude ?? '') + .substring(0, min(point.longitude!.length, 10)) + .replaceAll(".", "") + .replaceAll("-", ""); + int combinedValue = int.parse(latitudePart + longitudePart); + + return combinedValue == childNode.id; + }, + orElse: () => GeoPointDTO(id: -1), + ); + + if (point.id != -1) { + checkedGeoPoints.add(point); + } + } + } + } else { + if(node.checked) { + var point = widget.geoPoints.firstWhere( + (point) { + String latitudePart = (point.latitude ?? '') + .substring(0, min(point.latitude!.length, 10)) + .replaceAll(".", "") + .replaceAll("-", ""); + String longitudePart = (point.longitude ?? '') + .substring(0, min(point.longitude!.length, 10)) + .replaceAll(".", "") + .replaceAll("-", ""); + int combinedValue = int.parse(latitudePart + longitudePart); + return combinedValue == node.id; + }, + orElse: () => GeoPointDTO(id: -1), + ); + + if (point.id != -1) { + checkedGeoPoints.add(point); + } + } + } + } + + // Passez la liste des GeoPointDTO cochés à la fonction filteredPoints + widget.filteredPoints(checkedGeoPoints); + } + + List _filterNodesBySearchText(String searchText) { + List filteredNodes = []; + for (var node in buildTreeNodes(widget.categories, widget.geoPoints)) { + if (_nodeOrChildrenContainsText(node, searchText)) { + if(node.children.isNotEmpty) { + for (var childNode in node.children) { + if (_nodeOrChildrenContainsText(childNode, searchText)) { + filteredNodes.add(childNode); + } + } + } else { + filteredNodes.add(node); + } + } + } + return filteredNodes; + } + + bool _nodeOrChildrenContainsText(TreeNode node, String searchText) { + // Remove accent and other special characters + String normalizedSearchText = removeDiacritics(searchText.toLowerCase()); + String normalizedTitle = removeDiacritics(node.title.toLowerCase()); + + if (normalizedTitle.contains(normalizedSearchText)) { + return true; + } + for (var childNode in node.children) { + if (_nodeOrChildrenContainsText(childNode, searchText)) { + return true; + } + } + return false; + } + + @override + Widget build(BuildContext context) { + final appContext = Provider.of(context); + VisitAppContext visitAppContext = appContext.getContext(); + var currentLanguage = visitAppContext.language; + final mapContext = Provider.of(context); + + var primaryColor = visitAppContext.configuration != null ? visitAppContext.configuration!.primaryColor != null ? Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : kSecondColor : kSecondColor; + double rounded = visitAppContext.configuration?.roundedValue?.toDouble() ?? 20.0; + + return Positioned( + left: 0, + top: _isExpanded ? screenSize.height * 0.11 : screenSize.height / 2 - (75 / 2), + child: AnimatedBuilder( + animation: _widthAnimation, + builder: (context, child) { + return Container( + width: _widthAnimation.value, + height: _isExpanded ? screenSize.height*0.78 : 75, + decoration: BoxDecoration( + color: _isExpanded ? kBackgroundColor.withValues(alpha: 0.9) : primaryColor.withValues(alpha: 0.8), + borderRadius: BorderRadius.only(topRight: Radius.circular(rounded), bottomRight: Radius.circular(rounded)), + ), + child: _showContent ? Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + IconButton( + icon: Icon(Icons.close, color: primaryColor), + onPressed: _toggleExpansion, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + height: 60, + width: (screenSize.width * 0.76), + child: Padding( + padding: const EdgeInsets.only(left: 10.0, bottom: 8.0, top: 4.0), + child: TextField( + focusNode: focusNode, + controller: _searchController, + onChanged: (value) { + filterNodes(); + }, + cursorColor: Colors.black, + style: const TextStyle(color: Colors.black), + decoration: InputDecoration( + labelText: _searchController.text.isEmpty ? TranslationHelper.getFromLocale("map.search", appContext.getContext()) : "", + labelStyle: const TextStyle( + color: Colors.black + ), + focusColor: primaryColor, + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)), + borderSide: BorderSide(color: primaryColor)), + //labelStyle: TextStyle(color: primaryColor), + suffixIcon: IconButton( + icon: _searchController.text.isNotEmpty ? Icon(Icons.close, color: primaryColor) : Icon(Icons.search, color: primaryColor), + onPressed: () { + if(_searchController.text.isNotEmpty) { + _searchController.text = ""; + // TODO reset view ? + } + filterNodes(); + if(_searchController.text.isNotEmpty) { + focusNode.unfocus(); + } + }, + ), + ), + ), + ), + ), + ], + ), + ValueListenableBuilder( + valueListenable: _searchTextNotifier, + builder: (context, value, _) { + return SizedBox( + width: screenSize.width * 0.8, + height: screenSize.height * 0.63, + child: FilterTree( + data: _filteredNodes, + selectOneToAll: true, + textColor: Colors.black, + onChecked: (node, checked, commonID) { + sendFilteredGeoPoint(); + }, + onClicked: (node, commonID) { + var selectedNode = widget.geoPoints.firstWhere( + (point) { + String latitudePart = (point.latitude ?? '') + .substring(0, min(point.latitude!.length, 10)) + .replaceAll(".", "") + .replaceAll("-", ""); + String longitudePart = (point.longitude ?? '') + .substring(0, min(point.longitude!.length, 10)) + .replaceAll(".", "") + .replaceAll("-", ""); + int combinedValue = + int.parse(latitudePart + longitudePart); + return combinedValue == commonID; + }, + orElse: () => GeoPointDTO(id: -1), + ); + + if (selectedNode.id != -1) { + mapContext.setSelectedPointForNavigate(selectedNode); + _toggleExpansion(); + } else { + print('Aucun point correspondant trouvé.'); + } + }, + checkBoxColor: primaryColor, + childrenPadding: const EdgeInsets.only( + left: 20, top: 10, right: 0, bottom: 10), + ), + ); + } + ), + ], + ), + ) : IconButton( + icon: const Icon(Icons.search, color: Colors.white), + onPressed: _toggleExpansion, + ), + ); + } + ), + ); + } +} + diff --git a/lib/Screens/Sections/Map/google_map_view.dart b/lib/Screens/Sections/Map/google_map_view.dart new file mode 100644 index 0000000..b7c09e0 --- /dev/null +++ b/lib/Screens/Sections/Map/google_map_view.dart @@ -0,0 +1,239 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:mymuseum_visitapp/Models/visitContext.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/map_context.dart'; +import 'package:mymuseum_visitapp/app_context.dart'; +import 'package:provider/provider.dart'; +import 'package:html/parser.dart' show parse; + +class GoogleMapView extends StatefulWidget { + final MapDTO mapDTO; + final List geoPoints; + final List> icons; + final String? language; + const GoogleMapView({ + Key? key, + required this.mapDTO, + required this.geoPoints, + required this.icons, + this.language, + }) : super(key: key); + + @override + _GoogleMapViewState createState() => _GoogleMapViewState(); +} + +class _GoogleMapViewState extends State { + ConfigurationDTO? configurationDTO; + Completer _controller = Completer(); + GoogleMapController? _GoogleMapcontroller; + Set markers = {}; + List? pointsToShow = []; + //List? selectedCategories = []; + bool init = false; + + Set getMarkers(language, mapContext) { + markers = {}; + + int i = 0; + pointsToShow!.forEach((point) { + var textSansHTML = parse(point.title!.firstWhere((translation) => translation.language == language).value); + point.id = i; + /*var mapMarker = new MapMarker( + id: point.id, + title: parse(textSansHTML.body!.text).documentElement!.text, + description: point.description!.firstWhere((translation) => translation.language == language).value, + longitude: point.longitude, + latitude: point.latitude, + contents: point.contents + );*/ + if (point.latitude != null && point.longitude != null) { + var icon = point.categorieId == null ? BitmapDescriptor.bytes(widget.icons.where((i) => i['id'] == null).first['icon']) : widget.icons.any((i) => i['id'] == point.categorieId) ? BitmapDescriptor.bytes(widget.icons.where((i) => i['id'] == point.categorieId).first['icon']) : BitmapDescriptor.bytes(widget.icons.where((i) => i['id'] == null).first['icon']); //widget.selectedMarkerIcon,; + markers.add(Marker( + draggable: false, + markerId: MarkerId(parse(textSansHTML.body!.text).documentElement!.text + point.latitude! + point.longitude!), + position: LatLng( + double.tryParse(point.latitude!)!, + double.tryParse(point.longitude!)!, + ), + icon: icon, //widget.selectedMarkerIcon, + //widget.selectedMarkerIcon != null ? BitmapDescriptor.fromBytes(widget.selectedMarkerIcon!) : BitmapDescriptor.defaultMarker, + /*icon: BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueYellow, + ),*/ + onTap: () { + //setState(() { + mapContext.setSelectedPoint(point); + //mapContext.setSelectedPointForNavigate(point); + //}); + }, + infoWindow: InfoWindow.noText)); + } + i++; + }); + return markers; + } + + @override + void initState() { + //selectedCategories = widget.mapDTO.categories!.map((cat) => cat.label!.firstWhere((element) => element.language == widget.language).value!).toList(); + super.initState(); + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final mapContext = Provider.of(context); + final appContext = Provider.of(context); + VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; + Size size = MediaQuery.of(context).size; + + pointsToShow = widget.geoPoints; + + /*if(!init) { + print("getmarkers in build");*/ + getMarkers(widget.language, mapContext); + /* init = true; + }*/ + //MapTypeApp? mapTypeApp = MapTypeApp.fromJson(widget.mapDTO!.mapType!.value); + //print(mapTypeApp.toString()); + + MapType type = MapType.hybrid; + //if(kIsWeb) { + if(widget.mapDTO.mapType != null) { + switch(widget.mapDTO.mapType!.value) { + case 0: + type = MapType.none; + break; + case 1: + type = MapType.normal; + break; + case 2: + type = MapType.satellite; + break; + case 3: + type = MapType.terrain; + break; + case 4: + type = MapType.hybrid; + break; + } + } + + /*} else { + print("is OTHEER"); + type = EnumToString.fromString(MapType.values, widget.mapDTO!.mapType.toString()) != null ? EnumToString.fromString(MapType.values, widget.mapDTO!.mapType.toString())! : MapType.hybrid; + }*/ + + + //MapType type = EnumToString.fromString(MapType.values, widget.mapDTO!.mapType.toString()) != null ? EnumToString.fromString(MapType.values, widget.mapDTO!.mapType.toString())! : MapType.hybrid; + return Stack( + children: [ + Center( + child: GoogleMap( + mapType: type, // widget.mapDTO!.mapType != null ? EnumToString.fromString(MapType.values, widget.mapDTO!.mapType.toString())!: MapType.hybrid, + mapToolbarEnabled: false, + indoorViewEnabled: false, + initialCameraPosition: CameraPosition( + target: widget.mapDTO.longitude != null && widget.mapDTO.latitude != null ? LatLng(double.tryParse(widget.mapDTO.latitude!)!, double.tryParse(widget.mapDTO.longitude!)!) : LatLng(50.465503, 4.865105) , // MDLF 50.416639, 4.879169 / Namur 50.465503, 4.865105 + zoom: widget.mapDTO.zoom != null ? widget.mapDTO.zoom!.toDouble() : 18, + ), + onMapCreated: (GoogleMapController controller) { + if(kIsWeb) { + //_controllerWeb.complete(controller); + } else { + _controller.complete(controller); + _GoogleMapcontroller = controller; + } + }, + markers: markers, + onTap: (LatLng location) { + print(location); + mapContext.setSelectedPoint(null); + mapContext.setSelectedPointForNavigate(null); + }, + ), + ), + Container( + child: Builder( + builder: (context) { + return Consumer( + builder: (context, mapContext, _) { + var geopoint = mapContext.getSelectedPointForNavigate() as GeoPointDTO?; + if (geopoint != null && _GoogleMapcontroller != null) { + print("COUCOU IL FAUT NAVUGATE"); + // TODO Handle zoomDetail + _GoogleMapcontroller!.getZoomLevel().then((actualZoom) { + var zoomToNavigate = actualZoom <= 12.0 ? 15.0 : actualZoom; + _GoogleMapcontroller!.animateCamera(CameraUpdate.newCameraPosition( + CameraPosition( + target: LatLng(double.tryParse(geopoint.latitude!)!, double.tryParse(geopoint.longitude!)!), + tilt: 0.0, + bearing: 0.0, + zoom: zoomToNavigate + //zoom: widget.mapDTO.zoom != null ? widget.mapDTO.zoom!.toDouble() : 16 + ) + )); + }); + } + return SizedBox(); + }, + ); + } + ), + ) + /*Positioned( + left: 5, + top: 35, + child: SizedBox( + width: size.width * 0.3, + height: size.height * 0.76, + child: GeoPointFilter( + language: tabletAppContext.language!, + geoPoints: widget.mapDTO!.points!, + categories: widget.mapDTO!.categories!, + filteredPoints: (filteredPoints) { + print("COUCOU FILTERED POINTS"); + print(filteredPoints); + }), + ), + ), + Positioned( + bottom: 35, + left: 10, + child: SizedBox( + width: size.width * 0.75, + child: MultiSelectContainer( + label: null, + color: kBackgroundGrey, + initialValue: selectedCategories!, + isMultiple: true, + values: widget.mapDTO!.categories!.map((categorie) => categorie.label!.firstWhere((element) => element.language == widget.language).value!).toList(), + onChanged: (value) { + var tempOutput = new List.from(value); + print(tempOutput); + if(init) { + selectedCategories = tempOutput; + pointsToShow = widget.mapDTO!.points!.where((point) => tempOutput.any((tps) => point.categorie?.label?.firstWhere((lab) => lab.language == widget.language).value == tps) || point.categorie == null).toList(); + setState(() { + markers = getMarkers(widget.language, mapContext); + mapContext.notifyListeners(); + }); + } + }, + ), + ), + ),*/ + ], + ); + } + +} \ No newline at end of file diff --git a/lib/Screens/Sections/Map/map_box_view.dart b/lib/Screens/Sections/Map/map_box_view.dart new file mode 100644 index 0000000..3d191a7 --- /dev/null +++ b/lib/Screens/Sections/Map/map_box_view.dart @@ -0,0 +1,236 @@ + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' as mapBox; +import 'package:mymuseum_visitapp/Models/visitContext.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/map_context.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/map_page.dart'; +import 'package:mymuseum_visitapp/app_context.dart'; +import 'package:provider/provider.dart'; +import 'package:html/parser.dart' show parse; + +class MapBoxView extends StatefulWidget { + final MapDTO? mapDTO; + final List geoPoints; + final List> icons; + final String? language; + const MapBoxView({ + Key? key, + this.mapDTO, + required this.geoPoints, + required this.icons, + this.language, + }) : super(key: key); + + @override + _MapBoxViewState createState() => _MapBoxViewState(); +} + +class AnnotationClickListener extends mapBox.OnPointAnnotationClickListener { + late List markersList; + late MapContext mapContext; + + AnnotationClickListener({ + required this.markersList, + required this.mapContext, + }); + + @override + void onPointAnnotationClick(mapBox.PointAnnotation annotation) { + try{ + var markerToShow = markersList.firstWhere((ml) => "${parse(ml.title!.first.value).documentElement!.text}${ml.latitude}${ml.longitude}" == annotation.textField); + mapContext.setSelectedPoint(markerToShow); + //mapContext.setSelectedPointForNavigate(markerToShow); + } catch(e) { + print("ISSSUE setSelectedMarker"); + print(e); + } + } +} + +class _MapBoxViewState extends State { + late mapBox.MapboxMap? mapboxMap; + mapBox.PointAnnotationManager? pointAnnotationManager; + bool filterZoneSelected = false; + + createPoints() { + var options = []; + int i = 0; + markersList = []; + pointsToShow!.forEach((point) { + if(point.title!.where((translation) => translation.language == widget.language).firstOrNull != null) { + var textSansHTML = parse(point.title!.firstWhere((translation) => translation.language == widget.language).value); + point.id = i; + point.title = point.title!.where((t) => t.language == widget.language).toList(); + /*var mapMarker = new MapMarker( + id: i, + title: parse(textSansHTML.body!.text).documentElement!.text, + description: point.description!.firstWhere((translation) => translation.language == widget.language).value, + longitude: point.longitude, + latitude: point.latitude, + contents: point.contents + );*/ + markersList.add(point); + options.add(mapBox.PointAnnotationOptions( + geometry: mapBox.Point( + coordinates: mapBox.Position( + double.tryParse(point.longitude!)!, + double.tryParse(point.latitude!)!, + )), // .toJson() + iconSize: 1.3, + textField: "${parse(textSansHTML.body!.text).documentElement!.text}${point.latitude}${point.longitude}", + textOpacity: 0.0, + iconOffset: [0.0, 0.0], + symbolSortKey: 10, + iconColor: 0, + iconImage: null, + image: point.categorieId == null ? widget.icons.where((i) => i['id'] == null).first['icon'] : widget.icons.any((i) => i['id'] == point.categorieId) ? widget.icons.where((i) => i['id'] == point.categorieId).first['icon'] : widget.icons.where((i) => i['id'] == null).first['icon'], //widget.selectedMarkerIcon, + )); // , + + i++; + } + }); + + print(options.length); + + return options; + } + + _onMapCreated(mapBox.MapboxMap mapboxMap, MapContext mapContext) { + this.mapboxMap = mapboxMap; + + mapboxMap.annotations.createPointAnnotationManager().then((pointAnnotationManager) async { + this.pointAnnotationManager = pointAnnotationManager; + pointAnnotationManager.createMulti(createPoints()); + pointAnnotationManager.addOnPointAnnotationClickListener(AnnotationClickListener(mapContext: mapContext, markersList: markersList)); + init = true; + }); + } + + ConfigurationDTO? configurationDTO; + //Completer _controller = Completer(); + //Set markers = {}; + List? pointsToShow = []; + List? selectedCategories = []; + bool init = false; + + @override + void initState() { + pointsToShow = widget.geoPoints;//widget.mapDTO!.points; + var nonNullCat = widget.mapDTO!.categories!.where((c) => c.label!.where((element) => element.language == widget.language).firstOrNull != null); + selectedCategories = nonNullCat.map((categorie) => categorie.label!.firstWhere((element) => element.language == widget.language).value!).toList(); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final mapContext = Provider.of(context); + + pointsToShow = widget.geoPoints;//widget.mapDTO!.points; + + if(pointAnnotationManager != null) { + pointAnnotationManager!.deleteAll(); + pointAnnotationManager!.createMulti(createPoints()); + //mapContext.notifyListeners(); + } + + final appContext = Provider.of(context); + VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; + Size size = MediaQuery.of(context).size; + + var type = mapBox.MapboxStyles.STANDARD; + if(widget.mapDTO!.mapTypeMapbox != null) { + switch(widget.mapDTO!.mapTypeMapbox!) { + case MapTypeMapBox.standard: + type = mapBox.MapboxStyles.STANDARD; + break; + case MapTypeMapBox.streets: + type = mapBox.MapboxStyles.MAPBOX_STREETS; + break; + case MapTypeMapBox.outdoors: + type = mapBox.MapboxStyles.OUTDOORS; + break; + case MapTypeMapBox.light: + type = mapBox.MapboxStyles.LIGHT; + break; + case MapTypeMapBox.dark: + type = mapBox.MapboxStyles.DARK; + break; + case MapTypeMapBox.satellite: + type = mapBox.MapboxStyles.SATELLITE; + break; + case MapTypeMapBox.satellite_streets: + type = mapBox.MapboxStyles.SATELLITE_STREETS; + break; + } + } + return Stack( + children: [ + Center( + child: mapBox.MapWidget( + key: ValueKey("mapBoxWidget"), + styleUri: type, + onMapCreated: (maBoxMap) { + _onMapCreated(maBoxMap, mapContext); + }, + onTapListener: (listener) { + // close on tap + mapContext.setSelectedPoint(null); + mapContext.setSelectedPointForNavigate(null); + }, + cameraOptions: mapBox.CameraOptions( + center: mapBox.Point(coordinates: widget.mapDTO!.longitude != null && widget.mapDTO!.latitude != null ? mapBox.Position(double.tryParse(widget.mapDTO!.longitude!)!, double.tryParse(widget.mapDTO!.latitude!)!) : mapBox.Position(4.865105, 50.465503)), //.toJson() + zoom: widget.mapDTO!.zoom != null ? widget.mapDTO!.zoom!.toDouble() : 12), + ) + ), + Container( + child: Builder( + builder: (context) { + return Consumer( + builder: (context, mapContext, _) { + var geoPoint = mapContext.getSelectedPointForNavigate() as GeoPointDTO?; + if (geoPoint != null && mapboxMap != null) { + print("COUCOU IL FAUT NAVUGATE MAPBOX"); + // TODO Handle zoomDetail + mapboxMap?.easeTo( + mapBox.CameraOptions( + center: mapBox.Point(coordinates: mapBox.Position(double.tryParse(geoPoint.longitude!)!, double.tryParse(geoPoint.latitude!)!)), //.toJson() + zoom: 16, + bearing: 0, + pitch: 3), + mapBox.MapAnimationOptions(duration: 2000, startDelay: 0)); + } + return SizedBox(); + }, + ); + } + ), + ) + /*Positioned( + left: 5, + top: 35, + child: SizedBox( + width: size.width * 0.3, + height: size.height * 0.76, + child: GeoPointFilter( + language: tabletAppContext.language!, + geoPoints: widget.mapDTO!.points!, + categories: widget.mapDTO!.categories!, + filteredPoints: (filteredPoints) { + print("COUCOU FILTERED POINTS"); + print(filteredPoints); + }), + ), + ),*/ + ], + ); + } + +} \ No newline at end of file diff --git a/lib/Screens/Sections/Map/map_context.dart b/lib/Screens/Sections/Map/map_context.dart new file mode 100644 index 0000000..e2a5a4e --- /dev/null +++ b/lib/Screens/Sections/Map/map_context.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; + +class MapContext with ChangeNotifier { + GeoPointDTO? _selectedPoint; + GeoPointDTO? _selectedPointNavigate; + + MapContext(this._selectedPoint); + + getSelectedPoint() => _selectedPoint; + setSelectedPoint(GeoPointDTO? selectedPoint) { + _selectedPoint = selectedPoint; + notifyListeners(); + } + + getSelectedPointForNavigate() => _selectedPointNavigate; + setSelectedPointForNavigate(GeoPointDTO? selectedPointNavigate) { + _selectedPointNavigate = selectedPointNavigate; + notifyListeners(); + } + +} \ No newline at end of file diff --git a/lib/Screens/Sections/Map/map_page.dart b/lib/Screens/Sections/Map/map_page.dart new file mode 100644 index 0000000..d67d962 --- /dev/null +++ b/lib/Screens/Sections/Map/map_page.dart @@ -0,0 +1,188 @@ +//import 'dart:async'; +import 'dart:convert'; +// 'dart:typed_data'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +// 'package:flutter/services.dart'; +//import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:mymuseum_visitapp/Models/visitContext.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/flutter_map_view.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/geo_point_filter.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/map_box_view.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/marker_view.dart'; +import 'package:mymuseum_visitapp/app_context.dart'; +import 'package:mymuseum_visitapp/constants.dart'; +import 'package:provider/provider.dart'; +/*import 'package:tablet_app/Components/loading.dart'; +import 'package:tablet_app/Components/loading_common.dart';*/ +//import 'dart:ui' as ui; +import 'package:flutter/widgets.dart'; +//import 'package:http/http.dart' as http; + +import 'google_map_view.dart'; +//import 'package:image/image.dart' as IMG; + +//Set markers = {}; +List markersList = []; + +class MapPage extends StatefulWidget { + final MapDTO section; + final List> icons; + MapPage({Key? key, required this.section, required this.icons}) : super(key: key); + + @override + _MapPage createState() => _MapPage(); +} + +class _MapPage extends State { + MapDTO? mapDTO; + //Completer _controller = Completer(); + //Uint8List? selectedMarkerIcon; + late ValueNotifier> _geoPoints = ValueNotifier>([]); + + /*Future getBytesFromAsset(ByteData data, int width) async { + //ByteData data = await rootBundle.load(path); + ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), + targetWidth: width); + ui.FrameInfo fi = await codec.getNextFrame(); + return (await fi.image.toByteData(format: ui.ImageByteFormat.png)) + !.buffer + .asUint8List(); + }*/ + + @override + void initState() { + //mapDTO = MapDTO.fromJson(jsonDecode(widget.section.data!)); + mapDTO = widget.section; + _geoPoints.value = mapDTO!.points!; + super.initState(); + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + } + + /*static final CameraPosition _kLake = CameraPosition( + bearing: 192.8334901395799, + target: LatLng(37.43296265331129, -122.08832357078792), + tilt: 59.440717697143555, + zoom: 59.151926040649414);*/ + + @override + Widget build(BuildContext context) { + //final mapContext = Provider.of(context); + final appContext = Provider.of(context); + VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; + + var primaryColor = visitAppContext.configuration != null ? visitAppContext.configuration!.primaryColor != null ? Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : kSecondColor : kSecondColor; + + /*return FutureBuilder( + future: getByteIcon(mapDTO!.iconSource), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + } else if (snapshot.connectionState == ConnectionState.none) { + return Text("No data"); + } else { + return Center( + child: Container( + child: LoadingCommon() + ) + ); + } + } + );*/ + return MediaQuery.removeViewInsets( + context: context, + removeBottom: true, + child: Stack( + fit: StackFit.passthrough, + children: [ + ValueListenableBuilder>( + valueListenable: _geoPoints, + builder: (context, value, _) { + switch(mapDTO!.mapProvider) { + case MapProvider.Google: + return GoogleMapView(language: appContext.getContext().language, geoPoints: value, mapDTO: mapDTO!, icons: widget.icons); + case MapProvider.MapBox: + return MapBoxView(language: appContext.getContext().language, geoPoints: value, mapDTO: mapDTO, icons: widget.icons); + // If mapbox bug as 3.24 flutter, we can test via this new widget + return FlutterMapView(language: appContext.getContext().language, geoPoints: value, mapDTO: mapDTO, icons: widget.icons); + default: + // By default google + return GoogleMapView(language: appContext.getContext().language, geoPoints: value, mapDTO: mapDTO!, icons: widget.icons); + } + } + ), + GeoPointFilter( + language: visitAppContext.language!, + geoPoints: mapDTO!.points!, + categories: mapDTO!.categories!, + provider: mapDTO!.mapProvider == null ? MapProvider.Google : mapDTO!.mapProvider!, + filteredPoints: (value) { + _geoPoints.value = value!; + }), + MarkerViewWidget(), + Positioned( + top: 35, + left: 10, + child: SizedBox( + width: 50, + height: 50, + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + decoration: BoxDecoration( + color: primaryColor, + shape: BoxShape.circle, + ), + child: const Icon(Icons.arrow_back, size: 23, color: Colors.white) + ), + ) + ), + ), + ] + /*floatingActionButton: FloatingActionButton.extended( + onPressed: _goToTheLake, + label: Text('To the lake!'), + icon: Icon(Icons.directions_boat), + ),*/ + ), + ); + } + + /*getByteIcon(String? source) async { + if(source != null) { + if(kIsWeb) { + Uint8List fileData = await http.readBytes(Uri.parse(source)); + selectedMarkerIcon = resizeImage(fileData, 40); + } else { + final ByteData imageData = await NetworkAssetBundle(Uri.parse(source)).load(""); + selectedMarkerIcon = await getBytesFromAsset(imageData, 50); + } + } else { + // default icon + final ByteData bytes = await rootBundle.load('assets/icons/marker.png'); + selectedMarkerIcon = await getBytesFromAsset(bytes, 25); + } + }*/ + + /*Uint8List resizeImage(Uint8List data, int width) { + Uint8List resizedData = data; + IMG.Image img = IMG.decodeImage(data)!; + IMG.Image resized = IMG.copyResize(img, width: width); + resizedData = Uint8List.fromList(IMG.encodeJpg(resized)); + return resizedData; + }*/ + +/*Future _goToTheLake() async { + final GoogleMapController controller = await _controller.future; + controller.animateCamera(CameraUpdate.newCameraPosition(_kLake)); + }*/ + +} + diff --git a/lib/Screens/Sections/Map/marker_view.dart b/lib/Screens/Sections/Map/marker_view.dart new file mode 100644 index 0000000..d0b768c --- /dev/null +++ b/lib/Screens/Sections/Map/marker_view.dart @@ -0,0 +1,692 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:mymuseum_visitapp/Components/loading_common.dart'; +import 'package:mymuseum_visitapp/Components/show_element_for_resource.dart'; +import 'package:mymuseum_visitapp/Helpers/ImageCustomProvider.dart'; +import 'package:mymuseum_visitapp/Models/visitContext.dart'; +import 'package:mymuseum_visitapp/app_context.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:provider/provider.dart'; +import 'package:html/parser.dart' show parse; +import 'package:qr_flutter/qr_flutter.dart'; + + +import '../../../constants.dart'; +import 'map_context.dart'; + +class MarkerViewWidget extends StatefulWidget { + MarkerViewWidget(); + + @override + _MarkerInfoWidget createState() => _MarkerInfoWidget(); +} + +class _MarkerInfoWidget extends State { + CarouselSliderController? sliderController; + ValueNotifier currentIndex = ValueNotifier(1); + + @override + void initState() { + sliderController = CarouselSliderController(); + super.initState(); + } + + @override + void dispose() { + sliderController = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + int breakPointPrice = 50; + + Size size = MediaQuery.of(context).size; + final mapContext = Provider.of(context); + final appContext = Provider.of(context); + VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; + var language = visitAppContext.language; + GeoPointDTO? selectedPoint = mapContext.getSelectedPoint() as GeoPointDTO?; + + ScrollController scrollDescription = new ScrollController(); + ScrollController scrollPrice = new ScrollController(); + + var isPointPrice = selectedPoint != null && selectedPoint.prices != null && selectedPoint.prices!.isNotEmpty && selectedPoint.prices!.any((d) => d.language == language) && selectedPoint.prices!.firstWhere((d) => d.language == language).value != null && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.trim().length > 0; + + Color primaryColor = new Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)); + + Size sizeMarker = Size(size.width * 0.93, size.height * 0.83); + return Center( + child: Visibility( + visible: selectedPoint != null, + child: Container( + width: sizeMarker.width, + height: sizeMarker.height, + margin: EdgeInsets.symmetric(vertical: 3, horizontal: 4), + decoration: BoxDecoration( + color: kBackgroundColor, // Colors.amberAccent //kBackgroundLight, + //shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 10.0), + /*boxShadow: [ + BoxShadow( + color: kBackgroundSecondGrey, + spreadRadius: 0.5, + blurRadius: 1.1, + offset: Offset(0, 1.1), // changes position of shadow + ), + ],*/ + ), + child: Stack( + children: [ + Positioned( + right: 5, + top: 5, + child: InkWell( + onTap: () { + setState(() { + mapContext.setSelectedPoint(null); + mapContext.setSelectedPointForNavigate(null); + }); + }, + child: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: kBackgroundGrey, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: kBackgroundGrey, + spreadRadius: 0.5, + blurRadius: 1.1, + offset: Offset(0, 1.1), // changes position of shadow + ), + ], + ), + child: Icon( + Icons.close, + size: 25, + color: Colors.white, + ), + ), + ), + ), + if(selectedPoint != null) + Row( + children: [ + selectedPoint.imageResourceId != null && selectedPoint.imageUrl != null ? ClipRRect( + borderRadius: BorderRadius.only(topLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), bottomLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)), + child: Container( + child: Center( + child: Container( + decoration: BoxDecoration( + border: Border(right: BorderSide(width: 0.05, color: kMainGrey)), + color: Colors.grey, + ), + width: size.width * 0.17, + height: size.height, + child: CachedNetworkImage( + imageUrl: selectedPoint.imageUrl!, + width: size.width, + fit: BoxFit.cover, + progressIndicatorBuilder: (context, url, downloadProgress) { + return Center( + child: SizedBox( + width: 50, + height: 50, + child: LoadingCommon(), + ), + ); + }, + errorWidget: (context, url, error) => Icon(Icons.error), + ), + ), + ) + ), + ): SizedBox(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 8.0, left: 15.0), + child: Container( + //color: Colors.green, + height: size.height * 0.06, + width: size.width * 0.65, + child: HtmlWidget( + selectedPoint.title!.firstWhere((t) => t.language == language).value!, + textStyle: TextStyle(fontSize: 20.0), + ), + ), + ), + Row( + //mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 15), + child: Column( + children: [ + Container( + height: isPointPrice && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.length > breakPointPrice ? size.height * 0.50 : size.height * 0.7, + width: size.width * 0.38, + decoration: BoxDecoration( + color: kBackgroundLight, + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)) + ), + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 10, bottom: 10, top: 15), + child: Scrollbar( + controller: scrollDescription, + thumbVisibility: true, + thickness: 2.0, + child: SingleChildScrollView( + controller: scrollDescription, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if(selectedPoint.description!.any((d) => d.language == language) && selectedPoint.description!.firstWhere((d) => d.language == language).value != null && selectedPoint.description!.firstWhere((d) => d.language == language).value!.trim().length > 0) + HtmlWidget( + selectedPoint.description!.firstWhere((d) => d.language == language).value!, + customStylesBuilder: (element) { + return {'text-align': 'left', 'font-family': "Roboto"}; + }, + textStyle: TextStyle(fontSize: kDescriptionSize), + ), + ], + ), + ), + ), + ), + ), + ), + if(isPointPrice && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.length > breakPointPrice) + Container( + height: size.height * 0.20, + width: size.width * 0.38, + decoration: BoxDecoration( + color: kBackgroundLight, + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)) + ), + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 10, bottom: 10, top: 15), + child: Scrollbar( + controller: scrollPrice, + thumbVisibility: true, + thickness: 2.0, + child: SingleChildScrollView( + controller: scrollPrice, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + //crossAxisAlignment: CrossAxisAlignment.center, + //mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Center(child: Padding( + padding: const EdgeInsets.all(5.0), + child: Icon(Icons.price_change_outlined, color: primaryColor, size: 25), + )), + HtmlWidget( + selectedPoint.prices!.firstWhere((d) => d.language == language).value!, + customStylesBuilder: (element) { + return {'text-align': 'left', 'font-family': "Roboto"}; + }, + textStyle: TextStyle(fontSize: kDescriptionSize), + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + ), + SizedBox( + width: size.width * 0.32, + height: size.height * 0.7, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + //color: Colors.green, + height: size.height * 0.35, + child: CarouselSlider( + carouselController: sliderController, + options: CarouselOptions( + onPageChanged: (int index, CarouselPageChangedReason reason) { + currentIndex.value = index + 1; + }, + height: size.height *0.33, + enlargeCenterPage: true, + pageSnapping: true, + reverse: false, + ), + items: selectedPoint.contents!.map((ContentDTO i) { + return Builder( + builder: (BuildContext context) { + AppContext appContext = Provider.of(context); + var resourcetoShow = getElementForResource(context, appContext, i, true); + return resourcetoShow; + }, + ); + }).toList(), + ), + ), + Container( + //color: Colors.yellow, + height: size.height * 0.33, + child: Padding( + padding: const EdgeInsets.only(top: 10.0), + child: Column( + children: [ + /*if(isPointPrice && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.length > breakPointPrice) + Container( + height: size.height * 0.1, + width: size.width * 0.38, + decoration: BoxDecoration( + color: kBackgroundLight, + borderRadius: BorderRadius.all(Radius.circular(tabletAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)) + ), + child: Padding( + padding: EdgeInsets.all(5), + child: Scrollbar( + thumbVisibility: true, + thickness: 2.0, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(5.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + // todo add width ? + Padding( + padding: const EdgeInsets.all(5.0), + child: Icon(Icons.price_change_outlined, color: primaryColor, size: 13), + ), + HtmlWidget( + selectedPoint.prices!.firstWhere((d) => d.language == language).value!, + customStylesBuilder: (element) { + return {'text-align': 'left', 'font-family': "Roboto"}; + }, + textStyle: TextStyle(fontSize: 12), + ), + ], + ), + ), + ), + ), + ), + ),*/ + if(isPointPrice && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.length <= breakPointPrice) + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.price_change_outlined, color: primaryColor, size: 13), + Padding( + padding: const EdgeInsets.all(4.0), + child: HtmlWidget( + selectedPoint.prices!.firstWhere((d) => d.language == language).value!, + customStylesBuilder: (element) { + return {'text-align': 'left', 'font-family': "Roboto"}; + }, + textStyle: TextStyle(fontSize: 12), + ), + ) + ], + ), + selectedPoint.phone != null && selectedPoint.phone!.isNotEmpty && selectedPoint.phone!.any((d) => d.language == language) && selectedPoint.phone!.firstWhere((d) => d.language == language).value != null && selectedPoint.phone!.firstWhere((d) => d.language == language).value!.trim().length > 0 ? Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.phone, color: primaryColor, size: 13), + Padding( + padding: const EdgeInsets.all(4.0), + child: Text(parse(selectedPoint.phone!.firstWhere((p) => p.language == language).value!).documentElement!.text, style: TextStyle(fontSize: 12)), + ) + ], + ): SizedBox(), + selectedPoint.email != null && selectedPoint.email!.isNotEmpty && selectedPoint.email!.any((d) => d.language == language) && selectedPoint.email!.firstWhere((d) => d.language == language).value != null && selectedPoint.email!.firstWhere((d) => d.language == language).value!.trim().length > 0 ? Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.email, color: primaryColor, size: 13), + Padding( + padding: const EdgeInsets.all(4.0), + child: SizedBox( + width: size.width*0.25, + child: AutoSizeText(parse(selectedPoint.email!.firstWhere((p) => p.language == language).value!).documentElement!.text, style: TextStyle(fontSize: 12), maxLines: 3) + ), + ) + ], + ): SizedBox(), + selectedPoint.site != null && selectedPoint.site!.isNotEmpty && selectedPoint.site!.any((d) => d.language == language) && selectedPoint.site!.firstWhere((d) => d.language == language).value != null && selectedPoint.site!.firstWhere((d) => d.language == language).value!.trim().length > 0 ? Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon(Icons.public, color: primaryColor, size: 13), + Padding( + padding: const EdgeInsets.all(4.0), + child: SizedBox( + width: size.width*0.25, + child: AutoSizeText(parse(selectedPoint.site!.firstWhere((p) => p.language == language).value!).documentElement!.text, style: TextStyle(fontSize: 12), maxLines: 3) + ), + ) + ], + ): SizedBox(), + selectedPoint.site != null && selectedPoint.site!.isNotEmpty && selectedPoint.site!.any((d) => d.language == language) && selectedPoint.site!.firstWhere((d) => d.language == language).value != null && selectedPoint.site!.firstWhere((d) => d.language == language).value!.trim().length > 0 ? Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: size.width *0.1, + height: 120, + child: QrImageView( + padding: EdgeInsets.only(left: 5.0, top: 5.0, bottom: 5.0, right: 5.0), + data: parse(selectedPoint.site!.firstWhere((p) => p.language == language).value!).documentElement!.text, + version: QrVersions.auto, + size: 50.0, + ), + ), + ), + ), + ): SizedBox(), + ], + ), + ), + ), + ], + ), + ), + ) + ], + ) + ], + ), + ], + ), + /*Align( + alignment: Alignment.topCenter, + child: Padding( + padding: const EdgeInsets.only(top: 20), + child: HtmlWidget( + (mapContext.getSelectedMarker() as MapMarker).title!, + customStylesBuilder: (element) { + return {'text-align': 'center'}; + }, + textStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: kIsWeb ? kWebTitleSize : kTitleSize) + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 75), + child: Center( + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if((mapContext.getSelectedMarker() as MapMarker).contents != null && (mapContext.getSelectedMarker() as MapMarker).contents!.length > 0) + Stack( + children: [ + Container( + //color: Colors.green, + child: CarouselSlider( + carouselController: sliderController, + options: CarouselOptions( + onPageChanged: (int index, CarouselPageChangedReason reason) { + currentIndex.value = index + 1; + }, + height: size.height *0.35, + enlargeCenterPage: true, + pageSnapping: true, + reverse: false, + ), + items: (mapContext.getSelectedMarker() as MapMarker).contents!.map((ContentGeoPoint i) { + return Builder( + builder: (BuildContext context) { + AppContext appContext = Provider.of(context); + var resourcetoShow = getElementForResource(context, appContext, i); + return Padding( + padding: const EdgeInsets.all(8.0), + child: resourcetoShow, + ); + }, + ); + }).toList(), + ), + ), + Positioned( + bottom: 0, + child: Container( + //color: Colors.red, + height: 25, + width: sizeMarker.width, + //color: Colors.amber, + child: Padding( + padding: const EdgeInsets.only(top: 0), + child: Align( + alignment: Alignment.bottomCenter, + child: ValueListenableBuilder( + valueListenable: currentIndex, + builder: (context, value, _) { + return Text( + value.toString()+'/'+(mapContext.getSelectedMarker() as MapMarker).contents!.length.toString(), + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ); + } + ) + ), + ), + ), + ), + ], + ), + // Description + Container( + height: (mapContext.getSelectedMarker() as MapMarker).contents != null && (mapContext.getSelectedMarker() as MapMarker).contents!.length > 0 ? size.height *0.3 : size.height *0.6, + width: MediaQuery.of(context).size.width *0.4, + decoration: BoxDecoration( + color: kBackgroundColor, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(tabletAppContext.configuration!.roundedValue?.toDouble() ?? 10.0), + boxShadow: [ + BoxShadow( + color: kBackgroundSecondGrey, + spreadRadius: 0.5, + blurRadius: 1.1, + offset: Offset(0, 1.1), // changes position of shadow + ), + ], + ), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: HtmlWidget( + (mapContext.getSelectedMarker() as MapMarker).description!, + customStylesBuilder: (element) { + return {'text-align': 'center'}; + }, + textStyle: TextStyle(fontSize: kIsWeb ? kWebDescriptionSize : kDescriptionSize) + ), + ), + ), + ), + ] + ), + if((mapContext.getSelectedMarker() as MapMarker).contents != null && (mapContext.getSelectedMarker() as MapMarker).contents!.length > 1) + Positioned( + top: MediaQuery.of(context).size.height * 0.125, + right: -10, + child: InkWell( + onTap: () { + if ((mapContext.getSelectedMarker() as MapMarker).contents!.length > 0) + sliderController!.nextPage(duration: new Duration(milliseconds: 500), curve: Curves.fastOutSlowIn); + }, + child: Icon( + Icons.chevron_right, + size: kIsWeb ? 100 : 85, + color: kTestSecondColor, + ), + ) + ), + if((mapContext.getSelectedMarker() as MapMarker).contents != null && (mapContext.getSelectedMarker() as MapMarker).contents!.length > 1) + Positioned( + top: MediaQuery.of(context).size.height * 0.125, + left: -10, + child: InkWell( + onTap: () { + if ((mapContext.getSelectedMarker() as MapMarker).contents!.length > 0) + sliderController!.previousPage(duration: new Duration(milliseconds: 500), curve: Curves.fastOutSlowIn); + }, + child: Icon( + Icons.chevron_left, + size: kIsWeb ? 100 : 85, + color: kTestSecondColor, + ), + ) + ), + ], + ), + ), + ),*/ + ]) + ), + ), + ); + } +} + +getElementForResource(BuildContext context, AppContext appContext, ContentDTO i, bool addFullScreen) { + var widgetToInclude; + Size size = MediaQuery.of(context).size; + VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; + + switch(i.resource?.type) { + case ResourceType.Image: + case ResourceType.ImageUrl: + widgetToInclude = GestureDetector( + onTap: () { + if(addFullScreen) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)) + ), + contentPadding: EdgeInsets.zero, + // title: Text(eventAgenda.name!), + content: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)), + color: kBackgroundColor, + ), + height: size.height * 0.8, + width: size.width * 0.8, + child: Container( + decoration: BoxDecoration( + //color: Colors.yellow, + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0)), + ), + child: PhotoView( + imageProvider: ImageCustomProvider.getImageProvider(appContext, i.resourceId!, i.resource!.url!), + minScale: PhotoViewComputedScale.contained * 0.8, + maxScale: PhotoViewComputedScale.contained * 3.0, + backgroundDecoration: BoxDecoration( + color: kBackgroundGrey, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0), + ), + ), + ), + ), + ); + }, + ); + } + }, + child: Container( + decoration: BoxDecoration( + //color: kBackgroundLight, + image: DecorationImage( + image: ImageCustomProvider.getImageProvider(appContext, i.resourceId!, i.resource!.url!), + fit: BoxFit.cover, + ), + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0)), + /*border: Border.all( + color: kBackgroundGrey, + width: 1.0, + ),*/ + ), + ), + ); + break; + case ResourceType.Video: + case ResourceType.VideoUrl: + case ResourceType.Audio: + widgetToInclude = GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + if(addFullScreen && i.resource!.type != ResourceType.Audio) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)) + ), + contentPadding: EdgeInsets.zero, + // title: Text(eventAgenda.name!), + content: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)), + color: kBackgroundColor, + ), + height: size.height * 0.8, + width: size.width * 0.8, + child: Center(child: showElementForResource(ResourceDTO(id: i.resourceId, url: i.resource?.url, type: i.resource?.type), appContext, false, true)), + ), + ); + }, + ); + } + }, + child: IgnorePointer( + ignoring: i.resource!.type != ResourceType.Audio, + child: Container( + decoration: BoxDecoration( + color: Colors.yellow, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0), + ), + child: showElementForResource(ResourceDTO(id: i.resourceId, url: i.resource?.url, type: i.resource?.type), appContext, false, true), + ), + ), + ); + break; + } + + return Center( + child: Container( + height: MediaQuery.of(context).size.height * 0.6, + width: MediaQuery.of(context).size.width * 0.72, + child: AspectRatio( + aspectRatio: 16 / 9, + child: ClipRect( + child: widgetToInclude, + ), + ), + ), + ); +} diff --git a/lib/Screens/Sections/Map/tree_node.dart b/lib/Screens/Sections/Map/tree_node.dart new file mode 100644 index 0000000..c8a4672 --- /dev/null +++ b/lib/Screens/Sections/Map/tree_node.dart @@ -0,0 +1,42 @@ +class TreeNode { + bool checked; + bool show; + int id; + int pid; + int commonID; + String title; + List children; + + TreeNode({ + required this.checked, + required this.show, + required this.id, + required this.pid, + required this.commonID, + required this.title, + required this.children, + }); + + factory TreeNode.fromJson(Map json) { + return TreeNode( + checked: json['checked'] ?? false, + show: json['show'] ?? false, + id: json['id'] ?? 0, + pid: json['pid'] ?? 0, + commonID: json['commonID'] ?? 0, + title: json['title'] ?? '', + children: (json['children'] as List) + .map((childJson) => TreeNode.fromJson(childJson)) + .toList(), + ); + } + + List getAllChildrenTitles() { + List id = []; + for (var child in children) { + id.add(child.id); + id.addAll(child.getAllChildrenTitles()); + } + return id; + } +} diff --git a/lib/Screens/Sections/PDF/pdf_view.dart b/lib/Screens/Sections/PDF/pdf_page.dart similarity index 100% rename from lib/Screens/Sections/PDF/pdf_view.dart rename to lib/Screens/Sections/PDF/pdf_page.dart diff --git a/lib/Screens/Sections/Slider/slider_view.dart b/lib/Screens/Sections/Slider/slider_page.dart similarity index 58% rename from lib/Screens/Sections/Slider/slider_view.dart rename to lib/Screens/Sections/Slider/slider_page.dart index 2fdf16b..b9cd260 100644 --- a/lib/Screens/Sections/Slider/slider_view.dart +++ b/lib/Screens/Sections/Slider/slider_page.dart @@ -55,117 +55,125 @@ class _SliderPage extends State { return Stack( children: [ + Container( + height: size.height, + width: size.width, + color: kBackgroundLight, + ), if(sliderDTO.contents != null && sliderDTO.contents!.isNotEmpty) - CarouselSlider( - carouselController: sliderController, - options: CarouselOptions( - onPageChanged: (int index, CarouselPageChangedReason reason) { - currentIndex.value = index + 1; - }, - height: MediaQuery.of(context).size.height * 1.0, - enlargeCenterPage: false, - reverse: false, - ), - items: sliderDTO.contents!.map((i) { - return Builder( - builder: (BuildContext context) { - return Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - margin: const EdgeInsets.symmetric(horizontal: 5.0), - decoration: BoxDecoration( - color: Colors.green, - //color: configurationDTO.imageId == null ? configurationDTO.secondaryColor != null ? new Color(int.parse(configurationDTO.secondaryColor!.split('(0x')[1].split(')')[0], radix: 16)): kBackgroundGrey : null, - borderRadius: BorderRadius.circular(visitAppContex.configuration!.roundedValue?.toDouble() ?? 10.0), - //border: Border.all(width: 0.3, color: kSecondGrey), - ), - child: Column( - //crossAxisAlignment: CrossAxisAlignment.center, - //mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Padding( - padding: const EdgeInsets.all(10.0), - child: Container( - color: Colors.orange, - height: MediaQuery.of(context).size.height * 0.6, - width: MediaQuery.of(context).size.width * 0.72, - /*decoration: BoxDecoration( - color: kBackgroundLight, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(20.0), - /*image: i.source_ != null ? new DecorationImage( - fit: BoxFit.cover, - image: new NetworkImage( - i.source_, - ), - ): null,*/ - boxShadow: [ - BoxShadow( - color: kBackgroundSecondGrey, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ),*/ - child: Stack( - children: [ - getElementForResource(appContext, i), - Positioned( - bottom: 0, - right: 0, - child: Padding( - padding: const EdgeInsets.all(15.0), - child: HtmlWidget( - i.title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.title!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", - textStyle: const TextStyle(fontSize: kTitleSize, color: kBackgroundLight), - ), - ) - ) - ] - ),/**/ - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(bottom: 10), + Center( + child: CarouselSlider( + carouselController: sliderController, + options: CarouselOptions( + onPageChanged: (int index, CarouselPageChangedReason reason) { + currentIndex.value = index + 1; + }, + enableInfiniteScroll: false, + height: MediaQuery.of(context).size.height * 0.92, + enlargeCenterPage: false, + reverse: false, + ), + items: sliderDTO.contents!.map((i) { + return Builder( + builder: (BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + margin: const EdgeInsets.symmetric(horizontal: 5.0), + decoration: BoxDecoration( + color: kBackgroundGrey, + //color: configurationDTO.imageId == null ? configurationDTO.secondaryColor != null ? new Color(int.parse(configurationDTO.secondaryColor!.split('(0x')[1].split(')')[0], radix: 16)): kBackgroundGrey : null, + borderRadius: BorderRadius.circular(visitAppContex.configuration!.roundedValue?.toDouble() ?? 10.0), + //border: Border.all(width: 0.3, color: kSecondGrey), + ), + child: Column( + //crossAxisAlignment: CrossAxisAlignment.center, + //mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Padding( + padding: const EdgeInsets.all(10.0), child: Container( - height: MediaQuery.of(context).size.height *0.25, - width: MediaQuery.of(context).size.width *0.7, - decoration: BoxDecoration( - color: Colors.blue,// kBackgroundLight, + //color: Colors.orange, + height: MediaQuery.of(context).size.height * 0.6, + width: MediaQuery.of(context).size.width * 0.72, + /*decoration: BoxDecoration( + color: kBackgroundLight, shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(visitAppContex.configuration!.roundedValue?.toDouble() ?? 10.0), - boxShadow: const [ + borderRadius: BorderRadius.circular(20.0), + /*image: i.source_ != null ? new DecorationImage( + fit: BoxFit.cover, + image: new NetworkImage( + i.source_, + ), + ): null,*/ + boxShadow: [ BoxShadow( color: kBackgroundSecondGrey, - spreadRadius: 0.3, - blurRadius: 4, - offset: Offset(0, 2), // changes position of shadow + spreadRadius: 0.5, + blurRadius: 5, + offset: Offset(0, 1.5), // changes position of shadow ), ], - ), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(15.0), - child: HtmlWidget( - i.description!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", - textStyle: const TextStyle(fontSize: kDescriptionSize), - customStylesBuilder: (element) { - return {'text-align': 'center', 'font-family': "Roboto"}; - }, + ),*/ + child: Stack( + children: [ + getElementForResource(appContext, i), + Positioned( + bottom: 0, + right: 0, + child: Padding( + padding: const EdgeInsets.all(15.0), + child: HtmlWidget( + i.title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.title!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", + textStyle: const TextStyle(fontSize: kTitleSize, color: kBackgroundLight), + ), + ) + ) + ] + ),/**/ + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Container( + height: MediaQuery.of(context).size.height *0.25, + width: MediaQuery.of(context).size.width *0.7, + decoration: BoxDecoration( + color: kBackgroundLight,// kBackgroundLight, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(visitAppContex.configuration!.roundedValue?.toDouble() ?? 10.0), + boxShadow: const [ + BoxShadow( + color: kBackgroundSecondGrey, + spreadRadius: 0.3, + blurRadius: 4, + offset: Offset(0, 2), // changes position of shadow + ), + ], + ), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: HtmlWidget( + i.description!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "", + textStyle: const TextStyle(fontSize: kDescriptionSize), + customStylesBuilder: (element) { + return {'text-align': 'center', 'font-family': "Roboto"}; + }, + ), ), ), ), ), ), - ), - ], - ) - ); - }, - ); - }).toList(), + ], + ) + ); + }, + ); + }).toList(), + ), ), /*if(sliderDTO.contents != null && sliderDTO.contents!.length > 1) Positioned( @@ -214,7 +222,7 @@ class _SliderPage extends State { return AnimatedSmoothIndicator( activeIndex: value -1, count: sliderDTO.contents!.length, - effect: const ExpandingDotsEffect(activeDotColor: kMainColor), + effect: ExpandingDotsEffect(activeDotColor: primaryColor!), ); /*Text( @@ -287,8 +295,16 @@ class _SliderPage extends State { minScale: PhotoViewComputedScale.contained * 0.8, maxScale: PhotoViewComputedScale.contained * 3.0, backgroundDecoration: BoxDecoration( - color: kBackgroundSecondGrey, + color: kBackgroundLight, shape: BoxShape.rectangle, + boxShadow: const [ + BoxShadow( + color: kBackgroundGrey, + spreadRadius: 0.3, + blurRadius: 4, + offset: Offset(0, 2), // changes position of shadow + ), + ], borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0), ), ); @@ -299,8 +315,16 @@ class _SliderPage extends State { minScale: PhotoViewComputedScale.contained * 0.8, maxScale: PhotoViewComputedScale.contained * 3.0, backgroundDecoration: BoxDecoration( - color: kBackgroundSecondGrey, + color: kBackgroundLight, shape: BoxShape.rectangle, + boxShadow: const [ + BoxShadow( + color: kBackgroundGrey, + spreadRadius: 0.3, + blurRadius: 4, + offset: Offset(0, 2), // changes position of shadow + ), + ], borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0), ), ); @@ -310,7 +334,7 @@ class _SliderPage extends State { case ResourceType.Audio: widgetToInclude = Container( decoration: BoxDecoration( - //color: kBackgroundSecondGrey, + color: kBackgroundLight, //shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0), ), @@ -323,7 +347,6 @@ class _SliderPage extends State { child: Container( height: MediaQuery.of(context).size.height * 0.6, width: MediaQuery.of(context).size.width * 0.72, - color: Colors.yellow, child: AspectRatio( aspectRatio: 16 / 9, child: ClipRect( diff --git a/lib/Screens/Visit/components/body.dart b/lib/Screens/Visit/components/body.dart index a307835..bae26e9 100644 --- a/lib/Screens/Visit/components/body.dart +++ b/lib/Screens/Visit/components/body.dart @@ -256,6 +256,7 @@ class _BodyState extends State { rawSections = jsonDecode(jsonEncode(sectionsDownloaded)); var rawToSection = jsonDecode(jsonEncode(rawSections)).map((json) => SectionDTO.fromJson(json)).toList(); List sectionList = rawToSection.whereType().toList(); + visitAppContext.currentSections = rawSections; //print(sectionsDownloaded); if(sectionList.isNotEmpty) { diff --git a/lib/Screens/Visit/visit.dart b/lib/Screens/Visit/visit.dart index 32ca32d..a50eb72 100644 --- a/lib/Screens/Visit/visit.dart +++ b/lib/Screens/Visit/visit.dart @@ -78,6 +78,7 @@ class _VisitPageState extends State with WidgetsBindingObserver { final appContext = Provider.of(context, listen: false); VisitAppContext visitAppContext = appContext.getContext(); visitAppContext.configuration = widget.configuration; + visitAppContext.sectionIds = widget.configuration.sectionIds; appContext.setContext(visitAppContext); }); //listeningState(); diff --git a/lib/Screens/section_page.dart b/lib/Screens/section_page.dart index 3f8f454..bd83f6f 100644 --- a/lib/Screens/section_page.dart +++ b/lib/Screens/section_page.dart @@ -1,10 +1,11 @@ import 'dart:convert'; -import 'dart:developer'; +import 'dart:io'; import 'dart:typed_data'; //import 'package:confetti/confetti.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:manager_api_new/api.dart'; import 'package:mymuseum_visitapp/Components/CustomAppBar.dart'; import 'package:mymuseum_visitapp/Components/loading_common.dart'; @@ -14,16 +15,21 @@ import 'package:mymuseum_visitapp/Models/articleRead.dart'; import 'package:mymuseum_visitapp/Models/resourceModel.dart'; import 'package:mymuseum_visitapp/Models/visitContext.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.dart'; -import 'package:mymuseum_visitapp/Screens/Sections/PDF/pdf_view.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/map_context.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Map/map_page.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/PDF/pdf_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Puzzle/puzzle_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Quiz/quizz_page.dart'; -import 'package:mymuseum_visitapp/Screens/Sections/Slider/slider_view.dart'; +import 'package:mymuseum_visitapp/Screens/Sections/Slider/slider_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Video/video_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Web/web_page.dart'; import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/client.dart'; -import 'package:mymuseum_visitapp/constants.dart'; import 'package:provider/provider.dart'; +import 'package:http/http.dart' as http; +import 'package:image/image.dart' as IMG; +import 'dart:ui' as ui; +import 'package:path_provider/path_provider.dart'; class SectionPage extends StatefulWidget { const SectionPage({Key? key, required this.rawSection, required this.visitAppContextIn, required this.configuration, required this.sectionId}) : super(key: key); @@ -44,6 +50,9 @@ class _SectionPageState extends State { late dynamic rawSectionData; List resourcesModel = []; + late final MapContext mapContext = MapContext(null); + List>? icons; + @override void initState() { widget.visitAppContextIn.isContentCurrentlyShown = true; @@ -66,48 +75,59 @@ class _SectionPageState extends State { return Scaffold( key: _scaffoldKey, - appBar: test!.type != SectionType.Quiz && test.type != SectionType.Article && test.type != SectionType.Web && test.type != SectionType.Pdf && test.type != SectionType.Video && test.type != SectionType.Puzzle && test.type != SectionType.Slider ? CustomAppBar( + resizeToAvoidBottomInset: false, + appBar: test!.type == SectionType.Menu || test.type == SectionType.Agenda || test.type == SectionType.Weather ? CustomAppBar( title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "", isHomeButton: false, ) : null, - body: OrientationBuilder( - builder: (context, orientation) { - return FutureBuilder( - future: getSectionDetail(appContext, visitAppContext.clientAPI, widget.sectionId), - builder: (context, AsyncSnapshot snapshot) { - var sectionResult = snapshot.data; - if(sectionDTO != null && sectionResult != null) { - switch(sectionDTO!.type) { - case SectionType.Article: - ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!; - return ArticlePage(visitAppContextIn: widget.visitAppContextIn, articleDTO: articleDTO, resourcesModel: resourcesModel); - case SectionType.Quiz: - QuizDTO quizDTO = QuizDTO.fromJson(sectionResult)!; - return QuizPage(visitAppContextIn: widget.visitAppContextIn, quizDTO: quizDTO, resourcesModel: resourcesModel); - case SectionType.Web: - WebDTO webDTO = WebDTO.fromJson(sectionResult)!; - return WebPage(section: webDTO); - case SectionType.Pdf: - PdfDTO pdfDTO = PdfDTO.fromJson(sectionResult)!; - return PDFPage(section: pdfDTO); - case SectionType.Video: - VideoDTO videoDTO = VideoDTO.fromJson(sectionResult)!; - return VideoPage(section: videoDTO); - case SectionType.Puzzle: - PuzzleDTO puzzleDTO = PuzzleDTO.fromJson(sectionResult)!; - return PuzzlePage(section: puzzleDTO); - case SectionType.Slider: - SliderDTO sliderDTO = SliderDTO.fromJson(sectionResult)!; - return SliderPage(section: sliderDTO); - default: - return const Center(child: Text("Unsupported type")); + body: MediaQuery.removeViewInsets( + context: context, + removeBottom: true, + child: OrientationBuilder( + builder: (context, orientation) { + return FutureBuilder( + future: getSectionDetail(appContext, visitAppContext.clientAPI, widget.sectionId), + builder: (context, AsyncSnapshot snapshot) { + var sectionResult = snapshot.data; + if(sectionDTO != null && sectionResult != null) { + switch(sectionDTO!.type) { + case SectionType.Article: + ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!; + return ArticlePage(visitAppContextIn: widget.visitAppContextIn, articleDTO: articleDTO, resourcesModel: resourcesModel); + case SectionType.Quiz: + QuizDTO quizDTO = QuizDTO.fromJson(sectionResult)!; + return QuizPage(visitAppContextIn: widget.visitAppContextIn, quizDTO: quizDTO, resourcesModel: resourcesModel); + case SectionType.Web: + WebDTO webDTO = WebDTO.fromJson(sectionResult)!; + return WebPage(section: webDTO); + case SectionType.Pdf: + PdfDTO pdfDTO = PdfDTO.fromJson(sectionResult)!; + return PDFPage(section: pdfDTO); + case SectionType.Video: + VideoDTO videoDTO = VideoDTO.fromJson(sectionResult)!; + return VideoPage(section: videoDTO); + case SectionType.Puzzle: + PuzzleDTO puzzleDTO = PuzzleDTO.fromJson(sectionResult)!; + return PuzzlePage(section: puzzleDTO); + case SectionType.Slider: + SliderDTO sliderDTO = SliderDTO.fromJson(sectionResult)!; + return SliderPage(section: sliderDTO); + case SectionType.Map: + MapDTO mapDTO = MapDTO.fromJson(sectionResult)!; + return ChangeNotifierProvider.value( + value: mapContext, + child: MapPage(section: mapDTO, icons: icons ?? []), + ); + default: + return const Center(child: Text("Unsupported type")); + } + } else { + return const LoadingCommon(); } - } else { - return const LoadingCommon(); } - } - ); - } + ); + } + ), ) ); } @@ -219,6 +239,10 @@ class _SectionPageState extends State { } } break; + case SectionType.Map: + MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!; + icons = await getByteIcons(visitAppContext, mapDTO); + break; default: break; } @@ -232,3 +256,86 @@ class _SectionPageState extends State { } } } + +Future getBytesFromAsset(ByteData data, int width) async { + //ByteData data = await rootBundle.load(path); + ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width); + ui.FrameInfo fi = await codec.getNextFrame(); + return (await fi.image.toByteData(format: ui.ImageByteFormat.png))!.buffer.asUint8List(); +} + +Uint8List resizeImage(Uint8List data, int width) { + Uint8List resizedData = data; + IMG.Image img = IMG.decodeImage(data)!; + IMG.Image resized = IMG.copyResize(img, width: width); + resizedData = Uint8List.fromList(IMG.encodeJpg(resized)); + return resizedData; +} + +Future _checkIfLocalResourceExists(VisitAppContext visitAppContext, String iconId) async { + try { + Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory(); + String localPath = appDocumentsDirectory!.path; + Directory configurationDirectory = Directory('$localPath/${visitAppContext.configuration!.id}'); + List fileList = configurationDirectory.listSync(); + + if(fileList.any((fileL) => fileL.uri.pathSegments.last.contains(iconId))) { + File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(iconId)).path); + return file; + } + } catch(e) { + print("ERROR _checkIfLocalResourceExists CachedCustomResource"); + print(e); + } + return null; +} + +Future>> getByteIcons(VisitAppContext visitAppContext, MapDTO mapDTO) async { + //var mapDTO = MapDTO.fromJson(jsonDecode(section.data!)); + Uint8List selectedMarkerIcon; + if (mapDTO.iconSource != null) { + if (kIsWeb) { + Uint8List fileData = await http.readBytes(Uri.parse(mapDTO.iconSource!)); + selectedMarkerIcon = resizeImage(fileData, 40); + } else { + File? localIcon = await _checkIfLocalResourceExists(visitAppContext, mapDTO.iconResourceId!); + if(localIcon == null) { + final ByteData imageData = await NetworkAssetBundle(Uri.parse(mapDTO.iconSource!)).load(""); + selectedMarkerIcon = await getBytesFromAsset(imageData, 50); + } else { + Uint8List bytes = await localIcon.readAsBytes(); + selectedMarkerIcon = await getBytesFromAsset(ByteData.view(bytes.buffer), 50); + } + } + } else { + // Icône par défaut + final ByteData bytes = await rootBundle.load('assets/icons/marker.png'); + selectedMarkerIcon = await getBytesFromAsset(bytes, 25); + } + + List> icons = []; + + icons.add({'id': null, 'icon': selectedMarkerIcon}); + + // Utiliser Future.forEach() pour itérer de manière asynchrone sur la liste des catégories + await Future.forEach(mapDTO.categories!, (cat) async { + if (cat.resourceDTO != null && cat.resourceDTO!.url != null && cat.resourceDTO!.id != null) { + Uint8List categoryIcon; + if (kIsWeb) { + categoryIcon = await http.readBytes(Uri.parse(cat.resourceDTO!.url!)); + } else { + File? localIcon = await _checkIfLocalResourceExists(visitAppContext, cat.resourceDTO!.id!); + if(localIcon == null) { + final ByteData imageData = await NetworkAssetBundle(Uri.parse(cat.resourceDTO!.url!)).load(""); + categoryIcon = await getBytesFromAsset(imageData, 50); + } else { + Uint8List bytes = await localIcon.readAsBytes(); + categoryIcon = await getBytesFromAsset(ByteData.view(bytes.buffer), 50); + } + } + icons.add({'id': cat.id, 'icon': categoryIcon}); + } + }); + + return icons; +} diff --git a/pubspec.lock b/pubspec.lock index ec169d5..a0aa5a4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,14 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.4.1" - ar_flutter_plugin: - dependency: "direct main" + archive: + dependency: transitive description: - name: ar_flutter_plugin - sha256: "52b6b2ccec4b624ca3fb8d7cc68128f11126580b412c3da2da82c41ddfd6d6ae" + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "4.0.7" args: dependency: transitive description: @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + benchmark: + dependency: transitive + description: + name: benchmark + sha256: cb3eeea01e3f054df76ee9775ca680f3afa5f19f39b2bb426ba78ba27654493b + url: "https://pub.dev" + source: hosted + version: "0.3.0" boolean_selector: dependency: transitive description: @@ -241,6 +249,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_earcut: + dependency: transitive + description: + name: dart_earcut + sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b + url: "https://pub.dev" + source: hosted + version: "1.2.0" + dart_sort_queue: + dependency: transitive + description: + name: dart_sort_queue + sha256: f3353ba8b4850e072d3368757f62edb79af34a9703c3e3df9c59342721f5f5b1 + url: "https://pub.dev" + source: hosted + version: "0.0.2+3" dart_style: dependency: transitive description: @@ -395,6 +419,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_map: + dependency: "direct main" + description: + name: flutter_map + sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da" + url: "https://pub.dev" + source: hosted + version: "7.0.2" flutter_pdfview: dependency: "direct main" description: @@ -403,6 +435,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0+1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf" + url: "https://pub.dev" + source: hosted + version: "2.0.26" flutter_staggered_grid_view: dependency: "direct main" description: @@ -509,54 +549,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.15.4" - geolocator: + geotypes: dependency: transitive description: - name: geolocator - sha256: "5c23f3613f50586c0bbb2b8f970240ae66b3bd992088cf60dd5ee2e6f7dde3a8" + name: geotypes + sha256: "5bedf57de92283133dd221e363812ef50eaaba414f0823b1974ef7d84b86991f" url: "https://pub.dev" source: hosted - version: "9.0.2" - geolocator_android: - dependency: transitive - description: - name: geolocator_android - sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47" - url: "https://pub.dev" - source: hosted - version: "4.6.1" - geolocator_apple: - dependency: transitive - description: - name: geolocator_apple - sha256: c4ecead17985ede9634f21500072edfcb3dba0ef7b97f8d7bc556d2d722b3ba3 - url: "https://pub.dev" - source: hosted - version: "2.3.9" - geolocator_platform_interface: - dependency: transitive - description: - name: geolocator_platform_interface - sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012" - url: "https://pub.dev" - source: hosted - version: "4.2.4" - geolocator_web: - dependency: transitive - description: - name: geolocator_web - sha256: "102e7da05b48ca6bf0a5bda0010f886b171d1a08059f01bfe02addd0175ebece" - url: "https://pub.dev" - source: hosted - version: "2.2.1" - geolocator_windows: - dependency: transitive - description: - name: geolocator_windows - sha256: "4f4218f122a6978d0ad655fa3541eea74c67417440b09f0657238810d5af6bdc" - url: "https://pub.dev" - source: hosted - version: "0.1.3" + version: "0.0.2" get: dependency: "direct main" description: @@ -573,6 +573,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "4d6e199c561ca06792c964fa24b2bac7197bf4b401c2e1d23e345e5f9939f531" + url: "https://pub.dev" + source: hosted + version: "8.1.1" + google_maps_flutter: + dependency: "direct main" + description: + name: google_maps_flutter + sha256: "621125e35e81ca39ef600e45243d2be93167e61def72bc7207b0c4a635c58506" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + google_maps_flutter_android: + dependency: transitive + description: + name: google_maps_flutter_android + sha256: "721ffae2240e957c04b0de19ffd4b68580adb57a8224496b7fb55fad23aec98a" + url: "https://pub.dev" + source: hosted + version: "2.14.13" + google_maps_flutter_ios: + dependency: transitive + description: + name: google_maps_flutter_ios + sha256: c7433645c4c9b61c587938cb06072f3dad601239e596b090c0f8f206c1f2ade7 + url: "https://pub.dev" + source: hosted + version: "2.15.2" + google_maps_flutter_platform_interface: + dependency: transitive + description: + name: google_maps_flutter_platform_interface + sha256: "970c8f766c02909c7be282dea923c971f83a88adaf07f8871d0aacebc3b07bb2" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + google_maps_flutter_web: + dependency: transitive + description: + name: google_maps_flutter_web + sha256: a45786ea6691cc7cdbe2cf3ce2c2daf4f82a885745666b4a36baada3a4e12897 + url: "https://pub.dev" + source: hosted + version: "0.5.12" graphs: dependency: transitive description: @@ -613,6 +661,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: "direct main" + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" intl: dependency: "direct main" description: @@ -677,6 +733,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.13" + latlong2: + dependency: transitive + description: + name: latlong2 + sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" + url: "https://pub.dev" + source: hosted + version: "0.9.1" leak_tracker: dependency: transitive description: @@ -709,6 +773,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + lists: + dependency: transitive + description: + name: lists + sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + logger: + dependency: transitive + description: + name: logger + sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 + url: "https://pub.dev" + source: hosted + version: "2.5.0" logging: dependency: transitive description: @@ -724,6 +804,14 @@ packages: relative: true source: path version: "1.0.0" + mapbox_maps_flutter: + dependency: "direct main" + description: + name: mapbox_maps_flutter + sha256: f3dea7e14e5afc10ad03e16a6d5ece18ec5487ceaf9b2f83a64a2358d29149ba + url: "https://pub.dev" + source: hosted + version: "2.8.0" matcher: dependency: transitive description: @@ -748,6 +836,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + mgrs_dart: + dependency: transitive + description: + name: mgrs_dart + sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7 + url: "https://pub.dev" + source: hosted + version: "2.0.0" mime: dependency: transitive description: @@ -756,6 +852,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + sha256: "827765afbd4792ff3fd105ad593821ac0f6d8a7d352689013b07ee85be336312" + url: "https://pub.dev" + source: hosted + version: "4.0.1" nested: dependency: transitive description: @@ -956,6 +1060,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + polylabel: + dependency: transitive + description: + name: polylabel + sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" pool: dependency: transitive description: @@ -964,6 +1076,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + proj4dart: + dependency: transitive + description: + name: proj4dart + sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e + url: "https://pub.dev" + source: hosted + version: "2.1.0" provider: dependency: "direct main" description: @@ -988,14 +1116,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - qr_code_scanner: - dependency: "direct main" + qr: + dependency: transitive description: - name: qr_code_scanner - sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "3.0.2" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + rbush: + dependency: transitive + description: + name: rbush + sha256: "48b683421b4afb43a642f82c6aa31911e54f3069143d31c7d33cbe329df13403" + url: "https://pub.dev" + source: hosted + version: "1.1.1" rxdart: dependency: transitive description: @@ -1004,6 +1148,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + sanitize_html: + dependency: transitive + description: + name: sanitize_html + sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989" + url: "https://pub.dev" + source: hosted + version: "2.1.0" shared_preferences: dependency: transitive description: @@ -1161,6 +1313,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + sweepline_intersections: + dependency: transitive + description: + name: sweepline_intersections + sha256: a665c707200a4f07140a4029b41a7c4883beb3f04322cd8e08ebf650f69e1176 + url: "https://pub.dev" + source: hosted + version: "0.0.4" synchronized: dependency: transitive description: @@ -1193,6 +1353,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + turf: + dependency: transitive + description: + name: turf + sha256: "75347c45a5c1de805db7cb182286f05a3770e01546626c4dc292709d15cbe436" + url: "https://pub.dev" + source: hosted + version: "0.0.10" + turf_equality: + dependency: transitive + description: + name: turf_equality + sha256: f0f44ffe389547941358e0d3d4a747db2bd56115b32ff1cede5e5bdf3126a3e2 + url: "https://pub.dev" + source: hosted + version: "0.1.0" + turf_pip: + dependency: transitive + description: + name: turf_pip + sha256: ba4fd414baffd5d7b30880658ad6db82461c49ec023f8ffd0c23d398ad8b14be + url: "https://pub.dev" + source: hosted + version: "0.0.2" typed_data: dependency: transitive description: @@ -1201,6 +1385,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + unicode: + dependency: transitive + description: + name: unicode + sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1" + url: "https://pub.dev" + source: hosted + version: "0.3.1" url_launcher: dependency: transitive description: @@ -1441,6 +1633,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.5.1" + wkt_parser: + dependency: transitive + description: + name: wkt_parser + sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" + url: "https://pub.dev" + source: hosted + version: "2.0.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ed66398..b1940b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,14 +48,16 @@ dependencies: diacritic: ^0.1.3 flutter_widget_from_html: ^0.15.2 webview_flutter: ^4.10.0 - ar_flutter_plugin: ^0.7.3 + #ar_flutter_plugin: ^0.7.3 flutter_pdfview: ^1.4.0+1 youtube_player_flutter: ^9.1.1 youtube_player_iframe: ^5.2.1 # Specific mobile - qr_code_scanner: ^1.0.1 #not in web + #qr_code_scanner: ^1.0.1 #not in web + mobile_scanner: ^4.0.0 # that replace qr_code_scanner.. + sqflite: #not in web just_audio_cache: ^0.1.2 #not in web flutter_beacon: ^0.5.1 #not in web @@ -63,6 +65,12 @@ dependencies: smooth_page_indicator: ^1.2.1 + mapbox_maps_flutter: ^2.0.0 # specific mobile .. + google_maps_flutter: ^2.5.3 # Specific mobile and web + qr_flutter: ^4.1.0 # multi + flutter_map: ^7.0.2 #all + image: ^4.1.7 + manager_api_new: path: manager_api_new # The following adds the Cupertino Icons font to your application.