Compare commits

...

17 Commits

Author SHA1 Message Date
Thomas Fransolet
c6599e13c9 Updates visit name to configuration + scan + misc 2025-07-09 17:45:27 +02:00
Thomas Fransolet
959b494b12 Support also mymuseum url 2025-07-09 14:48:36 +02:00
Thomas Fransolet
f0e41adace Test update scanner background color 2025-07-09 13:22:21 +02:00
Thomas Fransolet
303e50a255 Menu done + article refonte wip 2025-07-04 17:21:17 +02:00
Thomas Fransolet
c50083b19f Agenda + misc for sub menu (pdf, agenda) 2025-07-03 17:37:24 +02:00
Thomas Fransolet
f07570d8ee Weather done 2025-07-02 17:11:02 +02:00
Thomas Fransolet
5e5d92a510 misc 2025-07-02 13:11:16 +02:00
Thomas Fransolet
b20a112eb2 Map working - (only google, mapContext issue on selected point) 2025-06-19 16:50:28 +02:00
Thomas Fransolet
4e9dc59df9 Slider ok, map wip + update gradle etc + qr scanner to mobile scanner 2025-06-18 18:00:29 +02:00
Thomas Fransolet
7aca0638ce Puzzle working + slider wip + misc 2025-06-12 17:27:53 +02:00
Thomas Fransolet
bddde86974 Working Web, Pdf and Video type + misc and clean code 2025-06-11 17:26:36 +02:00
Thomas Fransolet
4946247812 Misc + clean code logic future for configurations and sections 2025-06-11 12:04:01 +02:00
Thomas Fransolet
9c5ae56549 misc + wip load section (not working) 2025-06-10 16:51:48 +02:00
Thomas Fransolet
b7ca69162c Misc, update layout, nice main page + hero to configuration detail 2025-06-05 18:20:48 +02:00
Thomas Fransolet
878a2e4cf0 Wip version 3.0.0 2025-05-28 17:16:18 +02:00
Thomas Fransolet
53dff98388 WIP version 3.0.0 2025-05-28 14:08:32 +02:00
Thomas Fransolet
42d3e257b2 Reintegrate carousel slider (fix rename 5.0) 2025-02-26 13:56:27 +01:00
368 changed files with 25016 additions and 5986 deletions

View File

@ -3,10 +3,10 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Article/article_page.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/Screens/section_page.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
// NOT USED ANYMORE
@ -180,7 +180,7 @@ class _ScannerPageState extends State<ScannerPage> {
context,
MaterialPageRoute(
builder: (context) {
return ArticlePage(articleId: code, visitAppContextIn: VisitAppContext()); // will not work..
return SectionPage(configuration: ConfigurationDTO(), rawSection: null, sectionId: code, visitAppContextIn: VisitAppContext()); // will not work..
},
),
);

View File

@ -0,0 +1,117 @@
import 'package:ar_flutter_plugin/managers/ar_location_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_session_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_object_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_anchor_manager.dart';
import 'package:flutter/material.dart';
import 'package:ar_flutter_plugin/ar_flutter_plugin.dart';
import 'package:ar_flutter_plugin/datatypes/config_planedetection.dart';
class DebugOptionsWidget extends StatefulWidget {
DebugOptionsWidget({Key? key}) : super(key: key);
@override
_DebugOptionsWidgetState createState() => _DebugOptionsWidgetState();
}
class _DebugOptionsWidgetState extends State<DebugOptionsWidget> {
ARSessionManager? arSessionManager;
ARObjectManager? arObjectManager;
bool _showFeaturePoints = false;
bool _showPlanes = false;
bool _showWorldOrigin = false;
bool _showAnimatedGuide = true;
String _planeTexturePath = "Images/triangle.png";
bool _handleTaps = false;
@override
void dispose() {
super.dispose();
arSessionManager!.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Debug Options'),
),
body: Container(
child: Stack(children: [
ARView(
onARViewCreated: onARViewCreated,
planeDetectionConfig: PlaneDetectionConfig.horizontalAndVertical,
showPlatformType: true,
),
Align(
alignment: FractionalOffset.bottomRight,
child: Container(
width: MediaQuery.of(context).size.width * 0.5,
color: Color(0xFFFFFFF).withOpacity(0.5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
SwitchListTile(
title: const Text('Feature Points'),
value: _showFeaturePoints,
onChanged: (bool value) {
setState(() {
_showFeaturePoints = value;
updateSessionSettings();
});
},
),
SwitchListTile(
title: const Text('Planes'),
value: _showPlanes,
onChanged: (bool value) {
setState(() {
_showPlanes = value;
updateSessionSettings();
});
},
),
SwitchListTile(
title: const Text('World Origin'),
value: _showWorldOrigin,
onChanged: (bool value) {
setState(() {
_showWorldOrigin = value;
updateSessionSettings();
});
},
),
],
),
),
),
])));
}
void onARViewCreated(
ARSessionManager arSessionManager,
ARObjectManager arObjectManager,
ARAnchorManager arAnchorManager,
ARLocationManager arLocationManager) {
this.arSessionManager = arSessionManager;
this.arObjectManager = arObjectManager;
this.arSessionManager!.onInitialize(
showFeaturePoints: _showFeaturePoints,
showPlanes: _showPlanes,
customPlaneTexturePath: _planeTexturePath,
showWorldOrigin: _showWorldOrigin,
showAnimatedGuide: _showAnimatedGuide,
handleTaps: _handleTaps,
);
this.arObjectManager!.onInitialize();
}
void updateSessionSettings() {
this.arSessionManager!.onInitialize(
showFeaturePoints: _showFeaturePoints,
showPlanes: _showPlanes,
customPlaneTexturePath: _planeTexturePath,
showWorldOrigin: _showWorldOrigin,
);
}
}

144
AR-TEST/Tests/TestAR.dart Normal file
View File

@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:ar_flutter_plugin/ar_flutter_plugin.dart';
import 'package:mymuseum_visitapp/Screens/Tests/DebugOptionsWidget.dart';
import 'package:mymuseum_visitapp/Screens/Tests/cloudtest.dart';
import 'package:mymuseum_visitapp/Screens/Tests/testother.dart';
import 'localtest.dart';
class TestAR extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<TestAR> {
String _platformVersion = 'Unknown';
static const String _title = 'AR Plugin Demo';
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await ArFlutterPlugin.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text(_title),
),
body: Column(children: [
Text('Running on: $_platformVersion\n'),
Expanded(
child: ExampleList(),
),
]),
),
);
}
}
class ExampleList extends StatelessWidget {
ExampleList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final examples = [
Example(
'Debug Options',
'Visualize feature points, planes and world coordinate system',
() => Navigator.push(context,
MaterialPageRoute(builder: (context) => DebugOptionsWidget()))),
/*Example(
'Local & Online Objects',
'Place 3D objects from Flutter assets and the web into the scene',
() => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LocalAndWebObjectsWidget()))),*/
Example(
'Anchors & Objects on Planes',
'Place 3D objects on detected planes using anchors',
() => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ObjectsOnPlanesWidget()))),
Example(
'Object Transformation Gestures',
'Rotate and Pan Objects',
() => Navigator.push(context,
MaterialPageRoute(builder: (context) => ObjectGesturesWidget()))),
Example(
'Screenshots',
'Place 3D objects on planes and take screenshots',
() => Navigator.push(context,
MaterialPageRoute(builder: (context) => ScreenshotWidget()))),
/*Example(
'Cloud Anchors',
'Place and retrieve 3D objects using the Google Cloud Anchor API',
() => Navigator.push(context,
MaterialPageRoute(builder: (context) => CloudAnchorWidget()))),
Example(
'External Model Management',
'Similar to Cloud Anchors example, but uses external database to choose from available 3D models',
() => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ExternalModelManagementWidget())))*/
];
return ListView(
children:
examples.map((example) => ExampleCard(example: example)).toList(),
);
}
}
class ExampleCard extends StatelessWidget {
ExampleCard({Key? key, required this.example}) : super(key: key);
final Example example;
@override
build(BuildContext context) {
return Card(
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
example.onTap();
},
child: ListTile(
title: Text(example.name),
subtitle: Text(example.description),
),
),
);
}
}
class Example {
const Example(this.name, this.description, this.onTap);
final String name;
final String description;
final Function onTap;
}

72
AR-TEST/Tests/XRTest.dart Normal file
View File

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:webview_flutter/webview_flutter.dart';
class XRWithQRScannerPage extends StatefulWidget {
@override
_XRWithQRScannerPageState createState() => _XRWithQRScannerPageState();
}
class _XRWithQRScannerPageState extends State<XRWithQRScannerPage> {
String qrCode = "";
late final WebViewController _webViewController;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
@override
void initState() {
super.initState();
_webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(Uri.parse("https://immersive-web.github.io/webxr-samples/immersive-ar-session.html"))
;
//..loadFlutterAsset('assets/files/xr_environment.html'); // Charge le fichier local
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("XR avec Scanner QR Code")),
body: Stack(
children: [
// La couche QR code scanner (superposée au-dessus de l'XR)
Positioned.fill(
child: QRView(
onQRViewCreated: (controller) {
controller.scannedDataStream.listen((scanData) {
setState(() {
qrCode = scanData.code!;
print('QR Code détecté : $qrCode');
fetchData(qrCode);
});
});
}, key: qrKey,
),
),
// La couche XR (WebXR avec Three.js ou autre moteur)
WebViewWidget(
controller: _webViewController
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Color(0x00000000))
..addJavaScriptChannel(
'WebViewChannel',
onMessageReceived: (message) {
// Message reçu de JavaScript
print(message.message);
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(message.message),
),
);
},
),
),
],
),
);
}
Future<void> fetchData(String? qrCode) async {
print('Fetching data for QR Code: $qrCode');
}
}

View File

@ -0,0 +1,170 @@
import 'package:ar_flutter_plugin/managers/ar_location_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_session_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_object_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_anchor_manager.dart';
import 'package:ar_flutter_plugin/models/ar_anchor.dart';
import 'package:flutter/material.dart';
import 'package:ar_flutter_plugin/ar_flutter_plugin.dart';
import 'package:ar_flutter_plugin/datatypes/config_planedetection.dart';
import 'package:ar_flutter_plugin/datatypes/node_types.dart';
import 'package:ar_flutter_plugin/datatypes/hittest_result_types.dart';
import 'package:ar_flutter_plugin/models/ar_node.dart';
import 'package:ar_flutter_plugin/models/ar_hittest_result.dart';
import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart';
import 'dart:math';
class ObjectGesturesWidget extends StatefulWidget {
ObjectGesturesWidget({Key? key}) : super(key: key);
@override
_ObjectGesturesWidgetState createState() => _ObjectGesturesWidgetState();
}
class _ObjectGesturesWidgetState extends State<ObjectGesturesWidget> {
ARSessionManager? arSessionManager;
ARObjectManager? arObjectManager;
ARAnchorManager? arAnchorManager;
List<ARNode> nodes = [];
List<ARAnchor> anchors = [];
@override
void dispose() {
super.dispose();
arSessionManager!.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Object Transformation Gestures'),
),
body: Container(
child: Stack(children: [
ARView(
onARViewCreated: onARViewCreated,
planeDetectionConfig: PlaneDetectionConfig.horizontalAndVertical,
),
Align(
alignment: FractionalOffset.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: onRemoveEverything,
child: Text("Remove Everything")),
]),
)
])));
}
void onARViewCreated(
ARSessionManager arSessionManager,
ARObjectManager arObjectManager,
ARAnchorManager arAnchorManager,
ARLocationManager arLocationManager) {
this.arSessionManager = arSessionManager;
this.arObjectManager = arObjectManager;
this.arAnchorManager = arAnchorManager;
this.arSessionManager!.onInitialize(
showFeaturePoints: false,
showPlanes: true,
customPlaneTexturePath: "Images/triangle.png",
showWorldOrigin: true,
handlePans: true,
handleRotation: true,
);
this.arObjectManager!.onInitialize();
this.arSessionManager!.onPlaneOrPointTap = onPlaneOrPointTapped;
this.arObjectManager!.onPanStart = onPanStarted;
this.arObjectManager!.onPanChange = onPanChanged;
this.arObjectManager!.onPanEnd = onPanEnded;
this.arObjectManager!.onRotationStart = onRotationStarted;
this.arObjectManager!.onRotationChange = onRotationChanged;
this.arObjectManager!.onRotationEnd = onRotationEnded;
}
Future<void> onRemoveEverything() async {
/*nodes.forEach((node) {
this.arObjectManager.removeNode(node);
});*/
anchors.forEach((anchor) {
this.arAnchorManager!.removeAnchor(anchor);
});
anchors = [];
}
Future<void> onPlaneOrPointTapped(
List<ARHitTestResult> hitTestResults) async {
var singleHitTestResult = hitTestResults.firstWhere(
(hitTestResult) => hitTestResult.type == ARHitTestResultType.plane);
if (singleHitTestResult != null) {
var newAnchor =
ARPlaneAnchor(transformation: singleHitTestResult.worldTransform);
bool? didAddAnchor = await this.arAnchorManager!.addAnchor(newAnchor);
if (didAddAnchor!) {
this.anchors.add(newAnchor);
// Add note to anchor
var newNode = ARNode(
type: NodeType.webGLB,
uri:
"assets/files/Duck.glb",
scale: Vector3(0.2, 0.2, 0.2),
position: Vector3(0.0, 0.0, 0.0),
rotation: Vector4(1.0, 0.0, 0.0, 0.0));
bool? didAddNodeToAnchor =
await this.arObjectManager!.addNode(newNode, planeAnchor: newAnchor);
if (didAddNodeToAnchor!) {
this.nodes.add(newNode);
} else {
this.arSessionManager!.onError("Adding Node to Anchor failed");
}
} else {
this.arSessionManager!.onError("Adding Anchor failed");
}
}
}
onPanStarted(String nodeName) {
print("Started panning node " + nodeName);
}
onPanChanged(String nodeName) {
print("Continued panning node " + nodeName);
}
onPanEnded(String nodeName, Matrix4 newTransform) {
print("Ended panning node " + nodeName);
final pannedNode =
this.nodes.firstWhere((element) => element.name == nodeName);
/*
* Uncomment the following command if you want to keep the transformations of the Flutter representations of the nodes up to date
* (e.g. if you intend to share the nodes through the cloud)
*/
//pannedNode.transform = newTransform;
}
onRotationStarted(String nodeName) {
print("Started rotating node " + nodeName);
}
onRotationChanged(String nodeName) {
print("Continued rotating node " + nodeName);
}
onRotationEnded(String nodeName, Matrix4 newTransform) {
print("Ended rotating node " + nodeName);
final rotatedNode =
this.nodes.firstWhere((element) => element.name == nodeName);
/*
* Uncomment the following command if you want to keep the transformations of the Flutter representations of the nodes up to date
* (e.g. if you intend to share the nodes through the cloud)
*/
//rotatedNode.transform = newTransform;
}
}

View File

@ -0,0 +1,157 @@
import 'package:ar_flutter_plugin/managers/ar_location_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_session_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_object_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_anchor_manager.dart';
import 'package:ar_flutter_plugin/models/ar_anchor.dart';
import 'package:flutter/material.dart';
import 'package:ar_flutter_plugin/ar_flutter_plugin.dart';
import 'package:ar_flutter_plugin/datatypes/config_planedetection.dart';
import 'package:ar_flutter_plugin/datatypes/node_types.dart';
import 'package:ar_flutter_plugin/datatypes/hittest_result_types.dart';
import 'package:ar_flutter_plugin/models/ar_node.dart';
import 'package:ar_flutter_plugin/models/ar_hittest_result.dart';
import 'package:vector_math/vector_math_64.dart';
class ScreenshotWidget extends StatefulWidget {
const ScreenshotWidget({Key? key}) : super(key: key);
@override
_ScreenshotWidgetState createState() => _ScreenshotWidgetState();
}
class _ScreenshotWidgetState extends State<ScreenshotWidget> {
ARSessionManager? arSessionManager;
ARObjectManager? arObjectManager;
ARAnchorManager? arAnchorManager;
List<ARNode> nodes = [];
List<ARAnchor> anchors = [];
@override
void dispose() {
super.dispose();
arSessionManager!.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Screenshots'),
),
body:
Container(
child:
Stack(children: [
ARView(
onARViewCreated: onARViewCreated,
planeDetectionConfig: PlaneDetectionConfig.horizontalAndVertical,
),
Align(
alignment: FractionalOffset.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: onRemoveEverything,
child: const Text("Remove Everything")),
ElevatedButton(
onPressed: onTakeScreenshot,
child: const Text("Take Screenshot")),
]),
)
])));
}
void onARViewCreated(
ARSessionManager arSessionManager,
ARObjectManager arObjectManager,
ARAnchorManager arAnchorManager,
ARLocationManager arLocationManager) {
this.arSessionManager = arSessionManager;
this.arObjectManager = arObjectManager;
this.arAnchorManager = arAnchorManager;
this.arSessionManager!.onInitialize(
showFeaturePoints: false,
showPlanes: true,
customPlaneTexturePath: "Images/triangle.png",
showWorldOrigin: true,
);
this.arObjectManager!.onInitialize();
this.arSessionManager!.onPlaneOrPointTap = onPlaneOrPointTapped;
this.arObjectManager!.onNodeTap = onNodeTapped;
}
Future<void> onRemoveEverything() async {
/*nodes.forEach((node) {
this.arObjectManager.removeNode(node);
});*/
// anchors.forEach((anchor)
for (var anchor in anchors)
{
arAnchorManager!.removeAnchor(anchor);
};
anchors = [];
}
Future<void> onTakeScreenshot() async {
var image = await arSessionManager!.snapshot();
await showDialog(
context: context,
builder: (_) => Dialog(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(image: image, fit: BoxFit.cover)),
),
));
}
Future<void> onNodeTapped(List<String> nodes) async {
var number = nodes.length;
arSessionManager!.onError("Tapped $number node(s)");
}
Future<void> onPlaneOrPointTapped(
List<ARHitTestResult> hitTestResults) async {
var singleHitTestResult = hitTestResults.firstWhere(
(hitTestResult) => hitTestResult.type == ARHitTestResultType.plane);
if (singleHitTestResult != null) {
var newAnchor =
ARPlaneAnchor(transformation: singleHitTestResult.worldTransform);
bool? didAddAnchor = await arAnchorManager!.addAnchor(newAnchor);
if (didAddAnchor != null && didAddAnchor) {
anchors.add(newAnchor);
// Add note to anchor
var newNode = ARNode(
type: NodeType.webGLB,
uri:
"https://github.com/KhronosGroup/glTF-Sample-Models/blob/main/2.0/Duck/glTF-Binary/Duck.glb",
scale: Vector3(0.2, 0.2, 0.2),
position: Vector3(0.0, 0.0, 0.0),
rotation: Vector4(1.0, 0.0, 0.0, 0.0));
bool? didAddNodeToAnchor =
await arObjectManager!.addNode(newNode, planeAnchor: newAnchor);
if (didAddNodeToAnchor != null && didAddNodeToAnchor) {
nodes.add(newNode);
} else {
arSessionManager!.onError("Adding Node to Anchor failed");
}
} else {
arSessionManager!.onError("Adding Anchor failed");
}
/*
// To add a node to the tapped position without creating an anchor, use the following code (Please mind: the function onRemoveEverything has to be adapted accordingly!):
var newNode = ARNode(
type: NodeType.localGLTF2,
uri: "Models/Chicken_01/Chicken_01.gltf",
scale: Vector3(0.2, 0.2, 0.2),
transformation: singleHitTestResult.worldTransform);
bool didAddWebNode = await this.arObjectManager.addNode(newNode);
if (didAddWebNode) {
this.nodes.add(newNode);
}*/
}
}
}

View File

@ -0,0 +1,139 @@
import 'package:ar_flutter_plugin/managers/ar_location_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_session_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_object_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_anchor_manager.dart';
import 'package:ar_flutter_plugin/models/ar_anchor.dart';
import 'package:flutter/material.dart';
import 'package:ar_flutter_plugin/ar_flutter_plugin.dart';
import 'package:ar_flutter_plugin/datatypes/config_planedetection.dart';
import 'package:ar_flutter_plugin/datatypes/node_types.dart';
import 'package:ar_flutter_plugin/datatypes/hittest_result_types.dart';
import 'package:ar_flutter_plugin/models/ar_node.dart';
import 'package:ar_flutter_plugin/models/ar_hittest_result.dart';
import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart';
import 'dart:math';
class ObjectsOnPlanesWidget extends StatefulWidget {
ObjectsOnPlanesWidget({Key? key}) : super(key: key);
@override
_ObjectsOnPlanesWidgetState createState() => _ObjectsOnPlanesWidgetState();
}
class _ObjectsOnPlanesWidgetState extends State<ObjectsOnPlanesWidget> {
ARSessionManager? arSessionManager;
ARObjectManager? arObjectManager;
ARAnchorManager? arAnchorManager;
List<ARNode> nodes = [];
List<ARAnchor> anchors = [];
@override
void dispose() {
super.dispose();
arSessionManager!.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Anchors & Objects on Planes'),
),
body: Container(
child: Stack(children: [
ARView(
onARViewCreated: onARViewCreated,
planeDetectionConfig: PlaneDetectionConfig.horizontalAndVertical,
),
Align(
alignment: FractionalOffset.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: onRemoveEverything,
child: Text("Remove Everything")),
]),
)
])));
}
void onARViewCreated(
ARSessionManager arSessionManager,
ARObjectManager arObjectManager,
ARAnchorManager arAnchorManager,
ARLocationManager arLocationManager) {
this.arSessionManager = arSessionManager;
this.arObjectManager = arObjectManager;
this.arAnchorManager = arAnchorManager;
this.arSessionManager!.onInitialize(
showFeaturePoints: false,
showPlanes: true,
customPlaneTexturePath: "Images/triangle.png",
showWorldOrigin: true,
);
this.arObjectManager!.onInitialize();
this.arSessionManager!.onPlaneOrPointTap = onPlaneOrPointTapped;
this.arObjectManager!.onNodeTap = onNodeTapped;
}
Future<void> onRemoveEverything() async {
/*nodes.forEach((node) {
this.arObjectManager.removeNode(node);
});*/
anchors.forEach((anchor) {
this.arAnchorManager!.removeAnchor(anchor);
});
anchors = [];
}
Future<void> onNodeTapped(List<String> nodes) async {
var number = nodes.length;
this.arSessionManager!.onError("Tapped $number node(s)");
}
Future<void> onPlaneOrPointTapped(
List<ARHitTestResult> hitTestResults) async {
var singleHitTestResult = hitTestResults.firstWhere(
(hitTestResult) => hitTestResult.type == ARHitTestResultType.plane);
if (singleHitTestResult != null) {
var newAnchor =
ARPlaneAnchor(transformation: singleHitTestResult.worldTransform);
bool? didAddAnchor = await this.arAnchorManager!.addAnchor(newAnchor);
if (didAddAnchor!) {
this.anchors.add(newAnchor);
// Add note to anchor
var newNode = ARNode(
type: NodeType.webGLB,
uri:
"https://github.com/KhronosGroup/glTF-Sample-Models/blob/main/2.0/Duck/glTF-Binary/Duck.glb",
scale: Vector3(0.2, 0.2, 0.2),
position: Vector3(0.0, 0.0, 0.0),
rotation: Vector4(1.0, 0.0, 0.0, 0.0));
bool? didAddNodeToAnchor =
await this.arObjectManager!.addNode(newNode, planeAnchor: newAnchor);
if (didAddNodeToAnchor!) {
this.nodes.add(newNode);
} else {
this.arSessionManager!.onError("Adding Node to Anchor failed");
}
} else {
this.arSessionManager!.onError("Adding Anchor failed");
}
/*
// To add a node to the tapped position without creating an anchor, use the following code (Please mind: the function onRemoveEverything has to be adapted accordingly!):
var newNode = ARNode(
type: NodeType.localGLTF2,
uri: "Models/Chicken_01/Chicken_01.gltf",
scale: Vector3(0.2, 0.2, 0.2),
transformation: singleHitTestResult.worldTransform);
bool didAddWebNode = await this.arObjectManager.addNode(newNode);
if (didAddWebNode) {
this.nodes.add(newNode);
}*/
}
}
}

View File

@ -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'
}
@ -57,7 +65,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "be.unov.mymuseum.fortsaintheribert" // Update for mdlf and other clients -- "be.unov.mymuseum.fortsaintheribert" // be.unov.myinfomate.mdlf
minSdkVersion flutter.minSdkVersion
minSdkVersion 24// flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@ -51,7 +51,8 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyDg6ApuZb6TRsauIyHJ9-XVwGYeh7MsWXE"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/logo" />

View File

@ -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

View File

@ -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
}

BIN
assets/files/Duck.glb Normal file

Binary file not shown.

View File

@ -0,0 +1,202 @@
<!doctype html>
<!--
Copyright 2018 The Immersive Web Community Group
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no'>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<link rel='icon' type='image/png' sizes='32x32' href='favicon-32x32.png'>
<link rel='icon' type='image/png' sizes='96x96' href='favicon-96x96.png'>
<link rel='stylesheet' href='css/common.css'>
<title>Immersive AR Session</title>
</head>
<body>
<header>
<details open>
<summary>Immersive AR Session</summary>
<p>
This sample demonstrates how to use an 'immersive-ar' XRSession to
present a simple WebGL scene to a transparent or passthrough XR
device. The logic is largely the same as the corresponding VR sample,
with the primary difference being that no background is rendered and
the model is scaled down for easier viewing in a real-world space.
<a class="back" href="./">Back</a>
</p>
</details>
</header>
<script type="module">
import {WebXRButton} from './js/util/webxr-button.js';
import {Scene} from './js/render/scenes/scene.js';
import {Renderer, createWebGLContext} from './js/render/core/renderer.js';
import {SkyboxNode} from './js/render/nodes/skybox.js';
import {InlineViewerHelper} from './js/util/inline-viewer-helper.js';
import {Gltf2Node} from './js/render/nodes/gltf2.js';
import {QueryArgs} from './js/util/query-args.js';
// If requested, use the polyfill to provide support for mobile devices
// and devices which only support WebVR.
import WebXRPolyfill from './js/third-party/webxr-polyfill/build/webxr-polyfill.module.js';
if (QueryArgs.getBool('usePolyfill', true)) {
let polyfill = new WebXRPolyfill();
}
// XR globals.
let xrButton = null;
let xrImmersiveRefSpace = null;
let inlineViewerHelper = null;
// WebGL scene globals.
let gl = null;
let renderer = null;
let scene = new Scene();
let solarSystem = new Gltf2Node({url: 'media/gltf/space/space.gltf'});
// The solar system is big (citation needed). Scale it down so that users
// can move around the planets more easily.
solarSystem.scale = [0.01, 0.01, 0.1];
scene.addNode(solarSystem);
// Still adding a skybox, but only for the benefit of the inline view.
let skybox = new SkyboxNode({url: 'media/textures/milky-way-4k.png'});
scene.addNode(skybox);
function initXR() {
xrButton = new WebXRButton({
onRequestSession: onRequestSession,
onEndSession: onEndSession,
textEnterXRTitle: "START AR Youhou",
textXRNotFoundTitle: "AR NOT FOUND",
textExitXRTitle: "EXIT AR",
});
document.querySelector('header').appendChild(xrButton.domElement);
if (navigator.xr) {
// Checks to ensure that 'immersive-ar' mode is available, and only
// enables the button if so.
navigator.xr.isSessionSupported('immersive-ar').then((supported) => {
xrButton.enabled = supported;
});
navigator.xr.requestSession('inline').then(onSessionStarted);
}
}
function onRequestSession() {
// Requests an 'immersive-ar' session, which ensures that the users
// environment will be visible either via video passthrough or a
// transparent display. This may be presented either in a headset or
// fullscreen on a mobile device.
return navigator.xr.requestSession('immersive-ar')
.then((session) => {
xrButton.setSession(session);
session.isImmersive = true;
onSessionStarted(session);
});
}
function initGL() {
if (gl)
return;
gl = createWebGLContext({
xrCompatible: true
});
document.body.appendChild(gl.canvas);
function onResize() {
gl.canvas.width = gl.canvas.clientWidth * window.devicePixelRatio;
gl.canvas.height = gl.canvas.clientHeight * window.devicePixelRatio;
}
window.addEventListener('resize', onResize);
onResize();
renderer = new Renderer(gl);
scene.setRenderer(renderer);
}
function onSessionStarted(session) {
session.addEventListener('end', onSessionEnded);
if (session.isImmersive) {
// When in 'immersive-ar' mode don't draw an opaque background because
// we want the real world to show through.
skybox.visible = false;
}
initGL();
session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
let refSpaceType = session.isImmersive ? 'local' : 'viewer';
session.requestReferenceSpace(refSpaceType).then((refSpace) => {
if (session.isImmersive) {
xrImmersiveRefSpace = refSpace;
xrImmersiveRefSpace.addEventListener('reset', (evt) => {
if (evt.transform) {
// AR experiences typically should stay grounded to the real world.
// If there's a known origin shift, compensate for it here.
xrImmersiveRefSpace = xrImmersiveRefSpace.getOffsetReferenceSpace(evt.transform);
}
});
} else {
inlineViewerHelper = new InlineViewerHelper(gl.canvas, refSpace);
}
session.requestAnimationFrame(onXRFrame);
});
}
function onEndSession(session) {
session.end();
}
function onSessionEnded(event) {
if (event.session.isImmersive) {
xrButton.setSession(null);
// Turn the background back on when we go back to the inlive view.
skybox.visible = true;
}
}
// Called every time a XRSession requests that a new frame be drawn.
function onXRFrame(t, frame) {
let session = frame.session;
let refSpace = session.isImmersive ?
xrImmersiveRefSpace :
inlineViewerHelper.referenceSpace;
let pose = frame.getViewerPose(refSpace);
scene.startFrame();
session.requestAnimationFrame(onXRFrame);
scene.drawXRFrame(frame, pose);
scene.endFrame();
}
// Start the XR application.
initXR();
</script>
</body>
</html>

BIN
assets/icons/marker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,149 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'carousel_options.dart';
import 'carousel_state.dart';
import 'utils.dart';
abstract class CarouselController {
bool get ready;
Future<Null> get onReady;
Future<void> nextPage({Duration? duration, Curve? curve});
Future<void> previousPage({Duration? duration, Curve? curve});
void jumpToPage(int page);
Future<void> animateToPage(int page, {Duration? duration, Curve? curve});
void startAutoPlay();
void stopAutoPlay();
factory CarouselController() => CarouselControllerImpl();
}
class CarouselControllerImpl implements CarouselController {
final Completer<Null> _readyCompleter = Completer<Null>();
CarouselState? _state;
set state(CarouselState? state) {
_state = state;
if (!_readyCompleter.isCompleted) {
_readyCompleter.complete();
}
}
void _setModeController() =>
_state!.changeMode(CarouselPageChangedReason.controller);
@override
bool get ready => _state != null;
@override
Future<Null> get onReady => _readyCompleter.future;
/// Animates the controlled [CarouselSlider] to the next page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> nextPage(
{Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear}) async {
final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
if (isNeedResetTimer) {
_state!.onResetTimer();
}
_setModeController();
await _state!.pageController!.nextPage(duration: duration!, curve: curve!);
if (isNeedResetTimer) {
_state!.onResumeTimer();
}
}
/// Animates the controlled [CarouselSlider] to the previous page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> previousPage(
{Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear}) async {
final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
if (isNeedResetTimer) {
_state!.onResetTimer();
}
_setModeController();
await _state!.pageController!
.previousPage(duration: duration!, curve: curve!);
if (isNeedResetTimer) {
_state!.onResumeTimer();
}
}
/// Changes which page is displayed in the controlled [CarouselSlider].
///
/// Jumps the page position from its current value to the given value,
/// without animation, and without checking if the new value is in range.
void jumpToPage(int page) {
final index = getRealIndex(_state!.pageController!.page!.toInt(),
_state!.realPage - _state!.initialPage, _state!.itemCount);
_setModeController();
final int pageToJump = _state!.pageController!.page!.toInt() + page - index;
return _state!.pageController!.jumpToPage(pageToJump);
}
/// Animates the controlled [CarouselSlider] from the current page to the given page.
///
/// The animation lasts for the given duration and follows the given curve.
/// The returned [Future] resolves when the animation completes.
Future<void> animateToPage(int page,
{Duration? duration = const Duration(milliseconds: 300),
Curve? curve = Curves.linear}) async {
final bool isNeedResetTimer = _state!.options.pauseAutoPlayOnManualNavigate;
if (isNeedResetTimer) {
_state!.onResetTimer();
}
final index = getRealIndex(_state!.pageController!.page!.toInt(),
_state!.realPage - _state!.initialPage, _state!.itemCount);
int smallestMovement = page - index;
if (_state!.options.enableInfiniteScroll &&
_state!.itemCount != null &&
_state!.options.animateToClosest) {
if ((page - index).abs() > (page + _state!.itemCount! - index).abs()) {
smallestMovement = page + _state!.itemCount! - index;
} else if ((page - index).abs() >
(page - _state!.itemCount! - index).abs()) {
smallestMovement = page - _state!.itemCount! - index;
}
}
_setModeController();
await _state!.pageController!.animateToPage(
_state!.pageController!.page!.toInt() + smallestMovement,
duration: duration!,
curve: curve!);
if (isNeedResetTimer) {
_state!.onResumeTimer();
}
}
/// Starts the controlled [CarouselSlider] autoplay.
///
/// The carousel will only autoPlay if the [autoPlay] parameter
/// in [CarouselOptions] is true.
void startAutoPlay() {
_state!.onResumeTimer();
}
/// Stops the controlled [CarouselSlider] from autoplaying.
///
/// This is a more on-demand way of doing this. Use the [autoPlay]
/// parameter in [CarouselOptions] to specify the autoPlay behaviour of the carousel.
void stopAutoPlay() {
_state!.onResetTimer();
}
}

View File

@ -1,223 +0,0 @@
import 'package:flutter/material.dart';
enum CarouselPageChangedReason { timed, manual, controller }
enum CenterPageEnlargeStrategy { scale, height, zoom }
class CarouselOptions {
/// Set carousel height and overrides any existing [aspectRatio].
final double? height;
/// Aspect ratio is used if no height have been declared.
///
/// Defaults to 16:9 aspect ratio.
final double aspectRatio;
/// The fraction of the viewport that each page should occupy.
///
/// Defaults to 0.8, which means each page fills 80% of the carousel.
final double viewportFraction;
/// The initial page to show when first creating the [CarouselSlider].
///
/// Defaults to 0.
final int initialPage;
///Determines if carousel should loop infinitely or be limited to item length.
///
///Defaults to true, i.e. infinite loop.
final bool enableInfiniteScroll;
///Determines if carousel should loop to the closest occurence of requested page.
///
///Defaults to true.
final bool animateToClosest;
/// Reverse the order of items if set to true.
///
/// Defaults to false.
final bool reverse;
/// Enables auto play, sliding one page at a time.
///
/// Use [autoPlayInterval] to determent the frequency of slides.
/// Defaults to false.
final bool autoPlay;
/// Sets Duration to determent the frequency of slides when
///
/// [autoPlay] is set to true.
/// Defaults to 4 seconds.
final Duration autoPlayInterval;
/// The animation duration between two transitioning pages while in auto playback.
///
/// Defaults to 800 ms.
final Duration autoPlayAnimationDuration;
/// Determines the animation curve physics.
///
/// Defaults to [Curves.fastOutSlowIn].
final Curve autoPlayCurve;
/// Determines if current page should be larger than the side images,
/// creating a feeling of depth in the carousel.
///
/// Defaults to false.
final bool? enlargeCenterPage;
/// The axis along which the page view scrolls.
///
/// Defaults to [Axis.horizontal].
final Axis scrollDirection;
/// Called whenever the page in the center of the viewport changes.
final Function(int index, CarouselPageChangedReason reason)? onPageChanged;
/// Called whenever the carousel is scrolled
final ValueChanged<double?>? onScrolled;
/// How the carousel should respond to user input.
///
/// For example, determines how the items continues to animate after the
/// user stops dragging the page view.
///
/// The physics are modified to snap to page boundaries using
/// [PageScrollPhysics] prior to being used.
///
/// Defaults to matching platform conventions.
final ScrollPhysics? scrollPhysics;
/// Set to false to disable page snapping, useful for custom scroll behavior.
///
/// Default to `true`.
final bool pageSnapping;
/// If `true`, the auto play function will be paused when user is interacting with
/// the carousel, and will be resumed when user finish interacting.
/// Default to `true`.
final bool pauseAutoPlayOnTouch;
/// If `true`, the auto play function will be paused when user is calling
/// pageController's `nextPage` or `previousPage` or `animateToPage` method.
/// And after the animation complete, the auto play will be resumed.
/// Default to `true`.
final bool pauseAutoPlayOnManualNavigate;
/// If `enableInfiniteScroll` is `false`, and `autoPlay` is `true`, this option
/// decide the carousel should go to the first item when it reach the last item or not.
/// If set to `true`, the auto play will be paused when it reach the last item.
/// If set to `false`, the auto play function will animate to the first item when it was
/// in the last item.
final bool pauseAutoPlayInFiniteScroll;
/// Pass a `PageStoragekey` if you want to keep the pageview's position when it was recreated.
final PageStorageKey? pageViewKey;
/// Use [enlargeStrategy] to determine which method to enlarge the center page.
final CenterPageEnlargeStrategy enlargeStrategy;
/// How much the pages next to the center page will be scaled down.
/// If `enlargeCenterPage` is false, this property has no effect.
final double enlargeFactor;
/// Whether or not to disable the `Center` widget for each slide.
final bool disableCenter;
/// Whether to add padding to both ends of the list.
/// If this is set to true and [viewportFraction] < 1.0, padding will be added such that the first and last child slivers will be in the center of the viewport when scrolled all the way to the start or end, respectively.
/// If [viewportFraction] >= 1.0, this property has no effect.
/// This property defaults to true and must not be null.
final bool padEnds;
/// Exposed clipBehavior of PageView
final Clip clipBehavior;
CarouselOptions({
this.height,
this.aspectRatio = 16 / 9,
this.viewportFraction = 0.8,
this.initialPage = 0,
this.enableInfiniteScroll = true,
this.animateToClosest = true,
this.reverse = false,
this.autoPlay = false,
this.autoPlayInterval = const Duration(seconds: 4),
this.autoPlayAnimationDuration = const Duration(milliseconds: 800),
this.autoPlayCurve = Curves.fastOutSlowIn,
this.enlargeCenterPage = false,
this.onPageChanged,
this.onScrolled,
this.scrollPhysics,
this.pageSnapping = true,
this.scrollDirection = Axis.horizontal,
this.pauseAutoPlayOnTouch = true,
this.pauseAutoPlayOnManualNavigate = true,
this.pauseAutoPlayInFiniteScroll = false,
this.pageViewKey,
this.enlargeStrategy = CenterPageEnlargeStrategy.scale,
this.enlargeFactor = 0.3,
this.disableCenter = false,
this.padEnds = true,
this.clipBehavior = Clip.hardEdge,
});
///Generate new [CarouselOptions] based on old ones.
CarouselOptions copyWith(
{double? height,
double? aspectRatio,
double? viewportFraction,
int? initialPage,
bool? enableInfiniteScroll,
bool? reverse,
bool? autoPlay,
Duration? autoPlayInterval,
Duration? autoPlayAnimationDuration,
Curve? autoPlayCurve,
bool? enlargeCenterPage,
Function(int index, CarouselPageChangedReason reason)? onPageChanged,
ValueChanged<double?>? onScrolled,
ScrollPhysics? scrollPhysics,
bool? pageSnapping,
Axis? scrollDirection,
bool? pauseAutoPlayOnTouch,
bool? pauseAutoPlayOnManualNavigate,
bool? pauseAutoPlayInFiniteScroll,
PageStorageKey? pageViewKey,
CenterPageEnlargeStrategy? enlargeStrategy,
double? enlargeFactor,
bool? disableCenter,
Clip? clipBehavior,
bool? padEnds}) =>
CarouselOptions(
height: height ?? this.height,
aspectRatio: aspectRatio ?? this.aspectRatio,
viewportFraction: viewportFraction ?? this.viewportFraction,
initialPage: initialPage ?? this.initialPage,
enableInfiniteScroll: enableInfiniteScroll ?? this.enableInfiniteScroll,
reverse: reverse ?? this.reverse,
autoPlay: autoPlay ?? this.autoPlay,
autoPlayInterval: autoPlayInterval ?? this.autoPlayInterval,
autoPlayAnimationDuration:
autoPlayAnimationDuration ?? this.autoPlayAnimationDuration,
autoPlayCurve: autoPlayCurve ?? this.autoPlayCurve,
enlargeCenterPage: enlargeCenterPage ?? this.enlargeCenterPage,
onPageChanged: onPageChanged ?? this.onPageChanged,
onScrolled: onScrolled ?? this.onScrolled,
scrollPhysics: scrollPhysics ?? this.scrollPhysics,
pageSnapping: pageSnapping ?? this.pageSnapping,
scrollDirection: scrollDirection ?? this.scrollDirection,
pauseAutoPlayOnTouch: pauseAutoPlayOnTouch ?? this.pauseAutoPlayOnTouch,
pauseAutoPlayOnManualNavigate:
pauseAutoPlayOnManualNavigate ?? this.pauseAutoPlayOnManualNavigate,
pauseAutoPlayInFiniteScroll:
pauseAutoPlayInFiniteScroll ?? this.pauseAutoPlayInFiniteScroll,
pageViewKey: pageViewKey ?? this.pageViewKey,
enlargeStrategy: enlargeStrategy ?? this.enlargeStrategy,
enlargeFactor: enlargeFactor ?? this.enlargeFactor,
disableCenter: disableCenter ?? this.disableCenter,
clipBehavior: clipBehavior ?? this.clipBehavior,
padEnds: padEnds ?? this.padEnds,
);
}

View File

@ -1,396 +0,0 @@
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'carousel_controller.dart' as cs;
import 'carousel_options.dart';
import 'carousel_state.dart';
import 'utils.dart';
export 'carousel_controller.dart';
export 'carousel_options.dart';
typedef Widget ExtendedIndexedWidgetBuilder(
BuildContext context, int index, int realIndex);
class CarouselSlider extends StatefulWidget {
/// [CarouselOptions] to create a [CarouselState] with
final CarouselOptions options;
final bool? disableGesture;
/// The widgets to be shown in the carousel of default constructor
final List<Widget>? items;
/// The widget item builder that will be used to build item on demand
/// The third argument is the PageView's real index, can be used to cooperate
/// with Hero.
final ExtendedIndexedWidgetBuilder? itemBuilder;
/// A [MapController], used to control the map.
final cs.CarouselControllerImpl _carouselController;
final int? itemCount;
CarouselSlider(
{required this.items,
required this.options,
this.disableGesture,
cs.CarouselController? carouselController,
Key? key})
: itemBuilder = null,
itemCount = items != null ? items.length : 0,
_carouselController = carouselController != null
? carouselController as cs.CarouselControllerImpl
: cs.CarouselController() as cs.CarouselControllerImpl,
super(key: key);
/// The on demand item builder constructor
CarouselSlider.builder(
{required this.itemCount,
required this.itemBuilder,
required this.options,
this.disableGesture,
cs.CarouselController? carouselController,
Key? key})
: items = null,
_carouselController = carouselController != null
? carouselController as cs.CarouselControllerImpl
: cs.CarouselController() as cs.CarouselControllerImpl,
super(key: key);
@override
CarouselSliderState createState() => CarouselSliderState(_carouselController);
}
class CarouselSliderState extends State<CarouselSlider>
with TickerProviderStateMixin {
final cs.CarouselControllerImpl carouselController;
Timer? timer;
CarouselOptions get options => widget.options;
CarouselState? carouselState;
PageController? pageController;
/// mode is related to why the page is being changed
CarouselPageChangedReason mode = CarouselPageChangedReason.controller;
CarouselSliderState(this.carouselController);
void changeMode(CarouselPageChangedReason _mode) {
mode = _mode;
}
@override
void didUpdateWidget(CarouselSlider oldWidget) {
carouselState!.options = options;
carouselState!.itemCount = widget.itemCount;
// pageController needs to be re-initialized to respond to state changes
pageController = PageController(
viewportFraction: options.viewportFraction,
initialPage: carouselState!.realPage,
);
carouselState!.pageController = pageController;
// handle autoplay when state changes
handleAutoPlay();
super.didUpdateWidget(oldWidget);
}
@override
void initState() {
super.initState();
carouselState =
CarouselState(this.options, clearTimer, resumeTimer, this.changeMode);
carouselState!.itemCount = widget.itemCount;
carouselController.state = carouselState;
carouselState!.initialPage = widget.options.initialPage;
carouselState!.realPage = options.enableInfiniteScroll
? carouselState!.realPage + carouselState!.initialPage
: carouselState!.initialPage;
handleAutoPlay();
pageController = PageController(
viewportFraction: options.viewportFraction,
initialPage: carouselState!.realPage,
);
carouselState!.pageController = pageController;
}
Timer? getTimer() {
return widget.options.autoPlay
? Timer.periodic(widget.options.autoPlayInterval, (_) {
if (!mounted) {
clearTimer();
return;
}
final route = ModalRoute.of(context);
if (route?.isCurrent == false) {
return;
}
CarouselPageChangedReason previousReason = mode;
changeMode(CarouselPageChangedReason.timed);
int nextPage = carouselState!.pageController!.page!.round() + 1;
int itemCount = widget.itemCount ?? widget.items!.length;
if (nextPage >= itemCount &&
widget.options.enableInfiniteScroll == false) {
if (widget.options.pauseAutoPlayInFiniteScroll) {
clearTimer();
return;
}
nextPage = 0;
}
carouselState!.pageController!
.animateToPage(nextPage,
duration: widget.options.autoPlayAnimationDuration,
curve: widget.options.autoPlayCurve)
.then((_) => changeMode(previousReason));
})
: null;
}
void clearTimer() {
if (timer != null) {
timer?.cancel();
timer = null;
}
}
void resumeTimer() {
if (timer == null) {
timer = getTimer();
}
}
void handleAutoPlay() {
bool autoPlayEnabled = widget.options.autoPlay;
if (autoPlayEnabled && timer != null) return;
clearTimer();
if (autoPlayEnabled) {
resumeTimer();
}
}
Widget getGestureWrapper(Widget child) {
Widget wrapper;
if (widget.options.height != null) {
wrapper = Container(height: widget.options.height, child: child);
} else {
wrapper =
AspectRatio(aspectRatio: widget.options.aspectRatio, child: child);
}
if (true == widget.disableGesture) {
return NotificationListener(
onNotification: (Notification notification) {
if (widget.options.onScrolled != null &&
notification is ScrollUpdateNotification) {
widget.options.onScrolled!(carouselState!.pageController!.page);
}
return false;
},
child: wrapper,
);
}
return RawGestureDetector(
behavior: HitTestBehavior.opaque,
gestures: {
_MultipleGestureRecognizer:
GestureRecognizerFactoryWithHandlers<_MultipleGestureRecognizer>(
() => _MultipleGestureRecognizer(),
(_MultipleGestureRecognizer instance) {
instance.onStart = (_) {
onStart();
};
instance.onDown = (_) {
onPanDown();
};
instance.onEnd = (_) {
onPanUp();
};
instance.onCancel = () {
onPanUp();
};
}),
},
child: NotificationListener(
onNotification: (Notification notification) {
if (widget.options.onScrolled != null &&
notification is ScrollUpdateNotification) {
widget.options.onScrolled!(carouselState!.pageController!.page);
}
return false;
},
child: wrapper,
),
);
}
Widget getCenterWrapper(Widget child) {
if (widget.options.disableCenter) {
return Container(
child: child,
);
}
return Center(child: child);
}
Widget getEnlargeWrapper(Widget? child,
{double? width,
double? height,
double? scale,
required double itemOffset}) {
if (widget.options.enlargeStrategy == CenterPageEnlargeStrategy.height) {
return SizedBox(child: child, width: width, height: height);
}
if (widget.options.enlargeStrategy == CenterPageEnlargeStrategy.zoom) {
late Alignment alignment;
final bool horizontal = options.scrollDirection == Axis.horizontal;
if (itemOffset > 0) {
alignment = horizontal ? Alignment.centerRight : Alignment.bottomCenter;
} else {
alignment = horizontal ? Alignment.centerLeft : Alignment.topCenter;
}
return Transform.scale(child: child, scale: scale!, alignment: alignment);
}
return Transform.scale(
scale: scale!,
child: Container(child: child, width: width, height: height));
}
void onStart() {
changeMode(CarouselPageChangedReason.manual);
}
void onPanDown() {
if (widget.options.pauseAutoPlayOnTouch) {
clearTimer();
}
changeMode(CarouselPageChangedReason.manual);
}
void onPanUp() {
if (widget.options.pauseAutoPlayOnTouch) {
resumeTimer();
}
}
@override
void dispose() {
super.dispose();
clearTimer();
}
@override
Widget build(BuildContext context) {
return getGestureWrapper(PageView.builder(
padEnds: widget.options.padEnds,
scrollBehavior: ScrollConfiguration.of(context).copyWith(
scrollbars: false,
overscroll: false,
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
clipBehavior: widget.options.clipBehavior,
physics: widget.options.scrollPhysics,
scrollDirection: widget.options.scrollDirection,
pageSnapping: widget.options.pageSnapping,
controller: carouselState!.pageController,
reverse: widget.options.reverse,
itemCount: widget.options.enableInfiniteScroll ? null : widget.itemCount,
key: widget.options.pageViewKey,
onPageChanged: (int index) {
int currentPage = getRealIndex(index + carouselState!.initialPage,
carouselState!.realPage, widget.itemCount);
if (widget.options.onPageChanged != null) {
widget.options.onPageChanged!(currentPage, mode);
}
},
itemBuilder: (BuildContext context, int idx) {
final int index = getRealIndex(idx + carouselState!.initialPage,
carouselState!.realPage, widget.itemCount);
return AnimatedBuilder(
animation: carouselState!.pageController!,
child: (widget.items != null)
? (widget.items!.length > 0 ? widget.items![index] : Container())
: widget.itemBuilder!(context, index, idx),
builder: (BuildContext context, child) {
double distortionValue = 1.0;
// if `enlargeCenterPage` is true, we must calculate the carousel item's height
// to display the visual effect
double itemOffset = 0;
if (widget.options.enlargeCenterPage != null &&
widget.options.enlargeCenterPage == true) {
// pageController.page can only be accessed after the first build,
// so in the first build we calculate the itemoffset manually
var position = carouselState?.pageController?.position;
if (position != null &&
position.hasPixels &&
position.hasContentDimensions) {
var _page = carouselState?.pageController?.page;
if (_page != null) {
itemOffset = _page - idx;
}
} else {
BuildContext storageContext = carouselState!
.pageController!.position.context.storageContext;
final double? previousSavedPosition =
PageStorage.of(storageContext)?.readState(storageContext)
as double?;
if (previousSavedPosition != null) {
itemOffset = previousSavedPosition - idx.toDouble();
} else {
itemOffset =
carouselState!.realPage.toDouble() - idx.toDouble();
}
}
final double enlargeFactor =
options.enlargeFactor.clamp(0.0, 1.0);
final num distortionRatio =
(1 - (itemOffset.abs() * enlargeFactor)).clamp(0.0, 1.0);
distortionValue =
Curves.easeOut.transform(distortionRatio as double);
}
final double height = widget.options.height ??
MediaQuery.of(context).size.width *
(1 / widget.options.aspectRatio);
if (widget.options.scrollDirection == Axis.horizontal) {
return getCenterWrapper(getEnlargeWrapper(child,
height: distortionValue * height,
scale: distortionValue,
itemOffset: itemOffset));
} else {
return getCenterWrapper(getEnlargeWrapper(child,
width: distortionValue * MediaQuery.of(context).size.width,
scale: distortionValue,
itemOffset: itemOffset));
}
},
);
},
));
}
}
class _MultipleGestureRecognizer extends PanGestureRecognizer {}

View File

@ -1,43 +0,0 @@
import 'package:flutter/material.dart';
import 'carousel_options.dart';
class CarouselState {
/// The [CarouselOptions] to create this state
CarouselOptions options;
/// [pageController] is created using the properties passed to the constructor
/// and can be used to control the [PageView] it is passed to.
PageController? pageController;
/// The actual index of the [PageView].
///
/// This value can be ignored unless you know the carousel will be scrolled
/// backwards more then 10000 pages.
/// Defaults to 10000 to simulate infinite backwards scrolling.
int realPage = 10000;
/// The initial index of the [PageView] on [CarouselSlider] init.
///
int initialPage = 0;
/// The widgets count that should be shown at carousel
int? itemCount;
/// Will be called when using pageController to go to next page or
/// previous page. It will clear the autoPlay timer.
/// Internal use only
Function onResetTimer;
/// Will be called when using pageController to go to next page or
/// previous page. It will restart the autoPlay timer.
/// Internal use only
Function onResumeTimer;
/// The callback to set the Reason Carousel changed
Function(CarouselPageChangedReason) changeMode;
CarouselState(
this.options, this.onResetTimer, this.onResumeTimer, this.changeMode);
}

View File

@ -1,23 +0,0 @@
/// Converts an index of a set size to the corresponding index of a collection of another size
/// as if they were circular.
///
/// Takes a [position] from collection Foo, a [base] from where Foo's index originated
/// and the [length] of a second collection Baa, for which the correlating index is sought.
///
/// For example; We have a Carousel of 10000(simulating infinity) but only 6 images.
/// We need to repeat the images to give the illusion of a never ending stream.
/// By calling _getRealIndex with position and base we get an offset.
/// This offset modulo our length, 6, will return a number between 0 and 5, which represent the image
/// to be placed in the given position.
int getRealIndex(int position, int base, int? length) {
final int offset = position - base;
return remainder(offset, length);
}
/// Returns the remainder of the modulo operation [input] % [source], and adjust it for
/// negative values.
int remainder(int input, int? source) {
if (source == 0) return 0;
final int result = input % source!;
return result < 0 ? source + result : result;
}

View File

@ -4,6 +4,7 @@ import 'package:mymuseum_visitapp/Components/AdminPopup.dart';
import 'package:mymuseum_visitapp/Components/LanguageSelection.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Home/home.dart';
import 'package:mymuseum_visitapp/Screens/Home/home_3.0.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
@ -77,7 +78,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
visitAppContext.isScanningBeacons = false;
//Navigator.of(context).pop();
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(
builder: (context) => const HomePage(),
builder: (context) => const HomePage3(),
),(route) => false);
});
}
@ -96,14 +97,14 @@ class _CustomAppBarState extends State<CustomAppBar> {
child: visitAppContext.isMaximizeTextSize ? const Icon(Icons.text_fields, color: Colors.white) : const Icon(Icons.format_size, color: Colors.white)
),
),
Padding(
/*Padding(
padding: const EdgeInsets.only(right: 5.0),
child: SizedBox(
width: 50,
height: 50,
child: LanguageSelection()
)
),
),*/
],
flexibleSpace: Container(
decoration: const BoxDecoration(

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
//import 'package:flutter_svg_provider/flutter_svg_provider.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
@ -48,9 +48,10 @@ class _LanguageSelection extends State<LanguageSelection> with TickerProviderSta
}
return PopupMenuButton(
color: kMainColor.withValues(alpha: 0.65),
icon: Container(
height: size.height *0.07,
width: size.width *0.07,
height: size.height *0.08,
width: size.width *0.08,
decoration: flagDecoration(selectedLanguage!),
),
itemBuilder: (context){

View File

@ -26,9 +26,16 @@ class _ScannerBoutonState extends State<ScannerBouton> {
return InkWell(
onTap: _onItemTapped,
child: Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: kMainColor1,
color: kMainColor1.withValues(alpha: 0.6),
boxShadow: [
BoxShadow(
offset: const Offset(0, 1.5),
blurRadius: 3.5,
color: kConfigurationColor.withValues(alpha: 0.6), // Black color with 12% opacity
)
],
),
height: 85.0,
width: 85.0,

View File

@ -1,16 +1,14 @@
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/SlideFromRouteRight.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Article/article_page.dart';
import 'package:mymuseum_visitapp/Screens/Quizz/quizz_page.dart';
import 'package:mymuseum_visitapp/Screens/section_page.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class ScannerDialog extends StatefulWidget {
const ScannerDialog({Key? key, required this.appContext}) : super(key: key);
@ -22,19 +20,16 @@ class ScannerDialog extends StatefulWidget {
}
class _ScannerDialogState extends State<ScannerDialog> {
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
@ -49,201 +44,126 @@ class _ScannerDialogState extends State<ScannerDialog> {
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),
),
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);
},
)),
child: MobileScanner(
controller: controller,
//allowDuplicates: false,
onDetect: (barcodes) => _onDetect(barcodes),
),
),
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 ?? "";
if (barcode.format == BarcodeFormat.qrCode && code.isNotEmpty) {
isProcessing = true;
RegExp regExp = RegExp(r'^(?:https:\/\/web\.myinfomate\.be\/([^\/]+)\/([^\/]+)\/([^\/]+)|([^\/]+))$');
RegExp regExp2 = RegExp(r'^(?:https:\/\/web\.mymuseum\.be\/([^\/]+)\/([^\/]+)\/([^\/]+)|([^\/]+))$');
var match = regExp.firstMatch(code);
var match2 = regExp2.firstMatch(code);
String? instanceId;
String? configurationId;
String? sectionId;
if (match != null) {
if(match == null) {
instanceId = match2?.group(1);
configurationId = match2?.group(2);
sectionId = match2?.group(3) ?? match2?.group(4);
} else {
instanceId = match.group(1);
configurationId = match.group(2);
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()}')),
);*/
if ((match == null && match2 == null) || sectionId == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("L'URL ne correspond pas au format attendu."), backgroundColor: kMainColor2),
);
Navigator.of(context).pop();
return;
}
VisitAppContext visitAppContext = widget.appContext!.getContext();
if(!visitAppContext.sectionIds!.contains(sectionId) || sectionId == null) {
if (visitAppContext.sectionIds == null || !visitAppContext.sectionIds!.contains(sectionId)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(TranslationHelper.getFromLocale('invalidQRCode', widget.appContext!.getContext())), backgroundColor: kMainColor2),
SnackBar(content: Text(TranslationHelper.getFromLocale('invalidQRCode', visitAppContext)), backgroundColor: kMainColor2),
);
Navigator.of(context).pop();
} else {
SectionDTO section = visitAppContext.currentSections!.firstWhere((cs) => cs!.id == sectionId)!;
switch(section.type) {
case SectionType.Article:
Navigator.pushReplacement(
dynamic rawSection = visitAppContext.currentSections!.firstWhere((cs) => cs!['id'] == sectionId)!;
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return ArticlePage(visitAppContextIn: visitAppContext, articleId: section.id!);
},
),
SlideFromRightRoute(page: SectionPage(
configuration: visitAppContext.configuration!,
rawSection: rawSection,
visitAppContextIn: visitAppContext,
sectionId: rawSection['id'],
)),
);
break;
case SectionType.Quizz:
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) {
return QuizzPage(visitAppContextIn: visitAppContext, sectionId: section.id!);
},
),
);
break;
}
}
}
});
});
}
Future<void> _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) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('no Permission')),
);
// You can request multiple permissions at once.
Map<Permission, PermissionStatus> statuses = await [
Permission.camera,
].request();
print(statuses[Permission.camera]);
print(status);
}
}
}
@override
void dispose() {
controller?.dispose();
controller.dispose();
super.dispose();
}
}
showScannerDialog(BuildContext context, AppContext appContext) {
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
content: ScannerDialog(appContext: appContext),
contentPadding: EdgeInsets.zero,
), context: context
),
);
}

View File

@ -8,9 +8,11 @@ class SearchBox extends StatefulWidget {
const SearchBox({
Key? key,
this.onChanged,
this.width,
}) : super(key: key);
final ValueChanged? onChanged;
final double? width;
@override
State<SearchBox> createState() => _SearchBoxState();
@ -25,17 +27,18 @@ class _SearchBoxState extends State<SearchBox> {
final appContext = Provider.of<AppContext>(context);
return Container(
width: size.width*0.65,
width: widget.width ?? size.width*0.65,
margin: const EdgeInsets.all(kDefaultPadding),
padding: const EdgeInsets.symmetric(
horizontal: kDefaultPadding,
vertical: kDefaultPadding / 4, // 5 top and bottom
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.4),
color: Colors.white.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(12),
),
child: TextFormField(
cursorColor: kMainColor,
controller: _controller,
onChanged: widget.onChanged,
style: const TextStyle(color: Colors.white),

View File

@ -26,10 +26,11 @@ class _SearchNumberBoxState extends State<SearchNumberBox> {
vertical: kDefaultPadding / 4, // 5 top and bottom
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.4),
color: Colors.white.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(12),
),
child: TextFormField(
cursorColor: kMainColor,
controller: _controller,
onChanged: widget.onChanged,
keyboardType: TextInputType.number,

View File

@ -3,7 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';

View File

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
class SlideFromRightRoute extends PageRouteBuilder {
final Widget page;
SlideFromRightRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0); // départ à droite (hors écran)
const end = Offset.zero; // arrivée position normale
const curve = Curves.easeInOut;
final tween =
Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
final offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
);
}

View File

@ -1,10 +1,11 @@
import 'dart:convert';
import 'package:mymuseum_visitapp/Components/Carousel/carousel_slider.dart' as cs;
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/ShowImagePopup.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Map/marker_view.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
@ -22,12 +23,12 @@ class SliderImagesWidget extends StatefulWidget {
class _SliderImagesWidget extends State<SliderImagesWidget> {
List<ResourceModel?> resourcesInWidget = [];
late cs.CarouselController? sliderController;
late CarouselSliderController? sliderController;
final ValueNotifier<int> currentIndex = ValueNotifier<int>(1);
@override
void initState() {
sliderController = cs.CarouselController();
sliderController = CarouselSliderController();
resourcesInWidget = widget.resources;
super.initState();
}
@ -53,10 +54,10 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if(resourcesInWidget.isNotEmpty)
cs.CarouselSlider(
CarouselSlider(
carouselController: sliderController,
options: cs.CarouselOptions(
onPageChanged: (int index, cs.CarouselPageChangedReason reason) {
options: CarouselOptions(
onPageChanged: (int index, CarouselPageChangedReason reason) {
//setState(() {
//print("SET STATE");
currentIndex.value = index + 1;
@ -69,8 +70,12 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
items: resourcesInWidget.map<Widget>((i) {
return Builder(
builder: (BuildContext context) {
AppContext appContext = Provider.of<AppContext>(context);
ContentDTO contentDTO = ContentDTO(resourceId: i!.id, resource: ResourceDTO(id: i.id, type: i.type, label: i.label, url: i.source));
var resourcetoShow = getElementForResource(context, appContext, contentDTO, true);
return resourcetoShow;
//print(widget.imagesDTO[currentIndex-1]);
return FutureBuilder(
/*return FutureBuilder(
future: ApiService.getResource(appContext, visitAppContext.configuration!, i!.id!),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
return Padding(
@ -118,7 +123,7 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
),
);
}
);
);*/
},
);
}).toList(),

View File

@ -0,0 +1,299 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:provider/provider.dart';
import 'package:just_audio/just_audio.dart';
import 'package:just_audio_cache/just_audio_cache.dart';
class AudioPlayerFloatingContainer extends StatefulWidget {
const AudioPlayerFloatingContainer({Key? key, required this.file, required this.audioBytes, required this.resourceURl, required this.isAuto}) : super(key: key);
final File? file;
final Uint8List? audioBytes;
final String resourceURl;
final bool isAuto;
@override
State<AudioPlayerFloatingContainer> createState() => _AudioPlayerFloatingContainerState();
}
class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContainer> {
AudioPlayer player = AudioPlayer();
Uint8List? audiobytes = null;
bool isplaying = false;
bool audioplayed = false;
int currentpos = 0;
int maxduration = 100;
Duration? durationAudio;
String currentpostlabel = "00:00";
bool _isDisposed = false;
@override
void initState() {
//print("IN INITSTATE AUDDDIOOOO");
Future.delayed(Duration.zero, () async {
if(widget.audioBytes != null) {
audiobytes = widget.audioBytes!;
}
if(widget.file != null) {
audiobytes = await fileToUint8List(widget.file!);
}
player.durationStream.listen((Duration? d) { //get the duration of audio
if(d != null) {
maxduration = d.inSeconds;
durationAudio = d;
}
});
//player.bufferedPositionStream
player.positionStream.listen((event) {
if(durationAudio != null) {
currentpos = event.inMilliseconds; //get the current position of playing audio
//generating the duration label
int shours = Duration(milliseconds:durationAudio!.inMilliseconds - currentpos).inHours;
int sminutes = Duration(milliseconds:durationAudio!.inMilliseconds - currentpos).inMinutes;
int sseconds = Duration(milliseconds:durationAudio!.inMilliseconds - currentpos).inSeconds;
int rminutes = sminutes - (shours * 60);
int rseconds = sseconds - (sminutes * 60 + shours * 60 * 60);
String minutesToShow = rminutes < 10 ? '0$rminutes': rminutes.toString();
String secondsToShow = rseconds < 10 ? '0$rseconds': rseconds.toString();
currentpostlabel = "$minutesToShow:$secondsToShow";
setState(() {
//refresh the UI
if(currentpos > player.duration!.inMilliseconds) {
print("RESET ALL");
player.stop();
player.seek(const Duration(seconds: 0));
isplaying = false;
audioplayed = false;
currentpostlabel = "00:00";
}
});
}
});
/*player.onPositionChanged.listen((Duration p){
currentpos = p.inMilliseconds; //get the current position of playing audio
//generating the duration label
int shours = Duration(milliseconds:currentpos).inHours;
int sminutes = Duration(milliseconds:currentpos).inMinutes;
int sseconds = Duration(milliseconds:currentpos).inSeconds;
int rminutes = sminutes - (shours * 60);
int rseconds = sseconds - (sminutes * 60 + shours * 60 * 60);
String minutesToShow = rminutes < 10 ? '0$rminutes': rminutes.toString();
String secondsToShow = rseconds < 10 ? '0$rseconds': rseconds.toString();
currentpostlabel = "$minutesToShow:$secondsToShow";
setState(() {
//refresh the UI
});
});*/
/*if(audiobytes != null) {
print("GOT AUDIOBYYYTES - LOCALLY SOSO");
await player.setAudioSource(LoadedSource(audiobytes!));
} else {
print("GET SOUND BY URL");
await player.dynamicSet(url: widget.resourceURl);
}*/
if(widget.isAuto) {
//player.play(BytesSource(audiobytes));
//
player.play();
setState(() {
isplaying = true;
audioplayed = true;
});
}
});
super.initState();
Future.microtask(() async {
try {
if (_isDisposed || !mounted) return;
if (widget.audioBytes != null) {
await player.setAudioSource(LoadedSource(widget.audioBytes!));
} else {
await player.dynamicSet(url: widget.resourceURl);
}
if (_isDisposed || !mounted) return;
if (widget.isAuto) {
await player.play();
setState(() {
isplaying = true;
audioplayed = true;
});
}
} catch (e, stack) {
debugPrint('Audio error: $e');
debugPrintStack(stackTrace: stack);
}
});
}
@override
void dispose() {
_isDisposed = true;
player.stop();
player.dispose();
super.dispose();
}
Future<Uint8List> fileToUint8List(File file) async {
List<int> bytes = await file.readAsBytes();
return Uint8List.fromList(bytes);
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
return InkWell(
onTap: () async {
if(!isplaying && !audioplayed){
//player.play(BytesSource(audiobytes));
//await player.setUrl(widget.resourceURl);
player.play();
setState(() {
isplaying = true;
audioplayed = true;
});
}else if(audioplayed && !isplaying){
//player.resume();
player.play();
setState(() {
isplaying = true;
audioplayed = true;
});
}else{
player.pause();
setState(() {
isplaying = false;
});
}
},
child: Container(
width: size.width,
decoration: BoxDecoration(
color: Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)).withValues(alpha: 0.7),
borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0),
),
child: isplaying ? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.pause),
Text(currentpostlabel),
],
) : audioplayed ? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.play_arrow),
Text(currentpostlabel),
],
): const Icon(Icons.play_arrow),
/*Column(
children: [
//Text(currentpostlabel, style: const TextStyle(fontSize: 25)),
Wrap(
spacing: 10,
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: kSecondColor, // Background color
),
onPressed: () async {
if(!isplaying && !audioplayed){
//player.play(BytesSource(audiobytes));
await player.setAudioSource(LoadedSource(audiobytes));
player.play();
setState(() {
isplaying = true;
audioplayed = true;
});
}else if(audioplayed && !isplaying){
//player.resume();
player.play();
setState(() {
isplaying = true;
audioplayed = true;
});
}else{
player.pause();
setState(() {
isplaying = false;
});
}
},
icon: Icon(isplaying?Icons.pause:Icons.play_arrow),
//label:Text(isplaying?TranslationHelper.getFromLocale("pause", appContext.getContext()):TranslationHelper.getFromLocale("play", appContext.getContext()))
),
/*ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: kSecondColor, // Background color
),
onPressed: () async {
player.stop();
player.seek(const Duration(seconds: 0));
setState(() {
isplaying = false;
audioplayed = false;
currentpostlabel = "00:00";
});
},
icon: const Icon(Icons.stop),
//label: Text(TranslationHelper.getFromLocale("stop", appContext.getContext()))
),*/
],
)
],
),*/
),
);
}
}
// Feed your own stream of bytes into the player
class LoadedSource extends StreamAudioSource {
final List<int> bytes;
LoadedSource(this.bytes);
@override
Future<StreamAudioResponse> request([int? start, int? end]) async {
start ??= 0;
end ??= bytes.length;
return StreamAudioResponse(
sourceLength: bytes.length,
contentLength: end - start,
offset: start,
stream: Stream.value(bytes.sublist(start, end)),
contentType: 'audio/mpeg',
);
}
}

View File

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

View File

@ -1,23 +0,0 @@
import 'package:flutter/material.dart';
class FadeRoute extends PageRouteBuilder {
final Widget page;
FadeRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = 0.0;
const end = 1.0;
const curve = Curves.easeInOut;
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
final opacityAnimation = animation.drive(tween);
return FadeTransition(
opacity: opacityAnimation,
child: child,
);
},
);
}

View File

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';

View File

@ -0,0 +1,92 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/audio_player.dart';
import 'package:mymuseum_visitapp/Components/video_viewer.dart';
import 'package:mymuseum_visitapp/Components/video_viewer_youtube.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'cached_custom_resource.dart';
showElementForResource(ResourceDTO resourceDTO, AppContext appContext, bool isAuto, bool webView) {
VisitAppContext visitAppContext = appContext.getContext();
Color primaryColor = Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16));
return CachedCustomResource(resourceDTO: resourceDTO, isAuto: isAuto, webView: webView);
switch(resourceDTO.type) {
case ResourceType.Image:
case ResourceType.ImageUrl:
return CachedNetworkImage(
imageUrl: resourceDTO.url!,
fit: BoxFit.fill,
progressIndicatorBuilder: (context, url, downloadProgress) =>
CircularProgressIndicator(value: downloadProgress.progress, color: primaryColor),
errorWidget: (context, url, error) => Icon(Icons.error),
);
/*return Image.network(
resourceDTO.url!,
fit:BoxFit.fill,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(
color: primaryColor,
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
);*/
case ResourceType.Audio:
return AudioPlayerFloatingContainer(file: null, audioBytes: null, resourceURl: resourceDTO.url!, isAuto: isAuto);
/*return FutureBuilder(
future: getAudio(resourceDTO.url, appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
Size size = MediaQuery.of(context).size;
if (snapshot.connectionState == ConnectionState.done) {
var audioBytes;
if(snapshot.data != null) {
print("snapshot.data");
print(snapshot.data);
audioBytes = snapshot.data;
//this.player.playBytes(audiobytes);
}
return AudioPlayerFloatingContainer(audioBytes: audioBytes, resourceURl: resourceDTO.url, isAuto: true);
} else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
} else {
return Center(
child: Container(
//height: size.height * 0.2,
width: size.width * 0.2,
child: LoadingCommon()
)
);
}
}
);*/
case ResourceType.Video:
if(resourceDTO.url == null) {
return Center(child: Text("Error loading video"));
} else {
return VideoViewer(file: null, videoUrl: resourceDTO.url!);
}
case ResourceType.VideoUrl:
if(resourceDTO.url == null) {
return Center(child: Text("Error loading video"));
} else {
return VideoViewerYoutube(videoUrl: resourceDTO.url!, isAuto: isAuto, webView: webView);
}
}
}

View File

@ -0,0 +1,94 @@
import 'dart:io';
//import 'package:cached_video_player/cached_video_player.dart';
import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:video_player/video_player.dart';
import '../../constants.dart';
class VideoViewer extends StatefulWidget {
final String videoUrl;
final File? file;
VideoViewer({required this.videoUrl, required this.file});
@override
_VideoViewer createState() => _VideoViewer();
}
class _VideoViewer extends State<VideoViewer> {
late VideoPlayerController _controller; // Cached
@override
void initState() {
super.initState();
if(widget.file != null) {
_controller = VideoPlayerController.file(widget.file!) // Uri.parse() // Cached
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
} else {
_controller = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)) // Uri.parse()
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Center(
child: InkWell(
onTap: () {
setState(() {
if(_controller.value.isInitialized) {
if(_controller.value.isPlaying) {
_controller.pause();
} else {
_controller.play();
}
}
});
},
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Center(
child: Container(
child: LoadingCommon()
)
),
),
),
if(!_controller.value.isPlaying && _controller.value.isInitialized)
Center(
child: FloatingActionButton(
backgroundColor: kMainColor.withValues(alpha: 0.8),
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
color: Colors.white),
),
)
],
);
}
}

View File

@ -0,0 +1,99 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart' as iframe;
//import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class VideoViewerYoutube extends StatefulWidget {
final String videoUrl;
final bool isAuto;
final bool webView;
VideoViewerYoutube({required this.videoUrl, required this.isAuto, this.webView = false});
@override
_VideoViewerYoutube createState() => _VideoViewerYoutube();
}
class _VideoViewerYoutube extends State<VideoViewerYoutube> {
iframe.YoutubePlayer? _videoViewWeb;
//YoutubePlayer? _videoView;
@override
void initState() {
String? videoId;
if (widget.videoUrl.isNotEmpty ) {
//videoId = YoutubePlayer.convertUrlToId(widget.videoUrl);
if (true) {
final _controllerWeb = iframe.YoutubePlayerController(
params: iframe.YoutubePlayerParams(
mute: false,
showControls: false,
showFullscreenButton: false,
loop: false,
showVideoAnnotations: false,
strictRelatedVideos: false,
enableKeyboard: false,
enableCaption: false,
pointerEvents: iframe.PointerEvents.auto
),
);
_controllerWeb.loadVideo(widget.videoUrl);
if(!widget.isAuto) {
_controllerWeb.stopVideo();
}
_videoViewWeb = iframe.YoutubePlayer(
controller: _controllerWeb,
//showVideoProgressIndicator: false,
/*progressIndicatorColor: Colors.amber,
progressColors: ProgressBarColors(
playedColor: Colors.amber,
handleColor: Colors.amberAccent,
),*/
);
} else /*{
// Cause memory issue on tablet
videoId = YoutubePlayer.convertUrlToId(widget.videoUrl);
YoutubePlayerController _controller = YoutubePlayerController(
initialVideoId: videoId!,
flags: YoutubePlayerFlags(
autoPlay: widget.isAuto,
controlsVisibleAtStart: false,
loop: true,
hideControls: false,
hideThumbnail: false,
),
);
_videoView = YoutubePlayer(
controller: _controller,
//showVideoProgressIndicator: false,
progressIndicatorColor: Colors.amber,
progressColors: ProgressBarColors(
playedColor: Colors.amber,
handleColor: Colors.amberAccent,
),
);
}*/
super.initState();
}
}
@override
void dispose() {
//_videoView = null;
_videoViewWeb = null;
super.dispose();
}
@override
Widget build(BuildContext context) => widget.videoUrl.isNotEmpty ?
_videoViewWeb!: //(widget.webView ? _videoViewWeb! : _videoView!)
const Center(child: Text("La vidéo ne peut pas être affichée, l'url est incorrecte", style: TextStyle(fontSize: kNoneInfoOrIncorrect)));
}

View File

@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Models/articleRead.dart';
import 'package:mymuseum_visitapp/Models/beaconSection.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
@ -400,7 +400,7 @@ class DatabaseHelper {
imageSource: element["imageSource"],
configurationId: element["configurationId"],
type: SectionType.values[element["type"]],
data: element["data"],
// data: element["data"], // TODO section data
dateCreation: DateTime.tryParse(element["dateCreation"]),
order: int.parse(element["orderOfElement"]),
);

View File

@ -0,0 +1,35 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
class ImageCustomProvider {
static ImageProvider<Object> getImageProvider(AppContext appContext, String? imageId, String imageSource) {
VisitAppContext visitAppContext = appContext.getContext();
try {
if(appContext.getContext().localPath != null && visitAppContext.configuration != null) {
Directory configurationDirectory = Directory('${visitAppContext.localPath!}/${visitAppContext.configuration!.id!}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
if(imageId != null && fileList.any((fileL) => fileL.uri.pathSegments.last.contains(imageId))) {
File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(imageId)).path);
print("FILE EXISTT");
return FileImage(file);
}
}
} catch(e) {
print("Error getImageProvider");
print(e.toString());
}
// If localpath not found or file missing
print("MISSINGG FILE");
print(imageId);
return CachedNetworkImageProvider(imageSource);
}
}

View File

@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Models/beaconSection.dart';
class ModelsHelper {
@ -35,7 +35,7 @@ class ModelsHelper {
'isSubSection': section.isSubSection,
'parentId': section.parentId,
'type': section.type!.value,
'data': section.data,
//'data': section.data, // TODO section data
'dateCreation': section.dateCreation!.toUtc().toIso8601String(),
'orderOfElement': section.order,
};

View File

@ -1,4 +1,4 @@
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/translations.dart';

View File

@ -1,4 +1,4 @@
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
class ResponseSubDTO {
List<TranslationAndResourceDTO>? label;

128
lib/Models/agenda.dart Normal file
View File

@ -0,0 +1,128 @@
import 'dart:convert';
class Agenda {
List<EventAgenda> events;
Agenda({required this.events});
factory Agenda.fromJson(String jsonString) {
final List<dynamic> jsonList = json.decode(jsonString);
List<EventAgenda> events = [];
for (var eventData in jsonList) {
try {
events.add(EventAgenda.fromJson(eventData));
} catch(e) {
print("Erreur lors du parsing du json : ${e.toString()}");
}
}
return Agenda(events: events);
}
}
class EventAgenda {
String? name;
String? description;
String? type;
DateTime? dateAdded;
DateTime? dateFrom;
DateTime? dateTo;
String? dateHour;
EventAddress? address;
String? website;
String? phone;
String? idVideoYoutube;
String? email;
String? image;
EventAgenda({
required this.name,
required this.description,
required this.type,
required this.dateAdded,
required this.dateFrom,
required this.dateTo,
required this.dateHour,
required this.address,
required this.website,
required this.phone,
required this.idVideoYoutube,
required this.email,
required this.image,
});
factory EventAgenda.fromJson(Map<String, dynamic> json) {
return EventAgenda(
name: json['name'],
description: json['description'],
type: json['type'] is !bool ? json['type'] : null,
dateAdded: json['date_added'] != null && json['date_added'].isNotEmpty ? DateTime.parse(json['date_added']) : null,
dateFrom: json['date_from'] != null && json['date_from'].isNotEmpty ? DateTime.parse(json['date_from']) : null,
dateTo: json['date_to'] != null && json['date_to'].isNotEmpty ? DateTime.parse(json['date_to']) : null,
dateHour: json['date_hour'],
address: json['address'] is !bool ? EventAddress.fromJson(json['address']) : null,
website: json['website'],
phone: json['phone'],
idVideoYoutube: json['id_video_youtube'],
email: json['email'],
image: json['image'],
);
}
}
class EventAddress {
String? address;
dynamic lat;
dynamic lng;
int? zoom;
String? placeId;
String? name;
String? streetNumber;
String? streetName;
String? streetNameShort;
String? city;
String? state;
String? stateShort;
String? postCode;
String? country;
String? countryShort;
EventAddress({
required this.address,
required this.lat,
required this.lng,
required this.zoom,
required this.placeId,
required this.name,
required this.streetNumber,
required this.streetName,
required this.streetNameShort,
required this.city,
required this.state,
required this.stateShort,
required this.postCode,
required this.country,
required this.countryShort,
});
factory EventAddress.fromJson(Map<String, dynamic> json) {
return EventAddress(
address: json['address'],
lat: json['lat'],
lng: json['lng'],
zoom: json['zoom'],
placeId: json['place_id'],
name: json['name'],
streetNumber: json['street_number'],
streetName: json['street_name'],
streetNameShort: json['street_name_short'],
city: json['city'],
state: json['state'],
stateShort: json['state_short'],
postCode: json['post_code'],
country: json['country'],
countryShort: json['country_short'],
);
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
class SectionRead {
String id = "";

View File

@ -1,4 +1,4 @@
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
class BeaconSection {
int? minorBeaconId;

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
class ResourceModel {
String? id = "";

View File

@ -1,4 +1,4 @@
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
class Translation {
String? language = "";

View File

@ -1,28 +1,30 @@
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Models/articleRead.dart';
import 'package:mymuseum_visitapp/Models/beaconSection.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/client.dart';
class VisitAppContext with ChangeNotifier {
Client clientAPI = Client("https://api.myinfomate.be"); // Replace by https://api.mymuseum.be //http://192.168.31.140:8089
Client clientAPI = Client("https://api.mymuseum.be"); // Replace by https://api.mymuseum.be //http://192.168.31.140:8089 // https://api.myinfomate.be // http://192.168.31.228:5000
String? id = "";
String? language = "";
String? instanceId = "633ee379d9405f32f166f047"; // 63514fd67ed8c735aaa4b8f2 MyInfoMate test instance -- Fort de Saint-Héribert Mymuseum instance id : 633ee379d9405f32f166f047 // 63514fd67ed8c735aaa4b8f1 Mymuseum test // MDLF instance 65ccc67265373befd15be511
String? instanceId = "63514fd67ed8c735aaa4b8f2"; // 63514fd67ed8c735aaa4b8f2 MyInfoMate test instance -- Fort de Saint-Héribert Mymuseum instance id : 633ee379d9405f32f166f047 // 63514fd67ed8c735aaa4b8f1 Mymuseum test // MDLF instance 65ccc67265373befd15be511
List<ConfigurationDTO>? configurations;
ConfigurationDTO? configuration;
List<String?>? sectionIds; // Use to valid QR code found
List<BeaconSection?>? beaconSections;
List<SectionDTO?>? currentSections;
List<dynamic>? currentSections;
List<SectionRead> readSections = [];
bool isContentCurrentlyShown = false;
bool isScanningBeacons = false;
bool isScanBeaconAlreadyAllowed = false;
bool isMaximizeTextSize = false;
Size? puzzleSize;
List<ResourceModel> audiosNotWorking = [];
bool? isAdmin = false;

217
lib/Models/weatherData.dart Normal file
View File

@ -0,0 +1,217 @@
class WeatherData {
String? cod;
int? message;
int? cnt;
List<WeatherForecast>? list;
City? city;
WeatherData({this.cod, this.message, this.cnt, this.list, this.city});
factory WeatherData.fromJson(Map<String, dynamic> json) {
return WeatherData(
cod: json['cod'],
message: json['message'],
cnt: json['cnt'],
list: (json['list'] as List?)?.map((item) => WeatherForecast.fromJson(item)).toList(),
city: json['city'] != null ? City.fromJson(json['city']) : null,
);
}
}
class WeatherForecast {
int? dt;
MainWeatherData? main;
List<Weather>? weather;
Clouds? clouds;
Wind? wind;
int? visibility;
dynamic pop;
Rain? rain;
Sys? sys;
String? dtTxt;
WeatherForecast({
this.dt,
this.main,
this.weather,
this.clouds,
this.wind,
this.visibility,
this.pop,
this.rain,
this.sys,
this.dtTxt,
});
factory WeatherForecast.fromJson(Map<String, dynamic> json) {
return WeatherForecast(
dt: json['dt'],
main: json['main'] != null ? MainWeatherData.fromJson(json['main']) : null,
weather: (json['weather'] as List?)?.map((item) => Weather.fromJson(item)).toList(),
clouds: json['clouds'] != null ? Clouds.fromJson(json['clouds']) : null,
wind: json['wind'] != null ? Wind.fromJson(json['wind']) : null,
visibility: json['visibility'],
pop: json['pop'],
rain: json['rain'] != null ? Rain.fromJson(json['rain']) : null,
sys: json['sys'] != null ? Sys.fromJson(json['sys']) : null,
dtTxt: json['dt_txt'],
);
}
}
class MainWeatherData {
double? temp;
double? feelsLike;
double? tempMin;
double? tempMax;
int? pressure;
int? seaLevel;
int? grndLevel;
int? humidity;
double? tempKf;
MainWeatherData({
this.temp,
this.feelsLike,
this.tempMin,
this.tempMax,
this.pressure,
this.seaLevel,
this.grndLevel,
this.humidity,
this.tempKf,
});
factory MainWeatherData.fromJson(Map<String, dynamic> json) {
return MainWeatherData(
temp: json['temp']?.toDouble(),
feelsLike: json['feels_like']?.toDouble(),
tempMin: json['temp_min']?.toDouble(),
tempMax: json['temp_max']?.toDouble(),
pressure: json['pressure'],
seaLevel: json['sea_level'],
grndLevel: json['grnd_level'],
humidity: json['humidity'],
tempKf: json['temp_kf']?.toDouble(),
);
}
}
class Weather {
int? id;
String? main;
String? description;
String? icon;
Weather({this.id, this.main, this.description, this.icon});
factory Weather.fromJson(Map<String, dynamic> json) {
return Weather(
id: json['id'],
main: json['main'],
description: json['description'],
icon: json['icon'],
);
}
}
class Clouds {
int? all;
Clouds({this.all});
factory Clouds.fromJson(Map<String, dynamic> json) {
return Clouds(
all: json['all'],
);
}
}
class Wind {
double? speed;
int? deg;
double? gust;
Wind({this.speed, this.deg, this.gust});
factory Wind.fromJson(Map<String, dynamic> json) {
return Wind(
speed: json['speed']?.toDouble(),
deg: json['deg'],
gust: json['gust']?.toDouble(),
);
}
}
class Rain {
double? h3;
Rain({this.h3});
factory Rain.fromJson(Map<String, dynamic> json) {
return Rain(
h3: json['3h']?.toDouble(),
);
}
}
class Sys {
String? pod;
Sys({this.pod});
factory Sys.fromJson(Map<String, dynamic> json) {
return Sys(
pod: json['pod'],
);
}
}
class City {
int? id;
String? name;
Coord? coord;
String? country;
int? population;
int? timezone;
int? sunrise;
int? sunset;
City({
this.id,
this.name,
this.coord,
this.country,
this.population,
this.timezone,
this.sunrise,
this.sunset,
});
factory City.fromJson(Map<String, dynamic> json) {
return City(
id: json['id'],
name: json['name'],
coord: json['coord'] != null ? Coord.fromJson(json['coord']) : null,
country: json['country'],
population: json['population'],
timezone: json['timezone'],
sunrise: json['sunrise'],
sunset: json['sunset'],
);
}
}
class Coord {
double? lat;
double? lon;
Coord({this.lat, this.lon});
factory Coord.fromJson(Map<String, dynamic> json) {
return Coord(
lat: json['lat']?.toDouble(),
lon: json['lon']?.toDouble(),
);
}
}

View File

@ -3,7 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/Loading.dart';
import 'package:mymuseum_visitapp/Components/SearchBox.dart';
import 'package:mymuseum_visitapp/Components/SearchNumberBox.dart';
@ -12,7 +12,7 @@ import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/beaconSection.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Article/article_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/client.dart';

View File

@ -0,0 +1,294 @@
import 'dart:convert';
import 'package:diacritic/diacritic.dart';
import 'package:flutter/material.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/SlideFromRouteRight.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Components/SearchBox.dart';
import 'package:mymuseum_visitapp/Components/SearchNumberBox.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/section_page.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
import 'section_card.dart';
class Body extends StatefulWidget {
const Body({Key? key, required this.configuration}) : super(key: key);
final ConfigurationDTO configuration;
@override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
late List<SectionDTO> sections;
late List<SectionDTO> _allSections;
late List<dynamic> rawSections;
String? searchValue;
int? searchNumberValue;
final ValueNotifier<List<SectionDTO>> filteredSections = ValueNotifier([]);
late Future<List<SectionDTO>> _futureSections;
@override
void initState() {
super.initState();
final appContext = Provider.of<AppContext>(context, listen: false);
_futureSections = getSections(appContext);
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
Color? primaryColor = widget.configuration.primaryColor != null ? Color(int.parse(widget.configuration.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : null;
return SafeArea(
bottom: false,
top: false,
child: Stack(
children: [
Hero(
tag: widget.configuration.id!,
child: Container(
height: size.height * 0.28,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: widget.configuration.imageSource == null ? const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
/*Color(0xFFDD79C2),
Color(0xFFB65FBE),
Color(0xFF9146BA),
Color(0xFF7633B8),
Color(0xFF6528B6),
Color(0xFF6025B6)*/
kMainColor0, //Color(0xFFf6b3c4)
kMainColor1,
kMainColor2,
],
) : null,
image: widget.configuration.imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.65,
image: NetworkImage(
widget.configuration.imageSource!,
),
): null,
),
),
),
Column(
children: <Widget>[
SizedBox(
height: size.height * 0.11,
width: size.width,
child: Stack(
fit: StackFit.expand,
children: [
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
//setState(() {
/**/
Navigator.of(context).pop();
visitAppContext.configuration = null;
visitAppContext.isScanningBeacons = false;
/*Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(
builder: (context) => const HomePage3(),
),(route) => false);*/
//});
},
child: Container(
decoration: BoxDecoration(
color: primaryColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
],
),
),
Row(
children: [
SearchBox(onChanged: (value) {
searchValue = value?.trim();
applyFilters(visitAppContext);
}),
Expanded(
child: SearchNumberBox(onChanged: (value) {
if (value != null && value.isNotEmpty) {
searchNumberValue = int.tryParse(value);
} else {
searchNumberValue = null;
}
FocusScope.of(context).unfocus();
applyFilters(visitAppContext);
}
),
),
],
),
//const SizedBox(height: kDefaultPadding / 2),
Expanded(
child: Stack(
children: <Widget>[
// Our background
Container(
margin: const EdgeInsets.only(top: 0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 2,
offset: Offset(0, 1), // changes position of shadow
),
],
color: kBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
),
FutureBuilder(
future: _futureSections,
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
/*print("SECTIONTODISPA");
print(sectionsToDisplay);*/
return Padding(
padding: const EdgeInsets.only(bottom: 0),
child: RefreshIndicator(
color: kMainColor,
onRefresh: () async {
if(!widget.configuration.isOffline!) {
setState(() {
_futureSections = getSections(appContext);
});
} },
child: ValueListenableBuilder<List<SectionDTO>>(
valueListenable: filteredSections,
builder: (context, value, child) {
return ListView.builder(
itemCount: value.length,
itemBuilder: (context, index) => SectionCard(
configuration: widget.configuration,
itemCount: value.length,
itemIndex: index,
sectionDTO: value[index],
press: () {
Navigator.push(
context,
SlideFromRightRoute(page: SectionPage(
configuration: widget.configuration,
rawSection: rawSections[index],
visitAppContextIn: appContext.getContext(),
sectionId: value[index].id!,
)),
);
},
),
);
}
)
),
);
} else if (snapshot.connectionState == ConnectionState.none) {
return Text(TranslationHelper.getFromLocale("noData", appContext.getContext()));
} else {
return Center(
child: SizedBox(
height: size.height * 0.15,
child: const LoadingCommon()
)
);
}
}
)
],
),
),
],
),
],
),
);
}
Future<List<SectionDTO>> getSections(AppContext appContext) async {
VisitAppContext visitAppContext = appContext.getContext();
if(widget.configuration.isOffline!)
{
// OFFLINE
sections = List<SectionDTO>.from(await DatabaseHelper.instance.getData(DatabaseTableType.sections));
}
else
{
// ONLINE
List<dynamic>? sectionsDownloaded = await ApiService.getAllSections(visitAppContext.clientAPI, widget.configuration.id!);
rawSections = jsonDecode(jsonEncode(sectionsDownloaded));
var rawToSection = jsonDecode(jsonEncode(rawSections)).map((json) => SectionDTO.fromJson(json)).toList();
List<SectionDTO> sectionList = rawToSection.whereType<SectionDTO>().toList();
visitAppContext.currentSections = rawSections;
//print(sectionsDownloaded);
if(sectionList.isNotEmpty) {
sections = sectionList.toList();
//print(sections);
}
}
sections = sections.where((s) => s.configurationId == widget.configuration.id!).toList();
sections.sort((a,b) => a.order!.compareTo(b.order!));
_allSections = sections;
applyFilters(visitAppContext);
return _allSections;
}
void applyFilters(VisitAppContext visitAppContext) {
List<SectionDTO> result = _allSections;
if (searchValue != null && searchValue!.isNotEmpty) {
result = result.where((s) =>
removeDiacritics(TranslationHelper.get(s.title, visitAppContext).toLowerCase())
.contains(removeDiacritics(searchValue!.toLowerCase()))
).toList();
} else if (searchNumberValue != null) {
result = result.where((s) => s.order! + 1 == searchNumberValue).toList();
}
filteredSections.value = result;
}
}

View File

@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
@ -16,12 +16,14 @@ import 'package:provider/provider.dart';
class SectionCard extends StatelessWidget {
const SectionCard({
Key? key,
required this.configuration,
required this.itemIndex,
required this.itemCount,
required this.sectionDTO,
required this.press,
}) : super(key: key);
final ConfigurationDTO configuration;
final int itemIndex;
final int itemCount;
final SectionDTO sectionDTO;
@ -33,7 +35,7 @@ class SectionCard extends StatelessWidget {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
bool isOffline = (appContext.getContext() as VisitAppContext).configuration!.isOffline!;
bool isOffline = configuration.isOffline! ?? false;
return Container(
margin: EdgeInsets.only(
@ -69,7 +71,7 @@ class SectionCard extends StatelessWidget {
),
),
),
if(sectionDTO.imageId != null)
if(sectionDTO.imageId != null ) // && (appContext.getContext() as VisitAppContext).configuration != null
// section main image
Positioned(
top: kDefaultPadding +4.0,
@ -82,7 +84,7 @@ class SectionCard extends StatelessWidget {
// image is square but we add extra 20 + 20 padding thats why width is 200
width: size.width*0.5,
child: FutureBuilder(
future: ApiService.getResource(appContext, (appContext.getContext() as VisitAppContext).configuration!, sectionDTO.imageId!),
future: ApiService.getResource(appContext, configuration, sectionDTO.imageId!),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ClipRRect(
@ -142,10 +144,6 @@ class SectionCard extends StatelessWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: kDefaultPadding),
child: HtmlWidget(TranslationHelper.get(sectionDTO.title, appContext.getContext()))
/*Text(
TranslationHelper.get(sectionDTO.title, appContext.getContext()),
style: Theme.of(context).textTheme.button,
)*/,
),
// it use the available space
const Spacer(),
@ -171,6 +169,16 @@ class SectionCard extends StatelessWidget {
),
),
),
const Positioned(
right: -4,
top: (136/2)-18,
bottom: 25,
child: SizedBox(
width: 18,
height: 18,
child: Icon(Icons.chevron_right, size: 15, color: Colors.white)
),
)
],
),
),

View File

@ -6,16 +6,17 @@ import 'package:flutter/services.dart';
import 'package:flutter_beacon/flutter_beacon.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/ScannerBouton.dart';
import 'package:mymuseum_visitapp/Helpers/requirement_state_controller.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/beaconSection.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Article/article_page.dart';
import 'package:mymuseum_visitapp/Screens/Quizz/quizz_page.dart';
import 'package:mymuseum_visitapp/Screens/Visit/beaconArticleFound.dart';
import 'package:mymuseum_visitapp/Screens/ConfigurationPage/beaconArticleFound.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Quiz/quizz_page.dart';
import 'package:mymuseum_visitapp/Screens/section_page.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:permission_handler/permission_handler.dart';
@ -23,18 +24,18 @@ import 'package:provider/provider.dart';
import 'components/body.dart';
class VisitPage extends StatefulWidget {
const VisitPage({Key? key, required this.configurationId, required this.isAlreadyAllowed}) : super(key: key);
class ConfigurationPage extends StatefulWidget {
const ConfigurationPage({Key? key,required this.configuration, required this.isAlreadyAllowed}) : super(key: key);
final String configurationId;
final ConfigurationDTO configuration;
final bool isAlreadyAllowed;
@override
State<VisitPage> createState() => _VisitPageState();
State<ConfigurationPage> createState() => _ConfigurationPageState();
}
class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
ConfigurationDTO? configuration;
class _ConfigurationPageState extends State<ConfigurationPage> with WidgetsBindingObserver {
//ConfigurationDTO? configuration;
int timeBetweenBeaconPopUp = 20000; // 20 sec
int meterToBeacon = 100; // 15 meters
@ -72,6 +73,14 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
if(widget.isAlreadyAllowed) {
listeningState();
}
WidgetsBinding.instance.addPostFrameCallback((_) {
final appContext = Provider.of<AppContext>(context, listen: false);
VisitAppContext visitAppContext = appContext.getContext();
visitAppContext.configuration = widget.configuration;
visitAppContext.sectionIds = widget.configuration.sectionIds;
appContext.setContext(visitAppContext);
});
//listeningState();
}
@ -287,33 +296,17 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
//visitAppContext.isArticleCurrentlyShown = true;
lastTimePopUpWasClosed = DateTime.now();
switch(beaconSection!.sectionType!) {
case SectionType.Article:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ArticlePage(
builder: (context) => SectionPage(
configuration: widget.configuration,
rawSection: null,
visitAppContextIn: visitAppContext,
articleId: beaconSection.sectionId!,
sectionId: beaconSection!.sectionId!,
),
),
);
break;
case SectionType.Quizz:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuizzPage(
visitAppContextIn: visitAppContext,
sectionId: beaconSection.sectionId!,
),
),
);
break;
default:
// TODO HANDLE, SHOW NOT SUPPORTED
break;
}
},
)
],
@ -337,7 +330,7 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
configuration = visitAppContext.configuration;
//configuration = visitAppContext.configuration;
listener = controller.startStream.listen((flag) async {
print(flag);
@ -348,17 +341,15 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
}
});
return PopScope(
canPop: false,
canPop: true,
child: Scaffold(
appBar: CustomAppBar(
/*appBar: CustomAppBar(
title: TranslationHelper.get(configuration!.title, visitAppContext),
isHomeButton: true,
),
),*/
backgroundColor: kBackgroundGrey,
body: Body(configurationId: configuration!.id), // TODO handle error..
body: Body(configuration: widget.configuration),
floatingActionButton: Stack(
children: [
visitAppContext.beaconSections != null && visitAppContext.beaconSections!.where((bs) => bs!.configurationId == visitAppContext.configuration!.id).isNotEmpty ? Align(

View File

@ -3,12 +3,12 @@ import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/LanguageSelection.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Visit/visit.dart';
import 'package:mymuseum_visitapp/Screens/ConfigurationPage/configuration_page.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/Services/downloadConfiguration.dart';
import 'package:mymuseum_visitapp/app_context.dart';
@ -79,7 +79,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) =>
VisitPage(configurationId: configurations[index].id!, isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
ConfigurationPage(configuration: configurations[index], isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
));
}
@ -110,7 +110,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) =>
VisitPage(configurationId: configurations[index].id!, isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
ConfigurationPage(configuration: configurations[index], isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
));
}
}

View File

@ -4,8 +4,9 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/services.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/ScannerBouton.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/modelsHelper.dart';
@ -14,14 +15,12 @@ import 'package:mymuseum_visitapp/Helpers/requirement_state_controller.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/beaconSection.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Visit/visit.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/Services/downloadConfiguration.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 'configurations_list.dart';
class HomePage extends StatefulWidget {
@ -50,7 +49,9 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
isHomeButton: false,
),
body: SingleChildScrollView(
child: SizedBox(
child: Column(
children: [
SizedBox(
width: size.width,
height: size.height,
child: FutureBuilder(
@ -84,6 +85,27 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
}
}
),
),
/*InkWell(
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) {
return TestAR();
//return XRWithQRScannerPage();
},
),
);
},
child: const SizedBox(
height: 50,
width: 10,
child: Text('TEST XR'),
),
),*/
],
)
),
// floatingActionButton: ScannerBouton(appContext: appContext),

View File

@ -0,0 +1,454 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/services.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/LanguageSelection.dart';
import 'package:mymuseum_visitapp/Components/ScannerBouton.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/modelsHelper.dart';
import 'package:mymuseum_visitapp/Helpers/networkCheck.dart';
import 'package:mymuseum_visitapp/Helpers/requirement_state_controller.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/beaconSection.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/ConfigurationPage/configuration_page.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/Services/downloadConfiguration.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 'configurations_list.dart';
class HomePage3 extends StatefulWidget {
const HomePage3({Key? key}) : super(key: key);
@override
State<HomePage3> createState() => _HomePage3State();
}
class _HomePage3State extends State<HomePage3> with WidgetsBindingObserver {
int currentIndex = 0;
late List<ConfigurationDTO> configurations = [];
List<String?> alreadyDownloaded = [];
late VisitAppContext visitAppContext;
late Future<List<ConfigurationDTO>?> _futureConfigurations;
@override
void initState() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
super.initState();
final appContext = Provider.of<AppContext>(context, listen: false);
_futureConfigurations = getConfigurationsCall(appContext);
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context);
visitAppContext = appContext.getContext();
return Scaffold(
extendBody: true,
body: FutureBuilder(
future: _futureConfigurations,//getConfigurationsCall(visitAppContext.clientAPI, appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
configurations = List<ConfigurationDTO>.from(snapshot.data).where((configuration) => configuration.isMobile!).toList();
//String cleanedTitle = configurations[0].title.replaceAll('\n', ' ').replaceAll('<br>', ' '); // TODO
return Stack(
children: [
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.grey, Colors.lightGreen],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
),
/*SizedBox(
height: size.height * 0.35,
width: size.width,
child: Image.network(
configurations[2].imageSource!,
fit: BoxFit.cover,
)
),*/
SafeArea(
top: false,
bottom: false,
child: CustomScrollView(
slivers: [
/*SliverAppBar(
backgroundColor: Colors.transparent,
pinned: true,
expandedHeight: 200.0,
collapsedHeight: 100.0,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.none, // 👈 Optionnel pour éviter le fade
background: Image.network(
configurations[2].imageSource!,
fit: BoxFit.cover,
),
),
),*/
SliverAppBar(
backgroundColor: Colors.transparent,
pinned: false,
expandedHeight: 235.0,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin, // 👈 Optionnel pour éviter le fade
centerTitle: true,
background: Container(
padding: const EdgeInsets.only(bottom: 25.0),
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(25),
bottomRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color: Colors.black26,
spreadRadius: 0.35,
blurRadius: 2,
offset: Offset(0, -22),
),
],
),
child: ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(25),
bottomRight: Radius.circular(25),
),
child: Stack(
fit: StackFit.expand,
children: [
Opacity(
opacity: 0.85,
child: Image.network(
configurations[0].imageSource!,
fit: BoxFit.cover,
),
),
Positioned(
top: 35,
right: 10,
child: SizedBox(
width: 75,
height: 75,
child: LanguageSelection()
),
),
],
),
),
),
title: SizedBox(
width: /*widget.isHomeButton ?*/ size.width * 1.0 /*: null*/,
height: 120,
child: Center(
child: HtmlWidget(
configurations[0].title!.firstWhere((t) => t.language == "FR").value!,
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
), // plus de FlexibleSpaceBar
),
SliverPadding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0, bottom: 20.0),
sliver: SliverMasonryGrid.count(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childCount: configurations.length,
itemBuilder: (context, index) {
//return buildTile(configurations[index]);
String cleanedTitle = configurations[index].title!.firstWhere((t) => t.language == "FR").value!.replaceAll('\n', ' ').replaceAll('<br>', ' ');
return InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
ConfigurationPage(configuration: configurations[index], isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
));
},
child: Hero(
tag: configurations[index].id!,
child: Material(
type: MaterialType.transparency,
child: Container(
height: 200 + (index % 3) * 55,
decoration: BoxDecoration(
color: kSecondGrey,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
image: configurations[index].imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.5,
image: NetworkImage(
configurations[index].imageSource!,
),
): null,
),
child: Stack(
children: [
Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: HtmlWidget(
cleanedTitle,
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
Positioned(
bottom: 10,
right: 10,
child: Container(
width: 20,
height: 20,
decoration: const BoxDecoration(
//color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.chevron_right, size: 18, color: Colors.white)
),
)
],
),
),
),
),
);
},
),
),
/*SliverToBoxAdapter(
child: Image.network(configurations[0].imageSource!), // ou NetworkImage
),*/
],
),
),
/*FutureBuilder(
future: getConfigurationsCall(visitAppContext.clientAPI, appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return /*RefreshIndicator (
onRefresh: () {
setState(() {});
return Future(() => null);
},
color: kSecondColor,
child:*/ ;/*ConfigurationsList(
alreadyDownloaded: alreadyDownloaded,
configurations: configurations,
requestRefresh: () {
setState(() {}); // For refresh
},
),*/
//);
}
}
),*/
],
);
} else if (snapshot.connectionState == ConnectionState.none) {
return Text(TranslationHelper.getFromLocale("noData", appContext.getContext()));
} else {
return Center(
child: SizedBox(
height: size.height * 0.15,
child: const LoadingCommon()
)
);
}
}
),
);
return Scaffold(
/*appBar: CustomAppBar(
title: TranslationHelper.getFromLocale("visitTitle", appContext.getContext()),
isHomeButton: false,
),*/
body: SingleChildScrollView(
child: Column(
children: [
SizedBox(
width: size.width,
height: size.height,
child: FutureBuilder(
future: getConfigurationsCall(appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
configurations = List<ConfigurationDTO>.from(snapshot.data).where((configuration) => configuration.isMobile!).toList();
return RefreshIndicator (
onRefresh: () {
setState(() {});
return Future(() => null);
},
color: kSecondColor,
child: ConfigurationsList(
alreadyDownloaded: alreadyDownloaded,
configurations: configurations,
requestRefresh: () {
setState(() {}); // For refresh
},
),
);
} else if (snapshot.connectionState == ConnectionState.none) {
return Text(TranslationHelper.getFromLocale("noData", appContext.getContext()));
} else {
return Center(
child: Container(
height: size.height * 0.15,
child: LoadingCommon()
)
);
}
}
),
),
/*InkWell(
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) {
return TestAR();
//return XRWithQRScannerPage();
},
),
);
},
child: const SizedBox(
height: 50,
width: 10,
child: Text('TEST XR'),
),
),*/
],
)
),
// floatingActionButton: ScannerBouton(appContext: appContext),
//floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterFloat,
/*bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
if (currentIndex == 0) {
controller.startScanning();
} else {
controller.pauseScanning();
controller.startBroadcasting();
}
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'Scan',
),
BottomNavigationBarItem(
icon: Icon(Icons.bluetooth_audio),
label: 'Broadcast',
),
],
),*/
);
}
Future<List<ConfigurationDTO>?> getConfigurationsCall(AppContext appContext) async {
bool isOnline = await hasNetwork();
VisitAppContext visitAppContext = appContext.getContext();
List<ConfigurationDTO>? configurations;
configurations = List<ConfigurationDTO>.from(await DatabaseHelper.instance.getData(DatabaseTableType.configurations));
alreadyDownloaded = configurations.map((c) => c.id).toList();
if(!isOnline) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(TranslationHelper.getFromLocale("noInternet", appContext.getContext())), backgroundColor: kMainColor2),
);
// GET ALL SECTIONIDS FOR ALL CONFIGURATION (OFFLINE)
for(var configuration in configurations)
{
var sections = List<SectionDTO>.from(await DatabaseHelper.instance.queryWithConfigurationId(DatabaseTableType.sections, configuration.id!));
configuration.sectionIds = sections.map((e) => e.id!).toList();
}
// GET BEACONS FROM LOCAL
List<BeaconSection> beaconSections = List<BeaconSection>.from(await DatabaseHelper.instance.getData(DatabaseTableType.beaconSection));
print("GOT beaconSection from LOCAL");
print(beaconSections);
visitAppContext.beaconSections = beaconSections;
//appContext.setContext(visitAppContext);
return configurations;
}
if(visitAppContext.beaconSections == null) {
List<SectionDTO>? sections = await ApiService.getAllBeacons(visitAppContext.clientAPI, visitAppContext.instanceId!);
if(sections != null && sections.isNotEmpty) {
List<BeaconSection> beaconSections = sections.map((e) => BeaconSection(minorBeaconId: e.beaconId, orderInConfig: e.order, configurationId: e.configurationId, sectionId: e.id, sectionType: e.type)).toList();
visitAppContext.beaconSections = beaconSections;
try {
// Clear all before
await DatabaseHelper.instance.clearTable(DatabaseTableType.beaconSection);
// Store it locally for offline mode
for(var beaconSection in beaconSections) {
await DatabaseHelper.instance.insert(DatabaseTableType.beaconSection, ModelsHelper.beaconSectionToMap(beaconSection));
}
print("STORE beaconSection DONE");
} catch(e) {
print("Issue during beaconSection insertion");
print(e);
}
print("Got some Beacons for you");
print(beaconSections);
appContext.setContext(visitAppContext);
}
}
return await ApiService.getConfigurations(visitAppContext.clientAPI, visitAppContext);
}
}

View File

@ -1,443 +0,0 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
//import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/Loading.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Components/rounded_button.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/ResponseSubDTO.dart';
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/Quizz/drawPath.dart';
import 'package:mymuseum_visitapp/Screens/Quizz/questions_list.dart';
//import 'package:mymuseum_visitapp/Screens/Quizz/showResponses.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';
class QuizzPage extends StatefulWidget {
const QuizzPage({Key? key, required this.visitAppContextIn, required this.sectionId}) : super(key: key);
final String sectionId;
final VisitAppContext visitAppContextIn;
@override
State<QuizzPage> createState() => _QuizzPageState();
}
class _QuizzPageState extends State<QuizzPage> {
SectionDTO? sectionDTO;
List<ResourceModel?> resourcesModel = <ResourceModel?>[];
ResourceModel? audioResourceModel;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
late Uint8List audiobytes;
late VisitAppContext visitAppContext;
QuizzDTO? quizzDTO;
List<QuestionSubDTO> _questionsSubDTO = <QuestionSubDTO>[];
//ConfettiController? _controllerCenter;
int currentIndex = 1;
bool showResult = false;
bool showResponses = false;
bool kIsWeb = false;
bool isResultPage = false;
@override
void initState() {
widget.visitAppContextIn.isContentCurrentlyShown = true;
//_controllerCenter = ConfettiController(duration: const Duration(seconds: 10));
//_controllerCenter!.play();
super.initState();
}
@override
void dispose() {
visitAppContext.isContentCurrentlyShown = false;
currentIndex = 1;
//_controllerCenter!.dispose();
if(quizzDTO != null) {
if(quizzDTO!.questions != null) {
_questionsSubDTO = QuestionSubDTO().fromJSON(quizzDTO!.questions!);
}
}
super.dispose();
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
Size size = MediaQuery.of(context).size;
visitAppContext = appContext.getContext();
return Scaffold(
key: _scaffoldKey,
appBar: CustomAppBar(
title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext!) : "",
isHomeButton: false,
),
body: OrientationBuilder(
builder: (context, orientation) {
return FutureBuilder(
future: getQuizz(appContext, visitAppContext.clientAPI, widget.sectionId), // MAYBE MOVE THAT TO PARENT ..
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if(quizzDTO != null && sectionDTO != null) {
if(showResult) {
var goodResponses = 0;
for (var question in _questionsSubDTO) {
if(question.chosen == question.responsesSubDTO!.indexWhere((response) => response.isGood!)) {
goodResponses +=1;
}
}
log("goodResponses =" + goodResponses.toString());
LevelDTO levelToShow = LevelDTO();
var test = goodResponses/quizzDTO!.questions!.length;
if((0 == test || test < 0.25) && quizzDTO!.badLevel != null) {
levelToShow = quizzDTO!.badLevel!;
}
if((test>=0.25 && test < 0.5) && quizzDTO!.mediumLevel != null) {
levelToShow = quizzDTO!.mediumLevel!;
}
if((test>=0.5 && test < 0.75) && quizzDTO!.goodLevel != null) {
levelToShow = quizzDTO!.goodLevel!;
}
if((test>=0.75 && test <= 1) && quizzDTO!.greatLevel != null) {
levelToShow = quizzDTO!.greatLevel!;
}
return SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/*Center(
child: SizedBox(
width: 5,
height: 5,
child: ConfettiWidget(
confettiController: _controllerCenter!,
blastDirectionality: BlastDirectionality.explosive,
shouldLoop: false, // start again as soon as the animation is finished
colors: const [
kMainColor,
kSecondColor,
kConfigurationColor,
kMainColor1
//Colors.pink,
//Colors.orange,
//Colors.purple
], // manually specify the colors to be used
createParticlePath: drawPath, // define a custom shape/path.
),
),
),*/
if (orientation == Orientation.portrait)
Column(
children: [
if (!showResponses && levelToShow.label!.firstWhere((label) => label.language == visitAppContext!.language).resourceUrl != null) // TODO SUPPORT OTHER THAN IMAGES
resultImage(visitAppContext!, size, levelToShow, orientation),
if(!showResponses)
// TEXT BOX WITH MAIN SCORE
Text('$goodResponses/${quizzDTO!.questions!.length}', textAlign: TextAlign.center, style: TextStyle(fontSize: kIsWeb ? (showResponses ? 60 : 100) : 75, color: kBackgroundSecondGrey)),
],
),
if (orientation == Orientation.landscape)
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if(!showResponses)
// TEXT BOX WITH MAIN SCORE
Text('$goodResponses/${quizzDTO!.questions!.length}', textAlign: TextAlign.center, style: TextStyle(fontSize: kIsWeb ? (showResponses ? 60 : 100) : 75, color: kBackgroundSecondGrey)),
if (!showResponses && levelToShow.label!.firstWhere((label) => label.language == visitAppContext!.language).resourceUrl != null)
resultImage(visitAppContext!, size, levelToShow, orientation),
],
),
if(!showResponses)
// TEXT BOX WITH LEVEL TEXT RESULT
resultText(size, levelToShow, appContext),
if(showResponses)
QuestionsListWidget(
questionsSubDTO: _questionsSubDTO,
isShowResponse: true,
onShowResponse: () {},
orientation: orientation,
),
// RESPONSE BOX
//ShowReponsesWidget(questionsSubDTO: _questionsSubDTO),
if(orientation == Orientation.portrait && !showResponses)
// Buttons
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: resultButtons(size, orientation, visitAppContext!),
),
if(orientation == Orientation.landscape && !showResponses)
// Buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: resultButtons(size, orientation, visitAppContext!),
),
],
),
);
} else {
return QuestionsListWidget(
isShowResponse: false,
questionsSubDTO: _questionsSubDTO,
onShowResponse: () {
setState(() {
showResult = true;
});
},
orientation: orientation,
);
}
} else {
return const LoadingCommon();
}
}
);
}
),
floatingActionButton: showResponses ? FloatingActionButton(
onPressed: () {
setState(() {
showResult = false;
showResponses = false;
currentIndex = 1;
_questionsSubDTO = QuestionSubDTO().fromJSON(quizzDTO!.questions!);
});
},
backgroundColor: kBackgroundSecondGrey,
child: const Icon(Icons.undo),
) : null,
floatingActionButtonLocation: FloatingActionButtonLocation.miniEndFloat,
);
}
Future<QuizzDTO?> getQuizz(AppContext appContext, Client client, String sectionId) async {
try {
if(sectionDTO == null || quizzDTO == null) {
bool isConfigOffline = (appContext.getContext() as VisitAppContext).configuration!.isOffline!;
if(isConfigOffline)
{
// OFFLINE
List<Map<String, dynamic>> sectionTest = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.sections, sectionId);
if(sectionTest.isNotEmpty) {
sectionDTO = DatabaseHelper.instance.getSectionFromDB(sectionTest.first);
try {
SectionRead sectionRead = SectionRead(id: sectionDTO!.id!, readTime: DateTime.now().millisecondsSinceEpoch);
await DatabaseHelper.instance.insert(DatabaseTableType.articleRead, sectionRead.toMap());
visitAppContext!.readSections.add(sectionRead);
appContext.setContext(visitAppContext!);
} catch (e) {
print("DATABASE ERROR SECTIONREAD");
print(e);
}
} else {
print("EMPTY SECTION");
}
} else
{
// ONLINE
SectionDTO? sectionOnline = await client.sectionApi!.sectionGetDetail(sectionId);
if(sectionOnline != null) {
sectionDTO = sectionOnline;
} else {
print("EMPTY SECTION");
}
}
if(sectionDTO!.type == SectionType.Quizz) {
quizzDTO = QuizzDTO.fromJson(jsonDecode(sectionDTO!.data!));
}
if(quizzDTO != null) {
quizzDTO!.questions!.sort((a, b) => a.order!.compareTo(b.order!));
_questionsSubDTO = QuestionSubDTO().fromJSON(quizzDTO!.questions!);
if(quizzDTO!.questions != null && quizzDTO!.questions!.isNotEmpty) {
quizzDTO!.questions!.sort((a, b) => a.order!.compareTo(b.order!));
for (var question in quizzDTO!.questions!) {
if(isConfigOffline)
{
// OFFLINE
if(question.imageBackgroundResourceId != null) {
List<Map<String, dynamic>> ressourceQuizz = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, question.imageBackgroundResourceId!);
if(ressourceQuizz.isNotEmpty) {
resourcesModel.add(DatabaseHelper.instance.getResourceFromDB(ressourceQuizz.first));
} else {
print("EMPTY resourcesModel - second");
}
}
}
else
{
// ONLINE
if(question.imageBackgroundResourceId != null) {
resourcesModel.add(ResourceModel(id: question.imageBackgroundResourceId, source: question.imageBackgroundResourceUrl, type: ResourceType.Image));
}
}
}
}
}
setState(() {
//print(sectionDTO!.title);
});
} else {
return null; // TODO return local list..
}
} catch (e) {
print(e);
print("IN CATCH");
return null;
}
}
resultImage(VisitAppContext visitAppContext, Size size, LevelDTO levelToShow, Orientation orientation) {
return Container(
//height: size.height * 0.2,
//width: size.width * 0.25,
constraints: BoxConstraints(
maxHeight: size.height * 0.25,
maxWidth: kIsWeb ? size.width * 0.20 : orientation == Orientation.portrait ? size.width * 0.85 : size.width * 0.4,
),
alignment: Alignment.center,
decoration: BoxDecoration(
image: levelToShow.label!.where((label) => label.language == visitAppContext.language).isNotEmpty ? DecorationImage(
fit: BoxFit.contain,
opacity: 0.85,
image: NetworkImage(
levelToShow.label!.firstWhere((label) => label.language == visitAppContext.language).resourceUrl!,
),
): null,
borderRadius: const BorderRadius.all( Radius.circular(50.0)),
border: Border.all(
color: kBackgroundGrey,
width: 1.0,
),
),
child: Container(
//borderRadius: BorderRadius.all(Radius.circular(25.0)),
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
image: DecorationImage(
image: NetworkImage(
levelToShow.label!.firstWhere((label) => label.language == visitAppContext.language).resourceUrl!, // TODO REDUNDANCY here??
),
fit: BoxFit.cover,
),
borderRadius: const BorderRadius.all( Radius.circular(50.0)),
border: Border.all(
color: kBackgroundGrey,
width: 1.0,
),
),
),
);
}
resultText(Size size, LevelDTO levelToShow, AppContext appContext) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Container(
width: size.width *0.75,
height: kIsWeb ? (showResponses ? size.height *0.10 : size.height *0.20) : size.height *0.25,
decoration: BoxDecoration(
color: kBackgroundLight, //kBackgroundLight
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(10.0),
boxShadow: const [
BoxShadow(
color: kBackgroundSecondGrey,
spreadRadius: 0.3,
blurRadius: 4,
offset: Offset(0, 2), // changes position of shadow
),
],
),
child: Center(
child: SizedBox(
width: double.infinity,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Text(TranslationHelper.getWithResource(levelToShow.label, appContext.getContext() as VisitAppContext), textAlign: TextAlign.center, style: TextStyle(fontSize: kIsWeb ? kDescriptionSize : kDescriptionSize)),
),
),
),
),
),
);
}
resultButtons(Size size, Orientation orientation, VisitAppContext visitAppContext) {
return [
Padding(
padding: const EdgeInsets.all(4),
child: SizedBox(
height: kIsWeb ? 50 : 40,
width: orientation == Orientation.portrait ? size.width * 0.6 : size.width * 0.35,
child: RoundedButton(
text: TranslationHelper.getFromLocale("restart", visitAppContext),
color: kBackgroundSecondGrey,
textColor: kBackgroundLight,
icon: Icons.undo,
press: () {
setState(() {
showResult = false;
showResponses = false;
currentIndex = 1;
_questionsSubDTO = QuestionSubDTO().fromJSON(quizzDTO!.questions!);
});
},
fontSize: 18,
horizontal: 20,
vertical: 5
),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: kIsWeb ? 50 : 40,
width: orientation == Orientation.portrait ? size.width * 0.6 : size.width * 0.35,
child: RoundedButton(
text: TranslationHelper.getFromLocale("responses", visitAppContext),
color: kBackgroundSecondGrey,
textColor: kBackgroundLight,
icon: Icons.assignment_turned_in,
press: () {
setState(() {
showResponses = true;
});
},
fontSize: 18,
horizontal: 20,
vertical: 5
),
),
)
];
}
}

View File

@ -0,0 +1,198 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
//import 'dart:html';
import 'dart:ui' as ui;
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/loading_common.dart';
import 'package:mymuseum_visitapp/Models/agenda.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Agenda/event_list_item.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Agenda/event_popup.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Agenda/month_filter.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
class AgendaPage extends StatefulWidget {
final AgendaDTO section;
AgendaPage({required this.section});
@override
_AgendaPage createState() => _AgendaPage();
}
class _AgendaPage extends State<AgendaPage> {
AgendaDTO agendaDTO = AgendaDTO();
late Agenda agenda;
late ValueNotifier<List<EventAgenda>> filteredAgenda = ValueNotifier<List<EventAgenda>>([]);
late Uint8List mapIcon;
@override
void initState() {
/*print(widget.section!.data);
agendaDTO = AgendaDTO.fromJson(jsonDecode(widget.section!.data!))!;
print(agendaDTO);*/
agendaDTO = widget.section;
super.initState();
}
@override
void dispose() {
super.dispose();
}
Future<Agenda?> getAndParseJsonInfo(VisitAppContext visitAppContext) async {
try {
// Récupération du contenu JSON depuis l'URL
var httpClient = HttpClient();
// We need to get detail to get url from resourceId
var resourceIdForSelectedLanguage = agendaDTO.resourceIds!.where((ri) => ri.language == visitAppContext.language).first.value;
ResourceDTO? resourceDTO = await visitAppContext.clientAPI.resourceApi!.resourceGetDetail(resourceIdForSelectedLanguage!);
var request = await httpClient.getUrl(Uri.parse(resourceDTO!.url!));
var response = await request.close();
var jsonString = await response.transform(utf8.decoder).join();
agenda = Agenda.fromJson(jsonString);
agenda.events = agenda.events.where((a) => a.dateFrom != null && a.dateFrom!.isAfter(DateTime.now())).toList();
agenda.events.sort((a, b) => a.dateFrom!.compareTo(b.dateFrom!));
filteredAgenda.value = agenda.events;
mapIcon = await getByteIcon();
return agenda;
} catch(e) {
print("Erreur lors du parsing du json : ${e.toString()}");
return null;
}
}
getByteIcon() async {
final ByteData bytes = await rootBundle.load('assets/icons/marker.png');
var icon = await getBytesFromAsset(bytes, 25);
return icon;
}
Future<Uint8List> 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
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final VisitAppContext visitAppContext = Provider.of<AppContext>(context).getContext();
return FutureBuilder(future: getAndParseJsonInfo(visitAppContext),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return Center(
child: Text("Le fichier choisi n'est pas valide")
);
} else {
return Stack(
children: [
Center(
child: Container(
width: size.width,
height: size.height,
color: kBackgroundLight,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 2.0, top: 2.0),
child: ValueListenableBuilder<List<EventAgenda>>(
valueListenable: filteredAgenda,
builder: (context, value, _) {
return GridView.builder(
scrollDirection: Axis.vertical, // Changer pour horizontal si nécessaire
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // Nombre de colonnes dans la grid
crossAxisSpacing: 2.0, // Espace entre les colonnes
mainAxisSpacing: 2.0, // Espace entre les lignes
childAspectRatio: 0.65, // Aspect ratio des enfants de la grid
),
itemCount: value.length,
itemBuilder: (BuildContext context, int index) {
EventAgenda eventAgenda = value[index];
return GestureDetector(
onTap: () {
print("${eventAgenda.name}");
showDialog(
context: context,
builder: (BuildContext context) {
return EventPopup(eventAgenda: eventAgenda, mapProvider: agendaDTO.agendaMapProvider ?? MapProvider.Google, mapIcon: mapIcon);
},
);
},
child: EventListItem(
eventAgenda: eventAgenda,
),
);
},
);
}
),
),
),
),
Align(
alignment: Alignment.centerLeft,
child: MonthFilter(
events: snapshot.data.events,
onMonthSelected: (filteredList) {
print('events sélectionné: $filteredList');
var result = filteredList != null ? filteredList : <EventAgenda>[];
result.sort((a, b) => a.dateFrom!.compareTo(b.dateFrom!));
filteredAgenda.value = result;
}),
),
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
],
);
}
} else if (snapshot.connectionState == ConnectionState.none) {
return Text("No data");
} else {
return Center(
child: Container(
height: size.height * 0.2,
child: LoadingCommon()
)
);
}
},
);
}
} //_webView

View File

@ -0,0 +1,163 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Models/agenda.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
class EventListItem extends StatelessWidget {
final EventAgenda eventAgenda;
EventListItem({super.key, required this.eventAgenda});
final DateFormat formatter = DateFormat('dd/MM/yyyy');
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
var primaryColor = visitAppContext.configuration != null ? visitAppContext.configuration!.primaryColor != null ? new Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : kSecondColor : kSecondColor;
Size size = MediaQuery
.of(context)
.size;
return Container(
margin: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0),
//color: Colors.red,
boxShadow: const [
BoxShadow(
color: Colors.black26,
offset: Offset(0.0, 2.0),
blurRadius: 6.0,
),
],
),
//width: size.width * 0.5, //210.0,
//constraints: const BoxConstraints(maxWidth: 210, maxHeight: 100),
child: Column(
children: [
Container(
height: size.height * 0.18, // must be same ref0
constraints: const BoxConstraints(maxHeight: 250),
width: size.width*1,
child: Stack(
children: [
eventAgenda.image != null ? ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), topRight: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
child: Container(
//color: Colors.green,
//constraints: const BoxConstraints(maxHeight: 175),
child: CachedNetworkImage(
imageUrl: eventAgenda.image!,
width: size.width,
height: size.height * 0.2, // must be same ref0
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(),
Positioned(
right: 0.0,
bottom: 0.0,
child: Container(
decoration: BoxDecoration(
color: primaryColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 10.0,
right: 10.0,
top: 2.0,
bottom: 2.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
const Icon(
Icons.calendar_today_rounded,
size: 10.0,
color: kBackgroundColor,
),
const SizedBox(width: 5.0),
Text(
eventAgenda.dateFrom!.isAtSameMomentAs(eventAgenda.dateTo!) ? "${formatter.format(eventAgenda.dateFrom!)}": "${formatter.format(eventAgenda.dateFrom!)} - ${formatter.format(eventAgenda.dateTo!)}",
style: TextStyle(
color: kBackgroundColor,
fontSize: 12
),
),
],
),
],
),
),
),
),
],
),
),
Expanded(
/*height: size.height * 0.13,
constraints: BoxConstraints(maxHeight: 120),*/
child: Container(
width: size.width,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), bottomRight: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
border: Border(top: BorderSide(width: 0.1, color: kMainGrey))
),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
HtmlWidget(
'<div style="text-align: center;">${eventAgenda.name!.length > 75 ? eventAgenda.name!.substring(0, 75) + " ..." : eventAgenda.name}</div>',
customStylesBuilder: (element) {
return {'text-align': 'center', 'font-family': "Roboto"};
},
textStyle: TextStyle(fontSize: 14.0),
),
/*AutoSizeText(
eventAgenda.type!,
maxFontSize: 12.0,
style: TextStyle(
fontSize: 10.0,
color: Colors.grey,
),
),*/
],
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,475 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' as mapBox;
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/video_viewer_youtube.dart';
import 'package:mymuseum_visitapp/Models/agenda.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import 'package:url_launcher/url_launcher.dart';
class EventPopup extends StatefulWidget {
final EventAgenda eventAgenda;
final MapProvider mapProvider;
final Uint8List mapIcon;
EventPopup({Key? key, required this.eventAgenda, required this.mapProvider, required this.mapIcon}) : super(key: key);
@override
State<EventPopup> createState() => _EventPopupState();
}
class _EventPopupState extends State<EventPopup> {
final DateFormat formatter = DateFormat('dd/MM/yyyy hh:mm');
Completer<GoogleMapController> _controller = Completer();
Set<Marker> markers = {};
bool init = false;
mapBox.MapboxMap? mapboxMap;
mapBox.PointAnnotationManager? pointAnnotationManager;
Set<Marker> getMarkers() {
markers = {};
if (widget.eventAgenda.address!.lat != null && widget.eventAgenda.address!.lng != null) {
markers.add(Marker(
draggable: false,
markerId: MarkerId(widget.eventAgenda.address!.lat.toString() +
widget.eventAgenda.address!.lng.toString()),
position: LatLng(
double.parse(widget.eventAgenda.address!.lat!.toString()),
double.parse(widget.eventAgenda.address!.lng!.toString()),
),
icon: BitmapDescriptor.defaultMarker,
infoWindow: InfoWindow.noText));
}
return markers;
}
_onMapCreated(mapBox.MapboxMap mapboxMap, Uint8List icon) {
this.mapboxMap = mapboxMap;
mapboxMap.annotations.createPointAnnotationManager().then((pointAnnotationManager) async {
this.pointAnnotationManager = pointAnnotationManager;
pointAnnotationManager.createMulti(createPoints(LatLng(double.parse(widget.eventAgenda.address!.lat!.toString()), double.parse(widget.eventAgenda.address!.lng!.toString())), icon));
init = true;
});
}
createPoints(LatLng position, Uint8List icon) {
var options = <mapBox.PointAnnotationOptions>[];
options.add(mapBox.PointAnnotationOptions(
geometry: mapBox.Point(
coordinates: mapBox.Position(
position.longitude,
position.latitude,
)), // .toJson()
iconSize: 1.3,
iconOffset: [0.0, 0.0],
symbolSortKey: 10,
iconColor: 0,
iconImage: null,
image: icon, //widget.selectedMarkerIcon,
));
print(options.length);
return options;
}
Future<void> openEmail(String email) async {
final Uri emailLaunchUri = Uri(
scheme: 'mailto',
path: email,
);
try {
await launchUrl(emailLaunchUri, mode: LaunchMode.externalApplication);
} catch (e) {
print('Erreur lors de l\'ouverture de l\'email: $e');
}
}
Future<void> openPhone(String phone) async {
final Uri phoneLaunchUri = Uri(
scheme: 'tel',
path: phone,
);
try {
await launchUrl(phoneLaunchUri, mode: LaunchMode.externalApplication);
} catch (e) {
print('Erreur lors de l\'ouverture de l\'email: $e');
}
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
var dateToShow = widget.eventAgenda.dateFrom!.isAtSameMomentAs(widget.eventAgenda.dateTo!) ? "${formatter.format(widget.eventAgenda.dateFrom!)}": "${formatter.format(widget.eventAgenda.dateFrom!)} - ${formatter.format(widget.eventAgenda.dateTo!)}";
Size size = MediaQuery.of(context).size;
if(!init) {
print("getmarkers in build");
getMarkers();
init = true;
}
return Dialog(
insetPadding: const EdgeInsets.all(4.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0))
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
color: kBackgroundColor,
),
height: size.height * 0.92,
width: size.width * 0.95,
child: Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), topRight: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
child: Stack(
children: [
Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
kMainColor0,
kMainColor1,
kMainColor2,
],
),
border: const Border(right: BorderSide(width: 0.05, color: kMainGrey)),
color: Colors.grey,
image: widget.eventAgenda.image != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.4,
image: NetworkImage(
widget.eventAgenda.image!,
),
): null
),
width: size.width,
height: 125,
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10, top: 4),
child: Center(
child: HtmlWidget(
'<div style="text-align: center;">${widget.eventAgenda.name!}</div>',
textStyle: const TextStyle(fontSize: 20.0, color: Colors.white),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(top: 125),
child: SingleChildScrollView(
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: kSecondColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 6.0,
children: [
const Icon(Icons.calendar_today_rounded, color: kSecondColor, size: 18),
Text(
dateToShow,
style: const TextStyle(
fontSize: 16,
color: kSecondColor,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Container(
constraints: BoxConstraints(minHeight: 250, maxHeight: size.height * 0.38),
width: size.width,
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(
thumbVisibility: true,
thickness: 2.0,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
HtmlWidget(
widget.eventAgenda.description!,
customStylesBuilder: (element) {
return {'text-align': 'left', 'font-family': "Roboto"};
},
textStyle: const TextStyle(fontSize: 15.0),
),
widget.eventAgenda.idVideoYoutube != null && widget.eventAgenda.idVideoYoutube!.isNotEmpty ?
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
height: 250,
width: 350,
child: VideoViewerYoutube(videoUrl: "https://www.youtube.com/watch?v=${widget.eventAgenda.idVideoYoutube}", isAuto: false, webView: true)
),
) :
SizedBox(),
],
),
),
),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
widget.eventAgenda.address!.lat != null && widget.eventAgenda.address!.lng != null ?
SizedBox(
width: size.width,
height: size.height * 0.2,
child: widget.mapProvider == MapProvider.Google ?
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
child: GoogleMap(
mapToolbarEnabled: false,
initialCameraPosition: CameraPosition(
target: LatLng(double.parse(widget.eventAgenda.address!.lat!.toString()), double.parse(widget.eventAgenda.address!.lng!.toString())),
zoom: 14,
),
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
markers: markers,
),
) :
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
child: mapBox.MapWidget(
key: ValueKey("mapBoxWidget"),
styleUri: mapBox.MapboxStyles.STANDARD,
onMapCreated: (maBoxMap) {
_onMapCreated(maBoxMap, widget.mapIcon);
},
cameraOptions: mapBox.CameraOptions(
center: mapBox.Point(coordinates: mapBox.Position(double.parse(widget.eventAgenda.address!.lng!.toString()), double.parse(widget.eventAgenda.address!.lat!.toString()))), // .toJson()
zoom: 14
),
),
),
): SizedBox(),
widget.eventAgenda.address?.address != null &&
widget.eventAgenda.address!.address!.isNotEmpty
? GestureDetector(
onTap: () async {
final query = Uri.encodeComponent(widget.eventAgenda.address!.address!);
final googleMapsUrl = Uri.parse("https://www.google.com/maps/search/?api=1&query=$query");
if (await canLaunchUrl(googleMapsUrl)) {
await launchUrl(googleMapsUrl, mode: LaunchMode.externalApplication);
} else {
print("Impossible d'ouvrir Google Maps");
}
},
child: Container(
width: size.width,
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.location_on, size: 13, color: kMainColor),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
width: size.width * 0.7,
child: AutoSizeText(
textAlign: TextAlign.center,
widget.eventAgenda.address!.address!,
style: const TextStyle(
fontSize: 12,
color: kMainColor,
//decoration: TextDecoration.underline,
),
maxLines: 3,
),
),
)
],
),
),
)
: const SizedBox(),
widget.eventAgenda.phone != null && widget.eventAgenda.phone!.isNotEmpty
? SizedBox(
width: size.width,
height: 35,
child: InkWell(
onTap: () => openPhone(widget.eventAgenda.phone!),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.phone, size: 13, color: kMainColor),
Padding(
padding: const EdgeInsets.all(4.0),
child: Text(widget.eventAgenda.phone!, style: TextStyle(fontSize: 12, color: kMainColor)),
),
],
),
),
)
: SizedBox(),
widget.eventAgenda.email != null && widget.eventAgenda.email!.isNotEmpty
? SizedBox(
width: size.width,
height: 35,
child: InkWell(
onTap: () => openEmail(widget.eventAgenda.email!),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.email, size: 13, color: kMainColor),
Padding(
padding: const EdgeInsets.all(4.0),
child: AutoSizeText(
widget.eventAgenda.email!,
style: TextStyle(fontSize: 12, color: kMainColor),
maxLines: 3
),
)
],
),
),
)
: SizedBox(),
widget.eventAgenda.website != null && widget.eventAgenda.website!.isNotEmpty
? GestureDetector(
onTap: () async {
final url = Uri.parse(widget.eventAgenda.website!);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
} else {
// Optionnel : afficher une erreur
print('Impossible d\'ouvrir le lien');
}
},
child: SizedBox(
width: size.width * 0.8,
height: 35,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.public, size: 13, color: kMainColor),
const SizedBox(width: 4),
Flexible(
child: AutoSizeText(
textAlign: TextAlign.center,
widget.eventAgenda.website!,
style: const TextStyle(
fontSize: 12,
color: kMainColor,
//decoration: TextDecoration.underline,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
)
: const SizedBox(),
],
),
),
)
],
),
),
),
),
Positioned(
right: 4.5,
top: 4.5,
child: GestureDetector(
onTap: () {
setState(() {
Navigator.of(context).pop();
});
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: kMainColor.withValues(alpha: 0.55),
shape: BoxShape.circle,
),
child: const Icon(
Icons.close,
size: 25,
color: Colors.white,
),
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,207 @@
import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/agenda.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
class MonthFilter extends StatefulWidget {
final List<EventAgenda> events;
final Function(List<EventAgenda>?) onMonthSelected;
MonthFilter({required this.events, required this.onMonthSelected});
@override
_MonthFilterState createState() => _MonthFilterState();
}
class _MonthFilterState extends State<MonthFilter> with SingleTickerProviderStateMixin {
String? _selectedMonth;
bool _isExpanded = false;
bool _showContent = false;
late AnimationController _controller;
late Animation<double> _widthAnimation;
Map<String, List<String>> monthNames = {
'fr': ['', 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
'en': ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
'de': ['', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
'nl': ['', 'Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
'it': ['', 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
'es': ['', 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
'pl': ['', 'Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
'cn': ['', '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
'uk': ['', 'Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень'],
'ar': ['', 'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'],
};
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
_widthAnimation = Tween<double>(begin: 40, end: 265).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
}
void toggleExpand() {
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();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
var primaryColor = visitAppContext.configuration?.primaryColor != null
? Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16))
: kSecondColor;
double rounded = visitAppContext.configuration?.roundedValue?.toDouble() ?? 20.0;
List<Map<String, dynamic>> sortedMonths = _getSortedMonths();
return AnimatedBuilder(
animation: _widthAnimation,
builder: (context, child) {
return Container(
width: _widthAnimation.value,
height: _isExpanded ? 350 : 75,
decoration: BoxDecoration(
color: _isExpanded ? primaryColor.withValues(alpha: 0.9) : primaryColor.withValues(alpha: 0.5),
borderRadius: BorderRadius.only(
topRight: Radius.circular(rounded),
bottomRight: Radius.circular(rounded),
),
),
child: _showContent
? Column(
children: [
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: toggleExpand,
),
Expanded(
child: ListView.builder(
itemCount: sortedMonths.length + 1,
itemBuilder: (context, index) {
if (index == 0) return _buildAllItem(appContext, primaryColor);
final monthYear = sortedMonths[index - 1]['monthYear'];
final filteredEvents = _filterEvents(monthYear);
final nbrEvents = filteredEvents.length;
if (nbrEvents == 0) return const SizedBox.shrink();
String monthName = _getTranslatedMonthName(visitAppContext, monthYear);
String year = RegExp(r'\d{4}').stringMatch(monthYear)!;
bool isSelected = _selectedMonth == monthYear;
return ListTile(
title: Text(
'$monthName $year ($nbrEvents)',
style: TextStyle(
fontSize: 15.0,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? Colors.white : Colors.black,
),
textAlign: TextAlign.center
),
tileColor: isSelected ? primaryColor.withValues(alpha: 0.6) : null,
onTap: () {
setState(() => _selectedMonth = monthYear);
widget.onMonthSelected(filteredEvents);
toggleExpand(); // Auto close after tap
},
);
},
),
),
],
)
: _isExpanded ? null : IconButton(
icon: const Icon(Icons.menu, color: Colors.white),
onPressed: toggleExpand,
),
);
},
);
}
Widget _buildAllItem(AppContext appContext, Color primaryColor) {
final totalEvents = widget.events.length;
final isSelected = _selectedMonth == null;
return ListTile(
title: Text(
'${TranslationHelper.getFromLocale("agenda.all", appContext.getContext())} ($totalEvents)',
style: TextStyle(
fontSize: 15.0,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? Colors.white : Colors.black,
),
textAlign: TextAlign.center
),
tileColor: isSelected ? primaryColor.withValues(alpha: 0.6) : null,
onTap: () {
setState(() => _selectedMonth = null);
widget.onMonthSelected(widget.events);
toggleExpand(); // Auto close after tap
},
);
}
String _getTranslatedMonthName(VisitAppContext context, String monthYear) {
int monthIndex = int.parse(monthYear.split('-')[0]);
return monthNames[context.language!.toLowerCase()]![monthIndex];
}
List<Map<String, dynamic>> _getSortedMonths() {
Map<String, int> monthsMap = {};
for (var event in widget.events) {
final key = '${event.dateFrom!.month}-${event.dateFrom!.year}';
monthsMap[key] = (monthsMap[key] ?? 0) + 1;
}
List<Map<String, dynamic>> sorted = monthsMap.entries
.map((e) => {'monthYear': e.key, 'totalEvents': e.value})
.toList();
sorted.sort((a, b) {
final aParts = a['monthYear'].split('-').map(int.parse).toList();
final bParts = b['monthYear'].split('-').map(int.parse).toList();
return aParts[1] != bParts[1] ? aParts[1].compareTo(bParts[1]) : aParts[0].compareTo(bParts[0]);
});
return sorted;
}
List<EventAgenda> _filterEvents(String? monthYear) {
if (monthYear == null) return widget.events;
final parts = monthYear.split('-');
final selectedMonth = int.parse(parts[0]);
final selectedYear = int.parse(parts[1]);
return widget.events.where((event) {
final date = event.dateFrom!;
return date.month == selectedMonth && date.year == selectedYear;
}).toList();
}
}

View File

@ -4,7 +4,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Components/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Components/SliderImages.dart';
@ -13,7 +13,7 @@ import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
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/Article/audio_player.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Article/audio_player.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/client.dart';
@ -24,27 +24,31 @@ import 'package:path_provider/path_provider.dart';
import 'audio_player_floating.dart';
class ArticlePage extends StatefulWidget {
const ArticlePage({Key? key, required this.visitAppContextIn, required this.articleId}) : super(key: key);
const ArticlePage({Key? key, required this.visitAppContextIn, required this.articleDTO, required this.resourcesModel, this.mainAudioId}) : super(key: key);
final String articleId;
final ArticleDTO articleDTO;
final VisitAppContext visitAppContextIn;
final List<ResourceModel?> resourcesModel;
final String? mainAudioId;
@override
State<ArticlePage> createState() => _ArticlePageState();
}
class _ArticlePageState extends State<ArticlePage> {
SectionDTO? sectionDTO;
ArticleDTO? articleDTO;
List<ResourceModel?> resourcesModel = <ResourceModel?>[];
ResourceModel? audioResourceModel;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
File? audioFile;
late VisitAppContext visitAppContext;
late List<ResourceModel?> resourcesModelToShow;
@override
void initState() {
widget.visitAppContextIn.isContentCurrentlyShown = true;
audioResourceModel = widget.resourcesModel.firstWhere((r) => r?.id == widget.mainAudioId, orElse: () => null);
resourcesModelToShow = widget.resourcesModel.where((r) => r?.id != widget.mainAudioId).toList();
super.initState();
}
@ -62,28 +66,136 @@ class _ArticlePageState extends State<ArticlePage> {
visitAppContext = appContext.getContext();
var title = TranslationHelper.get(widget.articleDTO.title, appContext.getContext());
String cleanedTitle = title.replaceAll('\n', ' ').replaceAll('<br>', ' ');
return Scaffold(
key: _scaffoldKey,
appBar: CustomAppBar(
title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "",
isHomeButton: false,
isTextSizeButton: true,
body: Stack(
children: [
Container(
height: size.height * 0.28,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
body: FutureBuilder(
future: getArticle(appContext, visitAppContext.clientAPI, widget.articleId, false),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if(articleDTO != null && sectionDTO != null) {
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
kMainColor0,
kMainColor1,
kMainColor2,
],
),
image: widget.articleDTO.imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.65,
image: NetworkImage(
widget.articleDTO.imageSource!,
),
): null,
),
),
Column(
children: [
SizedBox(
height: size.height * 0.11,
width: size.width,
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(top: 22.0),
child: SizedBox(
width: size.width *0.7,
child: HtmlWidget(
cleanedTitle,
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
),
Positioned(
right: 10,
top: 45,
child: InkWell(
onTap: () {
setState(() {
visitAppContext.isMaximizeTextSize = !visitAppContext.isMaximizeTextSize;
appContext.setContext(visitAppContext);
});
},
child: SizedBox(
width: 50,
child: visitAppContext.isMaximizeTextSize ? const Icon(Icons.text_fields, size: 30, color: Colors.white) : const Icon(Icons.format_size, size: 30, color: Colors.white)
),
),
),
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
],
),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 2,
offset: Offset(0, 1), // changes position of shadow
),
],
color: kBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: OrientationBuilder(
builder: (context, orientation) {
if(size.height > size.width) {
return Column(
children: [
if(articleDTO!.isContentTop!)
if(widget.articleDTO.isContentTop!)
getContent(size, appContext),
if(articleDTO!.isContentTop! && resourcesModel.isNotEmpty)
getImages(size, articleDTO!.isContentTop!),
if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty)
getImages(size, widget.articleDTO.isContentTop!),
if(!articleDTO!.isContentTop! && resourcesModel.isNotEmpty)
getImages(size, articleDTO!.isContentTop!),
if(!articleDTO!.isContentTop!)
if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty)
getImages(size, widget.articleDTO.isContentTop!),
if(!widget.articleDTO.isContentTop!)
getContent(size, appContext),
/*if(audioResourceModel != null)
@ -99,14 +211,14 @@ class _ArticlePageState extends State<ArticlePage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if(articleDTO!.isContentTop!)
if(widget.articleDTO.isContentTop!)
getContent(size, appContext),
if(articleDTO!.isContentTop! && resourcesModel.isNotEmpty)
getImages(size, articleDTO!.isContentTop!),
if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty)
getImages(size, widget.articleDTO.isContentTop!),
if(!articleDTO!.isContentTop! && resourcesModel.isNotEmpty)
getImages(size, articleDTO!.isContentTop!),
if(!articleDTO!.isContentTop!)
if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty)
getImages(size, widget.articleDTO.isContentTop!),
if(!widget.articleDTO.isContentTop!)
getContent(size, appContext),
],
),
@ -116,19 +228,17 @@ class _ArticlePageState extends State<ArticlePage> {
),
);
}
} else {
return const LoadingCommon();
}
}
),
floatingActionButton: FutureBuilder(
future: getArticle(appContext, visitAppContext.clientAPI, widget.articleId, true),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
return Padding(
padding: EdgeInsets.only(right: 0, top: 0), //size.height*0.1
child: audioResourceModel != null && audioResourceModel!.source != null ? AudioPlayerFloatingContainer(file: audioFile, resourceURl: audioResourceModel!.source!, isAuto: articleDTO!.isReadAudioAuto!) : null,
);
}
),
),
],
),
],
),
floatingActionButton: Padding(
padding: const EdgeInsets.only(right: 0, top: 0), //size.height*0.1
child: audioResourceModel != null && audioResourceModel!.source != null ? AudioPlayerFloatingContainer(file: audioFile, resourceURl: audioResourceModel!.source!, isAuto: widget.articleDTO.isReadAudioAuto!) : null,
),
floatingActionButtonLocation: FloatingActionButtonLocation.miniEndFloat, //miniEndTop
);
@ -149,13 +259,13 @@ class _ArticlePageState extends State<ArticlePage> {
),
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0),
borderRadius: BorderRadius.circular(20.0),
boxShadow: const [kDefaultShadow],
),
child: SliderImagesWidget(
resources: resourcesModel,
resources: resourcesModelToShow,
height: size.height * 0.29,
contentsDTO: articleDTO!.contents!,
contentsDTO: widget.articleDTO.contents!,
)
)
)
@ -174,13 +284,13 @@ class _ArticlePageState extends State<ArticlePage> {
),
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0),
borderRadius: BorderRadius.circular(20.0),
boxShadow: const [kDefaultShadow],
),
child: SliderImagesWidget(
resources: resourcesModel,
resources: resourcesModelToShow,
height: size.height * 0.29,
contentsDTO: articleDTO!.contents!,
contentsDTO: widget.articleDTO.contents!,
)
)
)
@ -196,7 +306,7 @@ class _ArticlePageState extends State<ArticlePage> {
height: size.height * 0.76,
//color: Colors.blueAccent,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0, bottom: 8.0),
padding: const EdgeInsets.all(10.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(
@ -205,14 +315,14 @@ class _ArticlePageState extends State<ArticlePage> {
),
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0),
borderRadius: BorderRadius.circular(20.0),
boxShadow: const [kDefaultShadow],
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: HtmlWidget(
TranslationHelper.get(articleDTO!.content, appContext.getContext()),
TranslationHelper.get(widget.articleDTO.content, appContext.getContext()),
textStyle: TextStyle(fontSize: (appContext.getContext() as VisitAppContext).isMaximizeTextSize ? kArticleContentBiggerSize : kArticleContentSize),
customStylesBuilder: (element)
{
@ -233,7 +343,7 @@ class _ArticlePageState extends State<ArticlePage> {
//height: size.height * 0.65,
//color: Colors.blueAccent,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0, bottom: 8.0),
padding: const EdgeInsets.all(10.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(
@ -242,14 +352,14 @@ class _ArticlePageState extends State<ArticlePage> {
),
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(5.0),
borderRadius: BorderRadius.circular(20.0),
boxShadow: const [kDefaultShadow],
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(12.5),
child: HtmlWidget(
TranslationHelper.get(articleDTO!.content, appContext.getContext()),
TranslationHelper.get(widget.articleDTO.content, appContext.getContext()),
//textAlign: TextAlign.left,
textStyle: TextStyle(fontSize: (appContext.getContext() as VisitAppContext).isMaximizeTextSize ? kArticleContentBiggerSize : kArticleContentSize, fontFamily: "Arial"),
),
@ -262,7 +372,7 @@ class _ArticlePageState extends State<ArticlePage> {
}
}
Future<ArticleDTO?> getArticle(AppContext appContext, Client client, String articleId, bool isAudio) async {
/*Future<ArticleDTO?> getArticle(AppContext appContext, Client client, String articleId, bool isAudio) async {
try {
if(sectionDTO == null || articleDTO == null) {
bool isConfigOffline = (appContext.getContext() as VisitAppContext).configuration!.isOffline!;
@ -387,7 +497,7 @@ class _ArticlePageState extends State<ArticlePage> {
// Not needed as it's in display logic
//ResourceModel? resourceImageOnline = await ApiService.downloadImage(client, image);
//if(resourceImageOnline != null) {
resourcesModel.add(ResourceModel(id: image.resourceId, source: image.resourceUrl, type: ResourceType.Image));
resourcesModel.add(ResourceModel(id: image.resourceId, source: image.resource?.url, type: ResourceType.Image));
/*} else {
print("EMPTY resourcesModel online - audio");
}*/
@ -407,5 +517,5 @@ class _ArticlePageState extends State<ArticlePage> {
print("IN CATCH");
return null;
}
}
}*/
}

View File

@ -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<TreeNode> 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<FilterTree> {
@override
void initState() {
super.initState();
}
Map<String, int> map = {};
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return _buildNode(widget.data[index], const 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: const EdgeInsets.fromLTRB(0, 0, 0, 0),
initiallyExpanded: node.show,
title: Container(
margin: const EdgeInsets.only(left: 0),
padding: const 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 ?? const 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<TreeNode> 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;
}
}
}
}

View File

@ -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<GeoPointDTO> geoPoints;
final List<Map<String, dynamic>> 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<FlutterMapView> {
late List<GeoPointDTO> markersList;
late List<Marker> markers;
bool filterZoneSelected = false;
MapController? mapController;
List<Marker> 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<MapContext>(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<MapContext>(context);
final appContext = Provider.of<AppContext>(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<MapContext>(
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();
},
);
}
),
)
],
);
}
}

View File

@ -0,0 +1,389 @@
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<GeoPointDTO> geoPoints;
final List<CategorieDTO> categories;
final Function(List<GeoPointDTO>?) 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<GeoPointFilter> with SingleTickerProviderStateMixin {
List<GeoPointDTO> selectedGeoPoints = [];
late List<TreeNode> _filteredNodes;
late ValueNotifier<String> _searchTextNotifier;
final TextEditingController _searchController = TextEditingController();
FocusNode focusNode = FocusNode();
bool _isExpanded = false;
bool _showContent = false;
late AnimationController _controller;
late Animation<double> _widthAnimation;
late Size screenSize;
@override
void didChangeDependencies() {
super.didChangeDependencies();
screenSize = MediaQuery.of(context).size;
}
@override
void initState() {
super.initState();
_searchTextNotifier = ValueNotifier<String>('');
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
_widthAnimation = Tween<double>(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<TreeNode> buildTreeNodes(List<CategorieDTO> categories, List<GeoPointDTO> geoPoints) {
List<TreeNode> 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<GeoPointDTO> 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<TreeNode> _filterNodesBySearchText(String searchText) {
List<TreeNode> 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<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
var currentLanguage = visitAppContext.language;
final mapContext = Provider.of<MapContext>(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<String>(
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,
),
);
}
),
);
}
}

View File

@ -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<GeoPointDTO> geoPoints;
final List<Map<String, dynamic>> 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<GoogleMapView> {
ConfigurationDTO? configurationDTO;
Completer<GoogleMapController> _controller = Completer();
GoogleMapController? _GoogleMapcontroller;
Set<Marker> markers = {};
List<GeoPointDTO>? pointsToShow = [];
//List<String>? selectedCategories = [];
bool init = false;
Set<Marker> 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<MapContext>(context);
final appContext = Provider.of<AppContext>(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<MapContext>(
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<String>.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();
});
}
},
),
),
),*/
],
);
}
}

View File

@ -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<GeoPointDTO> geoPoints;
final List<Map<String, dynamic>> 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<GeoPointDTO> 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<MapBoxView> {
late mapBox.MapboxMap? mapboxMap;
mapBox.PointAnnotationManager? pointAnnotationManager;
bool filterZoneSelected = false;
createPoints() {
var options = <mapBox.PointAnnotationOptions>[];
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<GoogleMapController> _controller = Completer();
//Set<Marker> markers = {};
List<GeoPointDTO>? pointsToShow = [];
List<String>? 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<MapContext>(context);
pointsToShow = widget.geoPoints;//widget.mapDTO!.points;
if(pointAnnotationManager != null) {
pointAnnotationManager!.deleteAll();
pointAnnotationManager!.createMulti(createPoints());
//mapContext.notifyListeners();
}
final appContext = Provider.of<AppContext>(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<MapContext>(
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);
}),
),
),*/
],
);
}
}

View File

@ -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();
}
}

View File

@ -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<Marker> markers = {};
List<GeoPointDTO> markersList = [];
class MapPage extends StatefulWidget {
final MapDTO section;
final List<Map<String, dynamic>> icons;
MapPage({Key? key, required this.section, required this.icons}) : super(key: key);
@override
_MapPage createState() => _MapPage();
}
class _MapPage extends State<MapPage> {
MapDTO? mapDTO;
//Completer<GoogleMapController> _controller = Completer();
//Uint8List? selectedMarkerIcon;
late ValueNotifier<List<GeoPointDTO>> _geoPoints = ValueNotifier<List<GeoPointDTO>>([]);
/*Future<Uint8List> 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<MapContext>(context);
final appContext = Provider.of<AppContext>(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<dynamic> 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: <Widget>[
ValueListenableBuilder<List<GeoPointDTO>>(
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<void> _goToTheLake() async {
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition(_kLake));
}*/
}

View File

@ -0,0 +1,619 @@
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 '../../../constants.dart';
import 'map_context.dart';
class MarkerViewWidget extends StatefulWidget {
const MarkerViewWidget({super.key});
@override
_MarkerInfoWidget createState() => _MarkerInfoWidget();
}
class _MarkerInfoWidget extends State<MarkerViewWidget> {
CarouselSliderController? sliderController;
ValueNotifier<int> currentIndex = ValueNotifier<int>(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<MapContext>(context);
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext;
var language = visitAppContext.language;
GeoPointDTO? selectedPoint = mapContext.getSelectedPoint() as GeoPointDTO?;
ScrollController scrollDescription = ScrollController();
ScrollController scrollPrice = 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().isNotEmpty;
Color primaryColor = Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16));
Size sizeMarker = Size(size.width * 0.93, size.height * 0.8);
return Center(
child: Visibility(
visible: selectedPoint != null,
child: Container(
width: sizeMarker.width,
height: sizeMarker.height,
margin: const 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: <Widget> [
if(selectedPoint != null)
ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), topRight: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
child: Stack(
children: [
Container(
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
kMainColor0,
kMainColor1,
kMainColor2,
],
),
border: const Border(right: BorderSide(width: 0.05, color: kMainGrey)),
color: Colors.grey,
image: selectedPoint.imageUrl != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.4,
image: NetworkImage(
selectedPoint.imageUrl!,
),
): null
),
width: size.width,
height: 75,
child: Center(
child: HtmlWidget(
selectedPoint.title!.firstWhere((t) => t.language == language).value!,
textStyle: const TextStyle(fontSize: 20.0),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
],
),
),
if(selectedPoint != null)
Padding(
padding: const EdgeInsets.only(top: 75),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
//mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(6),
child: Container(
height: size.height * 0.4,
width: size.width,
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().isNotEmpty)
HtmlWidget(
selectedPoint.description!.firstWhere((d) => d.language == language).value!,
customStylesBuilder: (element) {
return {'text-align': 'left', 'font-family': "Roboto"};
},
textStyle: const TextStyle(fontSize: kMenuDescriptionDetailSize),
),
],
),
),
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
height: size.height * 0.2,
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<Widget>((ContentDTO i) {
return Builder(
builder: (BuildContext context) {
AppContext appContext = Provider.of<AppContext>(context);
var resourcetoShow = getElementForResource(context, appContext, i, true);
return resourcetoShow;
},
);
}).toList(),
),
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Column(
children: [
if(isPointPrice && selectedPoint.prices!.firstWhere((d) => d.language == language).value!.length > breakPointPrice)
Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Container(
height: size.height * 0.18,
width: size.width,
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: const TextStyle(fontSize: kMenuDescriptionDetailSize),
),
],
),
),
),
),
),
),
),
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: const 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().isNotEmpty ? Row(
mainAxisAlignment: MainAxisAlignment.center,
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: const TextStyle(fontSize: 18)),
)
],
): const 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().isNotEmpty ? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.email, color: primaryColor, size: 13),
Padding(
padding: const EdgeInsets.all(4.0),
child: AutoSizeText(parse(selectedPoint.email!.firstWhere((p) => p.language == language).value!).documentElement!.text, style: const TextStyle(fontSize: 18), maxLines: 3),
)
],
): const 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().isNotEmpty ? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.public, color: primaryColor, size: 13),
Padding(
padding: const EdgeInsets.all(4.0),
child: AutoSizeText(parse(selectedPoint.site!.firstWhere((p) => p.language == language).value!).documentElement!.text, style: const TextStyle(fontSize: 18), maxLines: 3),
)
],
): const 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<Widget>((ContentGeoPoint i) {
return Builder(
builder: (BuildContext context) {
AppContext appContext = Provider.of<AppContext>(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<int>(
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,
),
)
),
],
),
),
),*/
Positioned(
right: 6.5,
top: 12.5,
child: GestureDetector(
onTap: () {
setState(() {
mapContext.setSelectedPoint(null);
mapContext.setSelectedPointForNavigate(null);
});
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: primaryColor,
shape: BoxShape.circle,
),
child: const Icon(
Icons.close,
size: 25,
color: Colors.white,
),
),
),
),
])
),
),
);
}
}
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,
),
constraints: const BoxConstraints(minWidth: 250, minHeight: 250, maxHeight: 350),
height: size.height * 0.6,
width: size.width * 0.85,
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,
),
constraints: const BoxConstraints(minWidth: 250, minHeight: 250, maxHeight: 350),
height: size.height * 0.6,
width: size.width * 0.85,
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(
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,
),
),
),
);
}

View File

@ -0,0 +1,42 @@
class TreeNode {
bool checked;
bool show;
int id;
int pid;
int commonID;
String title;
List<TreeNode> 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<String, dynamic> 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<dynamic>)
.map((childJson) => TreeNode.fromJson(childJson))
.toList(),
);
}
List<int> getAllChildrenTitles() {
List<int> id = [];
for (var child in children) {
id.add(child.id);
id.addAll(child.getAllChildrenTitles());
}
return id;
}
}

View File

@ -0,0 +1,375 @@
import 'dart:convert';
import 'package:diacritic/diacritic.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/ScannerBouton.dart';
import 'package:mymuseum_visitapp/Components/SearchBox.dart';
import 'package:mymuseum_visitapp/Components/SearchNumberBox.dart';
import 'package:mymuseum_visitapp/Components/SlideFromRouteRight.dart';
import 'package:mymuseum_visitapp/Helpers/ImageCustomProvider.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/section_page.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
class MenuPage extends StatefulWidget {
final MenuDTO section;
final bool isImageBackground;
MenuPage({required this.section, required this.isImageBackground});
@override
_MenuPageState createState() => _MenuPageState();
}
class _MenuPageState extends State<MenuPage> {
//MenuDTO menuDTO = MenuDTO();
SectionDTO? selectedSection;
bool isImageBackground = false;
late List<dynamic> rawSubSectionsData;
late List<SectionDTO> subSections;
String? searchValue;
int? searchNumberValue;
late List<SectionDTO> _allSections;
final ValueNotifier<List<SectionDTO>> filteredSections = ValueNotifier([]);
@override
void initState() {
/*print(widget.section.data);
menuDTO = MenuDTO.fromJson(jsonDecode(widget.section.data!))!;
print(menuDTO);*/
//menuDTO = widget.section;
rawSubSectionsData = jsonDecode(jsonEncode(widget.section.sections));
//menuDTO.sections!.sort((a, b) => a.order!.compareTo(b.order!)); // useless, we get these after that
subSections = jsonDecode(jsonEncode(rawSubSectionsData)).map((json) => SectionDTO.fromJson(json)).whereType<SectionDTO>().toList();
isImageBackground = widget.isImageBackground;
WidgetsBinding.instance.addPostFrameCallback((_) {
final appContext = Provider.of<AppContext>(context, listen: false);
VisitAppContext visitAppContext = appContext.getContext();
_allSections = subSections;
applyFilters(visitAppContext);
});
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
Size size = MediaQuery.of(context).size;
VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext;
ConfigurationDTO configurationDTO = appContext.getContext().configuration;
Color backgroundColor = appContext.getContext().configuration != null ? Color(int.parse(appContext.getContext().configuration.secondaryColor.split('(0x')[1].split(')')[0], radix: 16)) : Colors.white;
Color textColor = backgroundColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
Color? primaryColor = configurationDTO.primaryColor != null ? Color(int.parse(configurationDTO.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : null;
return SafeArea(
bottom: false,
top: false,
child: Stack(
children: [
Container(
height: size.height * 0.28,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
/*Color(0xFFDD79C2),
Color(0xFFB65FBE),
Color(0xFF9146BA),
Color(0xFF7633B8),
Color(0xFF6528B6),
Color(0xFF6025B6)*/
kMainColor0, //Color(0xFFf6b3c4)
kMainColor1,
kMainColor2,
],
),
image: widget.section.imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.65,
image: NetworkImage(
widget.section.imageSource!,
),
): null,
),
),
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 15),
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
//setState(() {
/**/
Navigator.of(context).pop();
/*visitAppContext.configuration = null;
visitAppContext.isScanningBeacons = false;*/
/*Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(
builder: (context) => const HomePage3(),
),(route) => false);*/
//});
},
child: Container(
decoration: BoxDecoration(
color: primaryColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
// TODO add a check if search used.
SearchBox(
width: size.width *0.55,
onChanged: (value) {
searchValue = value?.trim();
applyFilters(visitAppContext);
}
),
// TODO add a check if number used.
Expanded(
child: SearchNumberBox(onChanged: (value) {
if (value != null && value.isNotEmpty) {
searchNumberValue = int.tryParse(value);
} else {
searchNumberValue = null;
}
FocusScope.of(context).unfocus();
applyFilters(visitAppContext);
}
),
),
],
),
),
Expanded(
child: Stack(
children: <Widget>[
// Our background
Container(
margin: const EdgeInsets.only(top: 0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 2,
offset: Offset(0, 1), // changes position of shadow
),
],
color: kBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
),
ValueListenableBuilder<List<SectionDTO>>(
valueListenable: filteredSections,
builder: (context, value, child) {
return GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 1, childAspectRatio: kIsWeb ? 1.7 : 1.95),
itemCount: value.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
//SectionDTO? section = await (appContext.getContext() as TabletAppContext).clientAPI!.sectionApi!.sectionGetDetail(menuDTO.sections![index].id!);
SectionDTO section = value[index];
var rawSectionData = rawSubSectionsData[index];
Navigator.push(
context,
SlideFromRightRoute(page: SectionPage(
configuration: configurationDTO,
rawSection: rawSectionData,
visitAppContextIn: appContext.getContext(),
sectionId: section.id!,
)),
);
},
child: Container(
decoration: isImageBackground ? boxDecoration(appContext, value[index], false, rawSubSectionsData[index]) : null,
padding: const EdgeInsets.all(20),
margin: const EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: isImageBackground ? Align(
alignment: Alignment.bottomRight,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: HtmlWidget(
value[index].title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "",
customStylesBuilder: (element) {
return {'text-align': 'right', 'font-family': "Roboto"};
},
textStyle: new TextStyle(fontSize: kMenuTitleDetailSize),
),
),
/*Align(
alignment: Alignment.centerRight,
child: HtmlWidget(
menuDTO.sections![index].description!.firstWhere((translation) => translation.language == appContext.getContext().language).value!,
customStylesBuilder: (element) {
return {'text-align': 'right'};
},
textStyle: new TextStyle(fontSize: kIsWeb? kWebSectionDescriptionDetailSize: kSectionDescriptionDetailSize, fontFamily: ""),
),
),*/
],
)
),
) : Column(
children: [
Expanded(
flex: 7,
child: Container(
decoration: BoxDecoration(
color: value[index].imageSource == null && value[index].type != SectionType.Video ? kBackgroundColor : null, // default color if no image
shape: BoxShape.rectangle,
image: value[index].imageSource != null || value[index].type == SectionType.Video ? new DecorationImage(
fit: BoxFit.contain, // contain or cover ?
image: ImageCustomProvider.getImageProvider(appContext, value[index].imageId, value[index].type == SectionType.Video ? getYoutubeThumbnailUrl(rawSubSectionsData[index]) : value[index].imageSource!),
): null,
),
)
),
Expanded(
flex: 3,
child: Container(
//color: Colors.yellow,
constraints: BoxConstraints(
maxWidth: size.width * 0.3,
),
child: Center(
child: HtmlWidget(
value[index].title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value ?? "",
customStylesBuilder: (element) {
return {'text-align': 'center', 'font-family': "Roboto"};
},
textStyle: const TextStyle(fontSize: 20),//calculateFontSize(constraints.maxWidth, constraints.maxHeight, kIsWeb ? kWebMenuTitleDetailSize : kMenuTitleDetailSize)),
),
),
)
)
],
),
),
);
}
);
}
),
],
),
),
],
),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: ScannerBouton(appContext: appContext),
),
),
],
),
);
}
void applyFilters(VisitAppContext visitAppContext) {
List<SectionDTO> result = _allSections;
if (searchValue != null && searchValue!.isNotEmpty) {
result = result.where((s) {
final rawTitle = TranslationHelper.get(s.title, visitAppContext);
final plainText = stripHtmlTags(rawTitle);
final normalizedTitle = removeDiacritics(plainText.toLowerCase());
final normalizedSearch = removeDiacritics(searchValue!.toLowerCase());
return normalizedTitle.contains(normalizedSearch);
}).toList();
} else if (searchNumberValue != null) {
result = result.where((s) => s.order! + 1 == searchNumberValue).toList();
}
filteredSections.value = result;
}
String stripHtmlTags(String htmlText) {
final exp = RegExp(r'<[^>]*>', multiLine: true, caseSensitive: false);
return htmlText.replaceAll(exp, '');
}
}
boxDecoration(AppContext appContext, SectionDTO section, bool isSelected, Object rawSubSectionData) {
VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext;
return BoxDecoration(
color: kBackgroundLight,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0),
image: section.imageSource != null || section.type == SectionType.Video ? DecorationImage(
fit: BoxFit.cover,
colorFilter: !isSelected? ColorFilter.mode(kBackgroundLight.withValues(alpha: 0.35), BlendMode.dstATop) : null,
image: ImageCustomProvider.getImageProvider(appContext, section.imageId, section.type == SectionType.Video ? getYoutubeThumbnailUrl(rawSubSectionData) : section.imageSource!),
): null,
boxShadow: const [
BoxShadow(
color: kBackgroundSecondGrey,
spreadRadius: 0.3,
blurRadius: 5,
offset: Offset(0, 1.5), // changes position of shadow
),
],
);
}
String getYoutubeThumbnailUrl(Object rawSectionData) {
try{
VideoDTO videoDTO = VideoDTO.fromJson(rawSectionData)!;
String thumbnailUrl = "";
if(videoDTO.source_ != null) {
//VideoDTO? videoDTO = VideoDTO.fromJson(jsonDecode(sectionDTO.data!));
Uri uri = Uri.parse(videoDTO.source_!);
String videoId = uri.queryParameters['v']!;
// Construire l'URL du thumbnail en utilisant l'identifiant de la vidéo YouTube
thumbnailUrl = 'https://img.youtube.com/vi/$videoId/0.jpg';
}
return thumbnailUrl;
} catch(e) {
return "";
}
}

View File

@ -0,0 +1,135 @@
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/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
class PdfFilter extends StatefulWidget {
final List<OrderedTranslationAndResourceDTO> pdfsList;
final Function(int?) onPDFSelected;
PdfFilter({required this.pdfsList, required this.onPDFSelected});
@override
_PdfFilterState createState() => _PdfFilterState();
}
class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMixin {
bool _isExpanded = false;
bool _showContent = false;
int _selectedOrderPdf = 0;
late AnimationController _controller;
late Animation<double> _widthAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
_widthAnimation = Tween<double>(begin: 40, end: 250).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
}
void toggleExpand() {
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();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
var currentLanguage = visitAppContext.language;
var primaryColor = visitAppContext.configuration?.primaryColor != null
? Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16))
: kSecondColor;
double rounded = visitAppContext.configuration?.roundedValue?.toDouble() ?? 20.0;
return AnimatedBuilder(
animation: _widthAnimation,
builder: (context, child) {
return Container(
width: _widthAnimation.value,
height: _isExpanded ? 300 : 75,
decoration: BoxDecoration(
color: _isExpanded ? primaryColor.withValues(alpha: 0.9) : primaryColor.withValues(alpha: 0.5) ,
borderRadius: BorderRadius.only(
topRight: Radius.circular(rounded),
bottomRight: Radius.circular(rounded),
),
),
child: _showContent
? Column(
children: [
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: toggleExpand,
),
Expanded(
child: ListView.builder(
itemCount: widget.pdfsList.length,
itemBuilder: (context, index) {
var pdfItem = widget.pdfsList[index];
var title = pdfItem.translationAndResourceDTOs!
.firstWhere((pfat) => pfat.language == currentLanguage)
.value;
bool isSelected = _selectedOrderPdf == pdfItem.order;
return ListTile(
title: HtmlWidget(
title!,
textStyle: TextStyle(
fontSize: 15.0,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? Colors.white : Colors.black,
fontFamily: 'Roboto',
),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
onTap: () {
setState(() {
_selectedOrderPdf = pdfItem.order!;
});
widget.onPDFSelected(_selectedOrderPdf);
toggleExpand(); // collapse after selection
},
tileColor: isSelected ? primaryColor.withValues(alpha: 0.6) : null,
);
},
),
),
],
)
: _isExpanded ? null : IconButton(
icon: const Icon(Icons.menu, color: Colors.white),
onPressed: toggleExpand,
),
);
},
);
}
}

View File

@ -0,0 +1,349 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
//import 'dart:html';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.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/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Sections/PDF/pdf_filter.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
class PDFPage extends StatefulWidget {
final PdfDTO section;
const PDFPage({super.key, required this.section});
@override
_PDFPage createState() => _PDFPage();
}
class _PDFPage extends State<PDFPage> {
PdfDTO pdfDTO = PdfDTO();
String remotePDFpath = "";
final Completer<PDFViewController> _controller = Completer<PDFViewController>();
int? pages = 0;
int? currentPage = 0;
bool isReady = false;
String errorMessage = '';
late ValueNotifier<OrderedTranslationAndResourceDTO?> selectedPdf = ValueNotifier<OrderedTranslationAndResourceDTO?>(pdfDTO.pdfs!.first);
ValueNotifier<Map<String, int>> currentState = ValueNotifier<Map<String, int>>({'page': 0, 'total': 1});
@override
void initState() {
/*print(widget.section!.data);
pdfDTO = PdfDTO.fromJson(jsonDecode(widget.section!.data!))!;
print(pdfDTO);*/
pdfDTO = widget.section;
pdfDTO.pdfs!.sort((a, b) => a.order!.compareTo(b.order!));
super.initState();
/*createFileOfPdfUrl(pdfDTO.source_!).then((f) {
setState(() {
remotePDFpath = f.path;
print("paaath");
print(remotePDFpath);
});
});*/
}
Future<File?> createFileOfPdfUrl(VisitAppContext visitAppContext, OrderedTranslationAndResourceDTO pdfFileDTO) async {
Completer<File?> completer = Completer();
if(pdfFileDTO.translationAndResourceDTOs!.firstWhere((pfat) => pfat.language == visitAppContext.language).resourceId == null) {
completer.complete(null);
} else {
var file = await _checkIfLocalResourceExists(visitAppContext, pdfFileDTO.translationAndResourceDTOs!.firstWhere((pfat) => pfat.language == visitAppContext.language).resourceId!);
if(file == null) {
print("Start download file from internet!");
try {
// "https://berlin2017.droidcon.cod.newthinking.net/sites/global.droidcon.cod.newthinking.net/files/media/documents/Flutter%20-%2060FPS%20UI%20of%20the%20future%20%20-%20DroidconDE%2017.pdf";
// final url = "https://pdfkit.org/docs/guide.pdf";
final url = pdfFileDTO.translationAndResourceDTOs!.firstWhere((pfat) => pfat.language == visitAppContext.language).resource!.url!;
final filename = url.substring(url.lastIndexOf("/") + 1);
var request = await HttpClient().getUrl(Uri.parse(url));
var response = await request.close();
var bytes = await consolidateHttpClientResponseBytes(response);
var dir = await getApplicationDocumentsDirectory();
print("Download files");
print("${dir.path}/$filename");
File file = File("${dir.path}/$filename");
await file.writeAsBytes(bytes, flush: true);
completer.complete(file);
} catch (e) {
throw Exception('Error parsing asset file!');
}
} else {
print("FOUND FILE PDF");
completer.complete(file);
}
}
return completer.future;
}
@override
void dispose() {
//_webView = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
var primaryColor = visitAppContext.configuration != null ? visitAppContext.configuration!.primaryColor != null ? new Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : kSecondColor : kSecondColor;
pdfDTO.pdfs!.sort((a, b) => a.order!.compareTo(b.order!));
Size size = MediaQuery.of(context).size;
var title = TranslationHelper.get(widget.section.title, appContext.getContext());
String cleanedTitle = title.replaceAll('\n', ' ').replaceAll('<br>', ' ');
return Stack(
children: [
Container(
height: size.height * 0.28,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
/*Color(0xFFDD79C2),
Color(0xFFB65FBE),
Color(0xFF9146BA),
Color(0xFF7633B8),
Color(0xFF6528B6),
Color(0xFF6025B6)*/
kMainColor0, //Color(0xFFf6b3c4)
kMainColor1,
kMainColor2,
],
),
image: widget.section.imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.65,
image: NetworkImage(
widget.section.imageSource!,
),
): null,
),
),
Column(
children: <Widget>[
SizedBox(
height: size.height * 0.11,
width: size.width,
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(top: 22.0),
child: SizedBox(
width: size.width *0.7,
child: HtmlWidget(
cleanedTitle,
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
),
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
],
),
),
Expanded(child: Container(
margin: const EdgeInsets.only(top: 0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 2,
offset: Offset(0, 1), // changes position of shadow
),
],
color: kBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
child: pdfDTO.pdfs != null && pdfDTO.pdfs!.isNotEmpty ?
Center(
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ValueListenableBuilder<OrderedTranslationAndResourceDTO?>(
valueListenable: selectedPdf,
builder: (context, value, _) {
return FutureBuilder(
future: createFileOfPdfUrl(visitAppContext, value!),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
print("snapshot.data");
print(snapshot.data);
if (snapshot.connectionState == ConnectionState.done) {
if(snapshot.data == null) {
return Center(child: Text("Aucun fichier à afficher"));
} else {
return Stack(
children: [
PDFView(
filePath: snapshot.data.path,
enableSwipe: true,
fitEachPage: true,
swipeHorizontal: true,
autoSpacing: false,
pageFling: true,
fitPolicy: FitPolicy.HEIGHT,
onRender: (_pages) {
//setState(() {
pages = _pages;
isReady = true;
//});
},
onError: (error) {
print(error.toString());
},
onPageError: (page, error) {
print('$page: ${error.toString()}');
},
onViewCreated: (PDFViewController pdfViewController) {
//_controller.complete(pdfViewController);
},
onPageChanged: (int? page, int? total) {
currentPage = page;
pages = total;
currentState.value = {'page': page!, 'total': total!};
print('page change: $page/$total');
},
),
Positioned(
bottom: 20,
left: 20,
child: ValueListenableBuilder<Map<String, int>>(
valueListenable: currentState,
builder: (context, value, _) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
color: primaryColor
),
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text("${value["page"]!+1}/${value["total"]!}",
style: const TextStyle(color: Colors.white, fontSize: 20)),
)
);
},
),
),
],
);
}
} else {
return Center(
child: Container(
child: LoadingCommon()
)
);
}
}
);
}
),
),
pdfDTO.pdfs!.length > 1 ? Align(
alignment: Alignment.centerLeft,
child: PdfFilter(
pdfsList: pdfDTO.pdfs!,
onPDFSelected: (selectedOrder) {
selectedPdf.value = pdfDTO.pdfs!.firstWhere((pdf) => pdf.order == selectedOrder);
}),
): const SizedBox(),
],
),
) :
Center(child: Text("Aucun pdf à afficher", style: TextStyle(fontSize: kNoneInfoOrIncorrect))))
)
)
],
)
],
);
/**/
}
} //_webView
Future<File?> _checkIfLocalResourceExists(VisitAppContext visitAppContext, String resourceId) async {
try {
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
Directory configurationDirectory = Directory('$localPath/${visitAppContext.configuration!.id}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
if(fileList.any((fileL) => fileL.uri.pathSegments.last.contains(resourceId))) {
File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(resourceId)).path);
return file;
}
} catch (e) {
print("ERROR _checkIfLocalResourceExists PDF");
print(e);
}
return null;
}

View File

@ -0,0 +1,68 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
class CorrectOverlay extends StatefulWidget {
final bool _isCorrect;
final VoidCallback _onTap;
CorrectOverlay(this._isCorrect, this._onTap);
@override
State createState() => new CorrectOverlayState();
}
class CorrectOverlayState extends State<CorrectOverlay>
with SingleTickerProviderStateMixin {
late Animation<double> _iconAnimation;
late AnimationController _iconAnimationController;
@override
void initState() {
super.initState();
_iconAnimationController = new AnimationController(
duration: new Duration(seconds: 2), vsync: this);
_iconAnimation = new CurvedAnimation(
parent: _iconAnimationController, curve: Curves.elasticOut);
_iconAnimation.addListener(() => this.setState(() {}));
_iconAnimationController.forward();
}
@override
void dispose() {
_iconAnimationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Container(
color: Colors.black54,
child: new InkWell(
onTap: () => widget._onTap(),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Container(
decoration: new BoxDecoration(
color: Colors.blueAccent, shape: BoxShape.circle),
child: new Transform.rotate(
angle: _iconAnimation.value * 2 * math.pi,
child: new Icon(
widget._isCorrect == true ? Icons.done : Icons.clear,
size: _iconAnimation.value * 80.0,
),
),
),
new Padding(
padding: new EdgeInsets.only(bottom: 20.0),
),
new Text(
widget._isCorrect == true ? "Correct!" : "Wrong!",
style: new TextStyle(color: Colors.white, fontSize: 30.0),
)
],
),
),
);
}
}

View File

@ -0,0 +1,95 @@
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/show_element_for_resource.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
void showMessage(TranslationAndResourceDTO translationAndResourceDTO, AppContext appContext, BuildContext context, Size size) {
print("translationAndResourceDTO");
print(translationAndResourceDTO);
VisitAppContext visitAppContext = appContext.getContext();
showDialog(
builder: (BuildContext context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0))
),
content: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if(translationAndResourceDTO.resourceId != null)
Container(
constraints: BoxConstraints(maxHeight: 250),
//color: Colors.cyan,
height: size.height *0.45,
width: size.width *0.5,
child: Center(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 30),
//border: Border.all(width: 3, color: Colors.black)
),
child: showElementForResource(ResourceDTO(id: translationAndResourceDTO.resourceId, type: translationAndResourceDTO.resource!.type, url: translationAndResourceDTO.resource!.url), appContext, true, false),
),
),
),
Container(
constraints: BoxConstraints(maxHeight: 350),
//color: Colors.green,
height: size.height *0.3,
width: size.width *0.5,
child: Center(
child: Align(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: HtmlWidget(
translationAndResourceDTO.value!,
customStylesBuilder: (element) {
return {'text-align': 'center', 'font-family': "Roboto"};
},
textStyle: const TextStyle(fontSize: kDescriptionSize),
),/*Text(
resourceDTO.label == null ? "" : resourceDTO.label,
style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)),*/
),
),
),
),
],
),
),
/*actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: AlignmentDirectional.bottomEnd,
child: Container(
width: 175,
height: 70,
child: RoundedButton(
text: "Merci",
icon: Icons.undo,
color: kSecondGrey,
press: () {
Navigator.of(context).pop();
},
fontSize: 20,
),
),
),
),
],
),
],*/
), context: context
);
}

View File

@ -0,0 +1,357 @@
import 'dart:convert';
import 'dart:async';
import 'dart:io';
import 'package:cached_network_image/cached_network_image.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/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Puzzle/message_dialog.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
import 'puzzle_piece.dart';
const IMAGE_PATH = 'image_path';
class PuzzlePage extends StatefulWidget {
final PuzzleDTO section;
PuzzlePage({required this.section});
@override
_PuzzlePage createState() => _PuzzlePage();
}
class _PuzzlePage extends State<PuzzlePage> {
PuzzleDTO puzzleDTO = PuzzleDTO();
int allInPlaceCount = 0;
bool isFinished = false;
GlobalKey _widgetKey = GlobalKey();
Size? realWidgetSize;
List<Widget> pieces = [];
bool isSplittingImage = true;
@override
void initState() {
//puzzleDTO = PuzzleDTO.fromJson(jsonDecode(widget.section!.data!))!;
puzzleDTO = widget.section;
puzzleDTO.rows = puzzleDTO.rows ?? 3;
puzzleDTO.cols = puzzleDTO.cols ?? 3;
WidgetsBinding.instance.addPostFrameCallback((_) async {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context, listen: false);
VisitAppContext visitAppContext = appContext.getContext();
print(puzzleDTO.messageDebut);
TranslationAndResourceDTO? messageDebut = puzzleDTO.messageDebut != null && puzzleDTO.messageDebut!.isNotEmpty ? puzzleDTO.messageDebut!.where((message) => message.language!.toUpperCase() == visitAppContext.language!.toUpperCase()).firstOrNull : null;
//await Future.delayed(const Duration(milliseconds: 50));
await WidgetsBinding.instance.endOfFrame;
getRealWidgetSize();
if(puzzleDTO.puzzleImage != null && puzzleDTO.puzzleImage!.url != null) {
//splitImage(Image.network(puzzleDTO.image!.resourceUrl!));
splitImage(CachedNetworkImage(
imageUrl: puzzleDTO.puzzleImage!.url!,
fit: BoxFit.fill,
errorWidget: (context, url, error) => Icon(Icons.error),
));
} else {
setState(() {
isSplittingImage = false;
});
}
if(messageDebut != null) {
showMessage(messageDebut, appContext, context, size);
}
});
super.initState();
}
Future<void> getRealWidgetSize() async {
RenderBox renderBox = _widgetKey.currentContext?.findRenderObject() as RenderBox;
Size size = renderBox.size;
setState(() {
realWidgetSize = size;
});
print("Taille réelle du widget : $size");
}
// we need to find out the image size, to be used in the PuzzlePiece widget
/*Future<Size> getImageSize(CachedNetworkImage image) async {
Completer<Size> completer = Completer<Size>();
/*image.image
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool _) {
completer.complete(
Size(info.image.width.toDouble(), info.image.height.toDouble()));
}));*/
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
imageBuilder: (BuildContext context, ImageProvider imageProvider) {
Completer<Size> completer = Completer<Size>();
imageProvider
.resolve(const ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool _) {
completer.complete(
Size(info.image.width.toDouble(), info.image.height.toDouble()));
}));
return CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
imageBuilder: (context, imageProvider) {
return Image(
image: imageProvider,
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
} else {
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / (loadingProgress.expectedTotalBytes ?? 1)
: null,
),
);
}
},
);
},
);
},
);
Size imageSize = await completer.future;
return imageSize;
}*/
// here we will split the image into small pieces
// using the rows and columns defined above; each piece will be added to a stack
void splitImage(CachedNetworkImage image) async {
//Size imageSize = await getImageSize(image);
//imageSize = realWidgetSize!;
Size imageSize = Size(realWidgetSize!.width * 1.25, realWidgetSize!.height * 1.25);
for (int x = 0; x < puzzleDTO.rows!; x++) {
for (int y = 0; y < puzzleDTO.cols!; y++) {
setState(() {
pieces.add(
PuzzlePiece(
key: GlobalKey(),
image: image,
imageSize: imageSize,
row: x,
col: y,
maxRow: puzzleDTO.rows!,
maxCol: puzzleDTO.cols!,
bringToTop: bringToTop,
sendToBack: sendToBack,
),
);
});
}
}
setState(() {
isSplittingImage = false;
});
}
// when the pan of a piece starts, we need to bring it to the front of the stack
void bringToTop(Widget widget) {
setState(() {
pieces.remove(widget);
pieces.add(widget);
});
}
// when a piece reaches its final position,
// it will be sent to the back of the stack to not get in the way of other, still movable, pieces
void sendToBack(Widget widget) {
setState(() {
allInPlaceCount++;
isFinished = allInPlaceCount == puzzleDTO.rows! * puzzleDTO.cols!;
pieces.remove(widget);
pieces.insert(0, widget);
if(isFinished) {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context, listen: false);
VisitAppContext visitAppContext = appContext.getContext();
TranslationAndResourceDTO? messageFin = puzzleDTO.messageFin != null && puzzleDTO.messageFin!.isNotEmpty ? puzzleDTO.messageFin!.where((message) => message.language!.toUpperCase() == visitAppContext.language!.toUpperCase()).firstOrNull : null;
if(messageFin != null) {
showMessage(messageFin, appContext, context, size);
}
}
});
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
var title = TranslationHelper.get(widget.section.title, appContext.getContext());
String cleanedTitle = title.replaceAll('\n', ' ').replaceAll('<br>', ' ');
return Stack(
children: [
Container(
height: size.height * 0.28,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
/*Color(0xFFDD79C2),
Color(0xFFB65FBE),
Color(0xFF9146BA),
Color(0xFF7633B8),
Color(0xFF6528B6),
Color(0xFF6025B6)*/
kMainColor0, //Color(0xFFf6b3c4)
kMainColor1,
kMainColor2,
],
),
image: widget.section.imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.65,
image: NetworkImage(
widget.section.imageSource!,
),
): null,
),
),
Column(
children: <Widget>[
SizedBox(
height: size.height * 0.11,
width: size.width,
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(top: 22.0),
child: SizedBox(
width: size.width *0.7,
child: HtmlWidget(
cleanedTitle,
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
),
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
],
),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 2,
offset: Offset(0, 1), // changes position of shadow
),
],
color: kBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
child: Center(
//color: Colors.green,
child: Container(
//color: Colors.green,
child: Padding(
key: _widgetKey,
padding: const EdgeInsets.all(0.0),
child: isSplittingImage ? Center(child: LoadingCommon()) :
puzzleDTO.puzzleImage == null || puzzleDTO.puzzleImage!.url == null || realWidgetSize == null
? Center(child: Text("Aucune image à afficher", style: TextStyle(fontSize: kNoneInfoOrIncorrect)))
: Center(
child: Padding(
padding: const EdgeInsets.all(0.0),
child: Container(
width: visitAppContext.puzzleSize != null && visitAppContext.puzzleSize!.width > 0 ? visitAppContext.puzzleSize!.width : realWidgetSize!.width * 0.8,
height: visitAppContext.puzzleSize != null && visitAppContext.puzzleSize!.height > 0 ? visitAppContext.puzzleSize!.height +1.5 : realWidgetSize!.height * 0.85,
child: Stack(
children: pieces,
),
),
),
),
),
),
)
)
),
),
],
),
],
);
}
}

View File

@ -0,0 +1,287 @@
import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:provider/provider.dart';
class PuzzlePiece extends StatefulWidget {
final CachedNetworkImage image;
final Size imageSize;
final int row;
final int col;
final int maxRow;
final int maxCol;
final Function bringToTop;
final Function sendToBack;
static PuzzlePiece fromMap(Map<String, dynamic> map) {
return PuzzlePiece(
image: map['image'],
imageSize: map['imageSize'],
row: map['row'],
col: map['col'],
maxRow: map['maxRow'],
maxCol: map['maxCol'],
bringToTop: map['bringToTop'],
sendToBack: map['SendToBack'],
);
}
PuzzlePiece(
{Key? key,
required this.image,
required this.imageSize,
required this.row,
required this.col,
required this.maxRow,
required this.maxCol,
required this.bringToTop,
required this.sendToBack})
: super(key: key);
@override
_PuzzlePieceState createState() => _PuzzlePieceState();
}
class _PuzzlePieceState extends State<PuzzlePiece> {
// the piece initial top offset
double? top;
// the piece initial left offset
double? left;
// can we move the piece ?
bool isMovable = true;
GlobalKey _widgetPieceKey = GlobalKey();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
RenderBox renderBox = _widgetPieceKey.currentContext?.findRenderObject() as RenderBox;
Size size = renderBox.size;
final appContext = Provider.of<AppContext>(context, listen: false);
VisitAppContext visitAppContext = appContext.getContext();
visitAppContext.puzzleSize = size; // do it another way
appContext.setContext(visitAppContext);
if(widget.row == 0 && widget.col == 0) {
widget.sendToBack(widget);
}
});
});
}
@override
Widget build(BuildContext context) {
var isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
var imageHeight = isPortrait ? widget.imageSize.width/1.55 : widget.imageSize.width;
var imageWidth = isPortrait ? widget.imageSize.width/1.55 : widget.imageSize.height;
final pieceWidth = imageWidth / widget.maxCol;
final pieceHeight = imageHeight / widget.maxRow;
if (top == null) {
top = Random().nextInt((imageHeight - pieceHeight).ceil()).toDouble();
var test = top!;
test -= widget.row * pieceHeight;
top = test /7; // TODO change ?
}
if (left == null) {
left = Random().nextInt((imageWidth - pieceWidth).ceil()).toDouble();
var test = left!;
test -= widget.col * pieceWidth;
left = test /7; // TODO change ?
}
if(widget.row == 0 && widget.col == 0) {
top = 0;
left = 0;
isMovable = false;
}
return Positioned(
top: top,
left: left,
width: imageWidth,
child: Container(
key: _widgetPieceKey,
decoration: widget.col == 0 && widget.row == 0 ? BoxDecoration(
border: Border.all(
color: Colors.black,
width: 0.5,
),
) : null,
child: GestureDetector(
onTap: () {
if (isMovable) {
widget.bringToTop(widget);
}
},
onPanStart: (_) {
if (isMovable) {
widget.bringToTop(widget);
}
},
onPanUpdate: (dragUpdateDetails) {
if (isMovable) {
setState(() {
var testTop = top!;
var testLeft = left!;
testTop = top!;
testLeft = left!;
testTop += dragUpdateDetails.delta.dy;
testLeft += dragUpdateDetails.delta.dx;
top = testTop;
left = testLeft;
if (-10 < top! && top! < 10 && -10 < left! && left! < 10) {
top = 0;
left = 0;
isMovable = false;
widget.sendToBack(widget);
}
});
}
},
child: ClipPath(
child: CustomPaint(
foregroundPainter: PuzzlePiecePainter(
widget.row, widget.col, widget.maxRow, widget.maxCol),
child: widget.image),
clipper: PuzzlePieceClipper(
widget.row, widget.col, widget.maxRow, widget.maxCol),
),
),
));
}
}
// this class is used to clip the image to the puzzle piece path
class PuzzlePieceClipper extends CustomClipper<Path> {
final int row;
final int col;
final int maxRow;
final int maxCol;
PuzzlePieceClipper(this.row, this.col, this.maxRow, this.maxCol);
@override
Path getClip(Size size) {
return getPiecePath(size, row, col, maxRow, maxCol);
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
// this class is used to draw a border around the clipped image
class PuzzlePiecePainter extends CustomPainter {
final int row;
final int col;
final int maxRow;
final int maxCol;
PuzzlePiecePainter(this.row, this.col, this.maxRow, this.maxCol);
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..color = Colors.black//Color(0x80FFFFFF)
..style = PaintingStyle.stroke
..strokeWidth = 2.5;
canvas.drawPath(getPiecePath(size, row, col, maxRow, maxCol), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
// this is the path used to clip the image and, then, to draw a border around it; here we actually draw the puzzle piece
Path getPiecePath(Size size, int row, int col, int maxRow, int maxCol) {
final width = size.width / maxCol;
final height = size.height / maxRow;
final offsetX = col * width;
final offsetY = row * height;
final bumpSize = height / 4;
var path = Path();
path.moveTo(offsetX, offsetY);
if (row == 0) {
// top side piece
path.lineTo(offsetX + width, offsetY);
} else {
// top bump
path.lineTo(offsetX + width / 3, offsetY);
path.cubicTo(
offsetX + width / 6,
offsetY - bumpSize,
offsetX + width / 6 * 5,
offsetY - bumpSize,
offsetX + width / 3 * 2,
offsetY);
path.lineTo(offsetX + width, offsetY);
}
if (col == maxCol - 1) {
// right side piece
path.lineTo(offsetX + width, offsetY + height);
} else {
// right bump
path.lineTo(offsetX + width, offsetY + height / 3);
path.cubicTo(
offsetX + width - bumpSize,
offsetY + height / 6,
offsetX + width - bumpSize,
offsetY + height / 6 * 5,
offsetX + width,
offsetY + height / 3 * 2);
path.lineTo(offsetX + width, offsetY + height);
}
if (row == maxRow - 1) {
// bottom side piece
path.lineTo(offsetX, offsetY + height);
} else {
// bottom bump
path.lineTo(offsetX + width / 3 * 2, offsetY + height);
path.cubicTo(
offsetX + width / 6 * 5,
offsetY + height - bumpSize,
offsetX + width / 6,
offsetY + height - bumpSize,
offsetX + width / 3,
offsetY + height);
path.lineTo(offsetX, offsetY + height);
}
if (col == 0) {
// left side piece
path.close();
} else {
// left bump
path.lineTo(offsetX, offsetY + height / 3 * 2);
path.cubicTo(
offsetX - bumpSize,
offsetY + height / 6 * 5,
offsetX - bumpSize,
offsetY + height / 6,
offsetX,
offsetY + height / 3);
path.close();
}
return path;
}

View File

@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
class ScoreWidget extends InheritedWidget {
ScoreWidget({Key? key, required Widget child}) : super(key: key, child: child);
int allInPlaceCount = 0;
static ScoreWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ScoreWidget>() as ScoreWidget;
}
@override
bool updateShouldNotify(ScoreWidget oldWidget) => false;
}

View File

@ -1,4 +1,5 @@
import 'package:mymuseum_visitapp/Components/Carousel/carousel_slider.dart' as cs;
import 'package:carousel_slider/carousel_controller.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
@ -23,7 +24,7 @@ class QuestionsListWidget extends StatefulWidget {
class _QuestionsListWidget extends State<QuestionsListWidget> {
List<QuestionSubDTO> _questionsSubDTO = <QuestionSubDTO>[];
cs.CarouselController? sliderController;
CarouselSliderController? sliderController;
int currentIndex = 1;
bool kIsWeb = false;
@ -31,7 +32,7 @@ class _QuestionsListWidget extends State<QuestionsListWidget> {
@override
void initState() {
super.initState();
sliderController = cs.CarouselController();
sliderController = CarouselSliderController();
_questionsSubDTO = widget.questionsSubDTO!;
}
@ -57,8 +58,8 @@ class _QuestionsListWidget extends State<QuestionsListWidget> {
MediaQuery.of(context).size.height * 0.85 :
MediaQuery.of(context).size.height * 0.75 :
widget.orientation == Orientation.portrait ?
MediaQuery.of(context).size.height :
MediaQuery.of(context).size.height,
MediaQuery.of(context).size.height * 0.88:
MediaQuery.of(context).size.height * 0.88,
width: double.infinity,
//color: Colors.orange,
child: Stack(
@ -69,10 +70,10 @@ class _QuestionsListWidget extends State<QuestionsListWidget> {
mainAxisAlignment: MainAxisAlignment.start,
children: [
if(_questionsSubDTO.isNotEmpty)
cs.CarouselSlider(
CarouselSlider(
carouselController: sliderController,
options: cs.CarouselOptions(
onPageChanged: (int index, cs.CarouselPageChangedReason reason) {
options: CarouselOptions(
onPageChanged: (int index, CarouselPageChangedReason reason) {
setState(() {
currentIndex = index + 1;
});

View File

@ -0,0 +1,506 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
//import 'package:confetti/confetti.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/CustomAppBar.dart';
import 'package:mymuseum_visitapp/Components/rounded_button.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/ResponseSubDTO.dart';
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/Quizz/drawPath.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Quiz/questions_list.dart';
//import 'package:mymuseum_visitapp/Screens/Quizz/showResponses.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';
class QuizPage extends StatefulWidget {
const QuizPage({Key? key, required this.visitAppContextIn, required this.quizDTO, required this.resourcesModel}) : super(key: key);
final QuizDTO quizDTO;
final VisitAppContext visitAppContextIn;
final List<ResourceModel?> resourcesModel;
@override
State<QuizPage> createState() => _QuizPageState();
}
class _QuizPageState extends State<QuizPage> {
List<ResourceModel?> resourcesModel = <ResourceModel?>[];
ResourceModel? audioResourceModel;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
late Uint8List audiobytes;
late VisitAppContext visitAppContext;
List<QuestionSubDTO> _questionsSubDTO = <QuestionSubDTO>[];
//ConfettiController? _controllerCenter;
int currentIndex = 1;
bool showResult = false;
bool showResponses = false;
bool kIsWeb = false;
bool isResultPage = false;
@override
void initState() {
widget.visitAppContextIn.isContentCurrentlyShown = true;
//_controllerCenter = ConfettiController(duration: const Duration(seconds: 10));
//_controllerCenter!.play();
if(widget.quizDTO.questions != null) {
_questionsSubDTO = QuestionSubDTO().fromJSON(widget.quizDTO.questions!);
}
super.initState();
}
@override
void dispose() {
visitAppContext.isContentCurrentlyShown = false;
currentIndex = 1;
//_controllerCenter!.dispose();
if(widget.quizDTO.questions != null) {
_questionsSubDTO = QuestionSubDTO().fromJSON(widget.quizDTO.questions!);
}
super.dispose();
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
Size size = MediaQuery.of(context).size;
visitAppContext = appContext.getContext();
return Scaffold(
key: _scaffoldKey,
/*appBar: CustomAppBar(
title: TranslationHelper.get(widget.quizDTO.title, visitAppContext),
isHomeButton: false,
),*/
body: Column(
children: [
Stack(
fit: StackFit.passthrough,
children: [
Container(
height: size.height * 0.12,
width: size.width,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 2,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
kMainColor0,
kMainColor1,
kMainColor2,
],
),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(25),
bottomRight: Radius.circular(25),
),
image: widget.quizDTO.imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.65,
image: NetworkImage(
widget.quizDTO.imageSource!,
),
): null,
),
),
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
setState(() {
Navigator.of(context).pop();
});
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
],
),
OrientationBuilder(
builder: (context, orientation) {
if(showResult) {
var goodResponses = 0;
for (var question in _questionsSubDTO) {
if(question.chosen == question.responsesSubDTO!.indexWhere((response) => response.isGood!)) {
goodResponses +=1;
}
}
log("goodResponses =" + goodResponses.toString());
List<TranslationAndResourceDTO> levelToShow = [];
var test = goodResponses/widget.quizDTO.questions!.length;
if((0 == test || test < 0.25) && widget.quizDTO.badLevel != null) {
levelToShow = widget.quizDTO.badLevel!;
}
if((test>=0.25 && test < 0.5) && widget.quizDTO.mediumLevel != null) {
levelToShow = widget.quizDTO.mediumLevel!;
}
if((test>=0.5 && test < 0.75) && widget.quizDTO.goodLevel != null) {
levelToShow = widget.quizDTO.goodLevel!;
}
if((test>=0.75 && test <= 1) && widget.quizDTO.greatLevel != null) {
levelToShow = widget.quizDTO.greatLevel!;
}
return SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/*Center(
child: SizedBox(
width: 5,
height: 5,
child: ConfettiWidget(
confettiController: _controllerCenter!,
blastDirectionality: BlastDirectionality.explosive,
shouldLoop: false, // start again as soon as the animation is finished
colors: const [
kMainColor,
kSecondColor,
kConfigurationColor,
kMainColor1
//Colors.pink,
//Colors.orange,
//Colors.purple
], // manually specify the colors to be used
createParticlePath: drawPath, // define a custom shape/path.
),
),
),*/
if (orientation == Orientation.portrait)
Column(
children: [
if (!showResponses && levelToShow.firstWhere((label) => label.language == visitAppContext.language).resource?.url != null) // TODO SUPPORT OTHER THAN IMAGES
resultImage(visitAppContext, size, levelToShow, orientation),
if(!showResponses)
// TEXT BOX WITH MAIN SCORE
Text('$goodResponses/${widget.quizDTO.questions!.length}', textAlign: TextAlign.center, style: TextStyle(fontSize: kIsWeb ? (showResponses ? 60 : 100) : 75, color: kBackgroundSecondGrey)),
],
),
if (orientation == Orientation.landscape)
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if(!showResponses)
// TEXT BOX WITH MAIN SCORE
Text('$goodResponses/${widget.quizDTO.questions!.length}', textAlign: TextAlign.center, style: TextStyle(fontSize: kIsWeb ? (showResponses ? 60 : 100) : 75, color: kBackgroundSecondGrey)),
if (!showResponses && levelToShow.firstWhere((label) => label.language == visitAppContext.language).resource?.url != null)
resultImage(visitAppContext, size, levelToShow, orientation),
],
),
if(!showResponses)
// TEXT BOX WITH LEVEL TEXT RESULT
resultText(size, levelToShow, appContext),
if(showResponses)
QuestionsListWidget(
questionsSubDTO: _questionsSubDTO,
isShowResponse: true,
onShowResponse: () {},
orientation: orientation,
),
// RESPONSE BOX
//ShowReponsesWidget(questionsSubDTO: _questionsSubDTO),
if(orientation == Orientation.portrait && !showResponses)
// Buttons
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: resultButtons(size, orientation, visitAppContext),
),
if(orientation == Orientation.landscape && !showResponses)
// Buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: resultButtons(size, orientation, visitAppContext),
),
],
),
);
} else {
return QuestionsListWidget(
isShowResponse: false,
questionsSubDTO: _questionsSubDTO,
onShowResponse: () {
setState(() {
showResult = true;
});
},
orientation: orientation,
);
}
}
),
],
),
floatingActionButton: showResponses ? FloatingActionButton(
onPressed: () {
setState(() {
showResult = false;
showResponses = false;
currentIndex = 1;
_questionsSubDTO = QuestionSubDTO().fromJSON(widget.quizDTO.questions!);
});
},
backgroundColor: kBackgroundSecondGrey,
child: const Icon(Icons.undo),
) : null,
floatingActionButtonLocation: FloatingActionButtonLocation.miniEndFloat,
);
}
/*Future<QuizDTO?> getQuizz(AppContext appContext, Client client, String sectionId) async {
try {
if(sectionDTO == null || quizDTO == null) {
bool isConfigOffline = (appContext.getContext() as VisitAppContext).configuration!.isOffline!;
if(isConfigOffline)
{
// OFFLINE
List<Map<String, dynamic>> sectionTest = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.sections, sectionId);
if(sectionTest.isNotEmpty) {
sectionDTO = DatabaseHelper.instance.getSectionFromDB(sectionTest.first);
try {
SectionRead sectionRead = SectionRead(id: sectionDTO!.id!, readTime: DateTime.now().millisecondsSinceEpoch);
await DatabaseHelper.instance.insert(DatabaseTableType.articleRead, sectionRead.toMap());
visitAppContext.readSections.add(sectionRead);
appContext.setContext(visitAppContext);
} catch (e) {
print("DATABASE ERROR SECTIONREAD");
print(e);
}
} else {
print("EMPTY SECTION");
}
} else
{
// ONLINE
SectionDTO? sectionOnline = await client.sectionApi!.sectionGetDetail(sectionId);
if(sectionOnline != null) {
sectionDTO = sectionOnline;
} else {
print("EMPTY SECTION");
}
}
if(sectionDTO!.type == SectionType.Quiz) {
quizDTO = QuizDTO.fromJson(jsonDecode(sectionDTO!.data!));
}
if(quizDTO != null) {
quizDTO!.questions!.sort((a, b) => a.order!.compareTo(b.order!));
_questionsSubDTO = QuestionSubDTO().fromJSON(quizDTO!.questions!);
if(quizDTO!.questions != null && quizDTO!.questions!.isNotEmpty) {
quizDTO!.questions!.sort((a, b) => a.order!.compareTo(b.order!));
for (var question in quizDTO!.questions!) {
if(isConfigOffline)
{
// OFFLINE
if(question.imageBackgroundResourceId != null) {
List<Map<String, dynamic>> ressourceQuizz = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, question.imageBackgroundResourceId!);
if(ressourceQuizz.isNotEmpty) {
resourcesModel.add(DatabaseHelper.instance.getResourceFromDB(ressourceQuizz.first));
} else {
print("EMPTY resourcesModel - second");
}
}
}
else
{
// ONLINE
if(question.imageBackgroundResourceId != null) {
resourcesModel.add(ResourceModel(id: question.imageBackgroundResourceId, source: question.imageBackgroundResourceUrl, type: ResourceType.Image));
}
}
}
}
}
setState(() {
//print(sectionDTO!.title);
});
} else {
return null; // TODO return local list..
}
} catch (e) {
print(e);
print("IN CATCH");
return null;
}
}*/
resultImage(VisitAppContext visitAppContext, Size size, List<TranslationAndResourceDTO> levelToShow, Orientation orientation) {
return Container(
//height: size.height * 0.2,
//width: size.width * 0.25,
constraints: BoxConstraints(
maxHeight: size.height * 0.25,
maxWidth: kIsWeb ? size.width * 0.20 : orientation == Orientation.portrait ? size.width * 0.85 : size.width * 0.4,
),
alignment: Alignment.center,
decoration: BoxDecoration(
image: levelToShow.where((label) => label.language == visitAppContext.language).isNotEmpty ? DecorationImage(
fit: BoxFit.contain,
opacity: 0.85,
image: NetworkImage(
levelToShow.firstWhere((label) => label.language == visitAppContext.language).resource!.url!,
),
): null,
borderRadius: const BorderRadius.all( Radius.circular(50.0)),
border: Border.all(
color: kBackgroundGrey,
width: 1.0,
),
),
child: Container(
//borderRadius: BorderRadius.all(Radius.circular(25.0)),
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
image: DecorationImage(
image: NetworkImage(
levelToShow.firstWhere((label) => label.language == visitAppContext.language).resource!.url!, // TODO REDUNDANCY here??
),
fit: BoxFit.cover,
),
borderRadius: const BorderRadius.all( Radius.circular(50.0)),
border: Border.all(
color: kBackgroundGrey,
width: 1.0,
),
),
),
);
}
resultText(Size size, List<TranslationAndResourceDTO> levelToShow, AppContext appContext) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Container(
width: size.width *0.75,
height: kIsWeb ? (showResponses ? size.height *0.10 : size.height *0.20) : size.height *0.25,
decoration: BoxDecoration(
color: kBackgroundLight, //kBackgroundLight
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(10.0),
boxShadow: const [
BoxShadow(
color: kBackgroundSecondGrey,
spreadRadius: 0.3,
blurRadius: 4,
offset: Offset(0, 2), // changes position of shadow
),
],
),
child: Center(
child: SizedBox(
width: double.infinity,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: HtmlWidget(
TranslationHelper.getWithResource(levelToShow, appContext.getContext() as VisitAppContext),textStyle: const TextStyle(fontFamily: 'Roboto', fontSize: 20),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
}
)
//Text(, textAlign: TextAlign.center, style: TextStyle(fontSize: kIsWeb ? kDescriptionSize : kDescriptionSize)),
),
),
),
),
),
);
}
resultButtons(Size size, Orientation orientation, VisitAppContext visitAppContext) {
return [
Padding(
padding: const EdgeInsets.all(4),
child: SizedBox(
height: kIsWeb ? 50 : 40,
width: orientation == Orientation.portrait ? size.width * 0.6 : size.width * 0.35,
child: RoundedButton(
text: TranslationHelper.getFromLocale("restart", visitAppContext),
color: kBackgroundSecondGrey,
textColor: kBackgroundLight,
icon: Icons.undo,
press: () {
setState(() {
showResult = false;
showResponses = false;
currentIndex = 1;
_questionsSubDTO = QuestionSubDTO().fromJSON(widget.quizDTO.questions!);
});
},
fontSize: 18,
horizontal: 20,
vertical: 5
),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: kIsWeb ? 50 : 40,
width: orientation == Orientation.portrait ? size.width * 0.6 : size.width * 0.35,
child: RoundedButton(
text: TranslationHelper.getFromLocale("responses", visitAppContext),
color: kBackgroundSecondGrey,
textColor: kBackgroundLight,
icon: Icons.assignment_turned_in,
press: () {
setState(() {
showResponses = true;
});
},
fontSize: 18,
horizontal: 20,
vertical: 5
),
),
)
];
}
}

View File

@ -0,0 +1,359 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api_new/api.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:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
import 'package:photo_view/photo_view.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class SliderPage extends StatefulWidget {
final SliderDTO section;
SliderPage({required this.section});
@override
_SliderPage createState() => _SliderPage();
}
class _SliderPage extends State<SliderPage> {
SliderDTO sliderDTO = SliderDTO();
CarouselSliderController? sliderController;
ValueNotifier<int> currentIndex = ValueNotifier<int>(1);
late ConfigurationDTO configurationDTO;
@override
void initState() {
sliderController = CarouselSliderController();
sliderDTO = widget.section;
sliderDTO.contents!.sort((a, b) => a.order!.compareTo(b.order!));
super.initState();
}
@override
void dispose() {
sliderController = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext;
Color? primaryColor = visitAppContext.configuration!.primaryColor != null ? Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : null;
configurationDTO = appContext.getContext().configuration;
return Stack(
children: [
Container(
height: size.height,
width: size.width,
color: kBackgroundLight,
),
if(sliderDTO.contents != null && sliderDTO.contents!.isNotEmpty)
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<Widget>((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(visitAppContext.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),
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(visitAppContext.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(),
),
),
/*if(sliderDTO.contents != null && sliderDTO.contents!.length > 1)
Positioned(
top: MediaQuery.of(context).size.height * 0.35,
right: 60,
child: InkWell(
onTap: () {
if (sliderDTO.contents!.length > 0)
sliderController!.nextPage(duration: new Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
},
child: Icon(
Icons.chevron_right,
size: 90,
color: primaryColor ?? kMainColor,
),
)
),*/
/*if(sliderDTO.contents != null && sliderDTO.contents!.length > 1)
Positioned(
top: MediaQuery.of(context).size.height * 0.35,
left: 60,
child: InkWell(
onTap: () {
if (sliderDTO.contents!.length > 0)
sliderController!.previousPage(duration: new Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
},
child: Icon(
Icons.chevron_left,
size: 90,
color: primaryColor ?? kMainColor,
),
)
),*/
if(sliderDTO.contents != null && sliderDTO.contents!.isNotEmpty) // Todo replace by dot ?
Padding(
padding: widget.section.parentId == null ? const EdgeInsets.only(bottom: 20) : const EdgeInsets.only(left: 15, bottom: 20),
child: Align(
alignment: widget.section.parentId == null ? Alignment.bottomCenter : Alignment.bottomLeft,
child: InkWell(
onTap: () {
sliderController!.previousPage(duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
},
child: ValueListenableBuilder<int>(
valueListenable: currentIndex,
builder: (context, value, _) {
return AnimatedSmoothIndicator(
activeIndex: value -1,
count: sliderDTO.contents!.length,
effect: ExpandingDotsEffect(activeDotColor: primaryColor!),
);
/*Text(
value.toString()+'/'+sliderDTO.contents!.length.toString(),
style: const TextStyle(fontSize: 25, fontWeight: FontWeight.w500),
);*/
}
),
)
),
),
if(sliderDTO.contents == null || sliderDTO.contents!.isEmpty)
const Center(child: Text("Aucun contenu à afficher", style: TextStyle(fontSize: kNoneInfoOrIncorrect))),
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
// Description
/*Container(
height: sliderDTO.images != null && sliderDTO.images.length > 0 ? size.height *0.3 : size.height *0.6,
width: MediaQuery.of(context).size.width *0.35,
decoration: BoxDecoration(
color: kBackgroundLight,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(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: Text(sliderDTO., textAlign: TextAlign.center, style: TextStyle(fontSize: 15)),
),
),
),*/
]
);
}
getElementForResource(AppContext appContext, ContentDTO i) {
var widgetToInclude;
VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext;
switch(i.resource!.type) {
case ResourceType.Image:
widgetToInclude = PhotoView(
imageProvider: ImageCustomProvider.getImageProvider(appContext, i.resourceId!, i.resource!.url!),
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.contained * 3.0,
backgroundDecoration: BoxDecoration(
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),
),
);
break;
case ResourceType.ImageUrl:
widgetToInclude = PhotoView(
imageProvider: CachedNetworkImageProvider(i.resource!.url!),
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.contained * 3.0,
backgroundDecoration: BoxDecoration(
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),
),
);
break;
case ResourceType.Video:
case ResourceType.VideoUrl:
case ResourceType.Audio:
widgetToInclude = Container(
decoration: BoxDecoration(
color: kBackgroundLight,
//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,
),
),
),
);
}
}

View File

@ -0,0 +1,263 @@
import 'dart:convert';
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/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
//import 'package:youtube_player_iframe/youtube_player_iframe.dart' as iframe;
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class VideoPage extends StatefulWidget {
final VideoDTO section;
VideoPage({required this.section});
@override
_VideoPage createState() => _VideoPage();
}
class _VideoPage extends State<VideoPage> {
//iframe.YoutubePlayer? _videoViewWeb;
YoutubePlayer? _videoView;
VideoDTO? videoDTO;
late YoutubePlayerController _controller;
bool isFullScreen = false;
@override
void initState() {
//print(widget.section!.data);
//videoDTO = VideoDTO.fromJson(jsonDecode(widget.section!.data!));
//print(videoDTO);
videoDTO= widget.section;
String? videoId;
if (videoDTO!.source_ != null && videoDTO!.source_!.isNotEmpty) {
videoId = YoutubePlayer.convertUrlToId(videoDTO!.source_!);
/*if (false) {
final _controllerWeb = iframe.YoutubePlayerController(
params: iframe.YoutubePlayerParams(
mute: false,
showControls: true,
showFullscreenButton: false,
loop: true,
showVideoAnnotations: false,
strictRelatedVideos: false,
enableKeyboard: false,
enableCaption: false,
pointerEvents: iframe.PointerEvents.auto
),
);
_controllerWeb.loadVideo(videoDTO!.source_!);
_videoViewWeb = iframe.YoutubePlayer(
controller: _controllerWeb,
//showVideoProgressIndicator: false,
/*progressIndicatorColor: Colors.amber,
progressColors: ProgressBarColors(
playedColor: Colors.amber,
handleColor: Colors.amberAccent,
),*/
);
} else {*/
videoId = YoutubePlayer.convertUrlToId(videoDTO!.source_!);
_controller = YoutubePlayerController(
initialVideoId: videoId!,
flags: const YoutubePlayerFlags(
autoPlay: true,
controlsVisibleAtStart: false,
loop: true,
hideControls: false,
hideThumbnail: true,
),
)..addListener(_onYoutubePlayerChanged);
_videoView = YoutubePlayer(
controller: _controller,
//showVideoProgressIndicator: false,
progressIndicatorColor: kMainColor,
progressColors: const ProgressBarColors(
playedColor: kMainColor,
handleColor: kSecondColor,
),
);
//}
_controller.toggleFullScreenMode();
super.initState();
}
}
void _onYoutubePlayerChanged() {
if (_controller.value.isFullScreen) {
setState(() {
isFullScreen = true;
});
} else {
setState(() {
isFullScreen = false;
});
}
}
@override
void dispose() {
_videoView = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context);
var title = TranslationHelper.get(widget.section.title, appContext.getContext());
String cleanedTitle = title.replaceAll('\n', ' ').replaceAll('<br>', ' ');
return Stack(
children: [
!isFullScreen ? Container(
height: size.height * 0.28,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
/*Color(0xFFDD79C2),
Color(0xFFB65FBE),
Color(0xFF9146BA),
Color(0xFF7633B8),
Color(0xFF6528B6),
Color(0xFF6025B6)*/
kMainColor0, //Color(0xFFf6b3c4)
kMainColor1,
kMainColor2,
],
),
image: widget.section.imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.65,
image: NetworkImage(
widget.section.imageSource!,
),
): null,
),
) : const SizedBox(),
Column(
children: <Widget>[
!isFullScreen ? SizedBox(
height: size.height * 0.11,
width: size.width,
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(top: 22.0),
child: SizedBox(
width: size.width *0.7,
child: HtmlWidget(
cleanedTitle,
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
),
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
_controller.dispose();
_videoView = null;
Navigator.of(context).pop();
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
],
),
): const SizedBox(),
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 2,
offset: Offset(0, 1), // changes position of shadow
),
],
color: kBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: ClipRRect(
borderRadius: !isFullScreen ? const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
): BorderRadius.zero,
child: videoDTO!.source_ != null && videoDTO!.source_!.isNotEmpty ?
_videoView :
const Center(child: Text("La vidéo ne peut pas être affichée, l'url est incorrecte", style: TextStyle(fontSize: kNoneInfoOrIncorrect))))
),
),
],
),
isFullScreen ? Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
_controller.toggleFullScreenMode();
_controller.dispose();
_videoView = null;
Navigator.of(context).pop();
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
): const SizedBox(),
],
);
}
}

View File

@ -0,0 +1,382 @@
import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.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/Models/weatherData.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
class WeatherPage extends StatefulWidget {
final WeatherDTO section;
WeatherPage({required this.section});
@override
State<WeatherPage> createState() => _WeatherPageState();
}
class _WeatherPageState extends State<WeatherPage> {
WeatherDTO weatherDTO = WeatherDTO();
WeatherData? weatherData = null;
int nbrNextHours = 5;
@override
void initState() {
/*print(widget.section!.data);
weatherDTO = WeatherDTO.fromJson(jsonDecode(widget.section!.data!))!;
print(weatherDTO);*/
weatherDTO = widget.section;
if(weatherDTO.result != null) {
Map<String, dynamic> weatherResultInJson = jsonDecode(weatherDTO.result!);
weatherData = WeatherData.fromJson(weatherResultInJson);
}
super.initState();
}
String formatTimestamp(int timestamp, AppContext appContext, bool isHourOnly, bool isDateOnly) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
// Determine the date format based on the application language
String dateFormat = appContext.getContext().language.toString().toUpperCase() == "EN" ?
'MM/dd/yyyy HH:mm'
: 'dd/MM/yyyy HH:mm';
if(isHourOnly) {
dateFormat = 'HH:mm';
}
if(isDateOnly) {
dateFormat = dateFormat.replaceAll('/yyyy HH:mm', '');
}
String formattedDate = DateFormat(dateFormat).format(dateTime);
return formattedDate;
}
String getTranslatedDayOfWeek(int timestamp, AppContext appContext, bool isDate) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
String dayToPrint = "";
print("dateTime.weekday");
print(dateTime.weekday);
switch(dateTime.weekday) {
case 1:
dayToPrint = TranslationHelper.getFromLocale("monday", appContext.getContext());
break;
case 2:
dayToPrint = TranslationHelper.getFromLocale("tuesday", appContext.getContext());
break;
case 3:
dayToPrint = TranslationHelper.getFromLocale("wednesday", appContext.getContext());
break;
case 4:
dayToPrint = TranslationHelper.getFromLocale("thursday", appContext.getContext());
break;
case 5:
dayToPrint = TranslationHelper.getFromLocale("friday", appContext.getContext());
break;
case 6:
dayToPrint = TranslationHelper.getFromLocale("saturday", appContext.getContext());
break;
case 7:
dayToPrint = TranslationHelper.getFromLocale("sunday", appContext.getContext());
break;
}
return isDate ? "${dayToPrint} ${formatTimestamp(timestamp, appContext, false, true)}" : dayToPrint;
}
List<WeatherForecast> getNextFiveDaysForecast(List<WeatherForecast> allForecasts) {
List<WeatherForecast> nextFiveDaysForecast = [];
DateTime today = DateTime.now();
List<WeatherForecast> nextDay1All = allForecasts.where((af) => (DateTime.fromMillisecondsSinceEpoch(af.dt! * 1000)).day == (today.add(Duration(days: 1))).day).toList();
List<WeatherForecast> nextDay2All = allForecasts.where((af) => (DateTime.fromMillisecondsSinceEpoch(af.dt! * 1000)).day == (today.add(Duration(days: 2))).day).toList();
List<WeatherForecast> nextDay3All = allForecasts.where((af) => (DateTime.fromMillisecondsSinceEpoch(af.dt! * 1000)).day == (today.add(Duration(days: 3))).day).toList();
List<WeatherForecast> nextDay4All = allForecasts.where((af) => (DateTime.fromMillisecondsSinceEpoch(af.dt! * 1000)).day == (today.add(Duration(days: 4))).day).toList();
List<WeatherForecast> nextDay5All = allForecasts.where((af) => (DateTime.fromMillisecondsSinceEpoch(af.dt! * 1000)).day == (today.add(Duration(days: 5))).day).toList();
var nextDay1MiddayTest = nextDay1All.where((nd) => (DateTime.fromMillisecondsSinceEpoch(nd.dt! * 1000)).hour == 12).firstOrNull;
if(nextDay1All.isNotEmpty) {
WeatherForecast nextDay1AllSummary = nextDay1MiddayTest ?? nextDay1All.last;
nextFiveDaysForecast.add(nextDay1AllSummary);
}
var nextDay2MiddayTest = nextDay2All.where((nd) => (DateTime.fromMillisecondsSinceEpoch(nd.dt! * 1000)).hour == 12).firstOrNull;
if(nextDay2All.isNotEmpty) {
WeatherForecast nextDay2Midday = nextDay2MiddayTest ?? nextDay2All.last;
nextFiveDaysForecast.add(nextDay2Midday);
}
var nextDay3MiddayTest = nextDay3All.where((nd) => (DateTime.fromMillisecondsSinceEpoch(nd.dt! * 1000)).hour == 12).firstOrNull;
if(nextDay3All.isNotEmpty) {
WeatherForecast nextDay3Midday = nextDay3MiddayTest ?? nextDay3All.last;
nextFiveDaysForecast.add(nextDay3Midday);
}
var nextDay4MiddayTest = nextDay4All.where((nd) => (DateTime.fromMillisecondsSinceEpoch(nd.dt! * 1000)).hour == 12).firstOrNull;
if(nextDay4All.isNotEmpty) {
WeatherForecast nextDay4Midday = nextDay4MiddayTest ?? nextDay4All.last;
nextFiveDaysForecast.add(nextDay4Midday);
}
var nextDay5MiddayTest = nextDay5All.where((nd) => (DateTime.fromMillisecondsSinceEpoch(nd.dt! * 1000)).hour == 12).firstOrNull;
if(nextDay5All.isNotEmpty) {
WeatherForecast nextDay5Midday = nextDay5MiddayTest ?? nextDay5All.last;
nextFiveDaysForecast.add(nextDay5Midday);
}
return nextFiveDaysForecast;
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
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 Stack(
children: [
weatherData == null ? const Center(child: Text("Aucune donnée à afficher")) : Container( // TODO translate ?
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
stops: const [
0.2,
0.5,
0.9,
0.95
],
colors: [
Colors.blue[50]!,
Colors.blue[100]!,
Colors.blue[200]!,
Colors.blue[300]!
]
)
),
//color: Colors.yellow,
//height: 300,
//width: 300,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Center(child: Text(weatherDTO.city!, style: const TextStyle(fontSize: kSectionTitleDetailSize, fontWeight: FontWeight.w500, color: Colors.black54, fontFamily: "Roboto"))),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("${weatherData!.list!.first.main!.temp!.round().toString()}°", style: const TextStyle(fontSize: 55.0, fontWeight: FontWeight.w400, color: Colors.black54, fontFamily: "Roboto")),
),
Container(
//color: Colors.green,
height: size.height * 0.2,
width: size.width * 0.45,
constraints: BoxConstraints(minWidth: 80),
child: Center(
child: CachedNetworkImage(imageUrl: "https://openweathermap.org/img/wn/${weatherData!.list!.first.weather!.first.icon!}@4x.png")
)
),
Container(
// color: Colors.green,
width: size.width * 0.2,
//color: Colors.red,
constraints: BoxConstraints(minWidth: 100),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Icon(Icons.water_drop_outlined, color: kSecondColor),
Text("${weatherData!.list!.first.pop!.round().toString()}%", style: const TextStyle(fontSize: 15.0, fontWeight: FontWeight.w400, color: Colors.black54, fontFamily: "Roboto")),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Icon(Icons.air, color: kSecondColor),
Text("${(weatherData!.list!.first.wind!.speed! * 3.6).toStringAsFixed(1)}km/h", style: const TextStyle(fontSize: 15.0, fontWeight: FontWeight.w400, color: Colors.black54, fontFamily: "Roboto")),
],
),
),
],
),
),
]),
Container(
height: size.height * 0.25,
width: size.width,
/*decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
//color: Colors.grey,
),*/
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 15, bottom: 10),
child: Align(alignment: Alignment.centerLeft, child: Text(TranslationHelper.getFromLocale("weather.hourly", appContext.getContext()), style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w400, color: Colors.black54, fontFamily: "Roboto"))),
),
Container(
height: size.height * 0.18,
width: size.width,
//color: Colors.lightGreen,
child: ListView(
scrollDirection: Axis.horizontal,
children: List.generate(
nbrNextHours,
(index) {
final weatherForecast = weatherData!.list!.sublist(1)[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: size.height * 0.15,
width: size.width * 0.25,
constraints: const BoxConstraints(minWidth: 125, maxWidth: 250),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
color: Colors.lightBlueAccent,
boxShadow: [
BoxShadow(
color: kBackgroundGrey.withValues(alpha: 0.6),
spreadRadius: 0.75,
blurRadius: 3.1,
offset: Offset(0, 2.5), // changes position of shadow
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(formatTimestamp(weatherForecast.dt!, appContext, true, false), style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.w400, color: Colors.white, fontFamily: "Roboto")),
Center(child: CachedNetworkImage(imageUrl: "https://openweathermap.org/img/wn/${weatherForecast.weather!.first.icon!}.png")),
Text('${weatherForecast.main!.temp!.round().toString()}°', style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.w600, color: Colors.white, fontFamily: "Roboto")),
],
),
),
);
},
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(0.0),
child: Container(
height: size.height * 0.3,
width: size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
//color: Colors.amber,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(alignment: Alignment.centerLeft, child: Text(TranslationHelper.getFromLocale("weather.nextdays", appContext.getContext()), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w400, color: Colors.black54, fontFamily: "Roboto"))),
),
Container(
height: size.height * 0.23,
width: size.width,
//color: Colors.lightGreen,
child: ListView(
scrollDirection: Axis.horizontal,
children:List.generate(
getNextFiveDaysForecast(weatherData!.list!).length, // nbrNextHours
(index) {
final weatherForecastNextDay = getNextFiveDaysForecast(weatherData!.list!)[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: size.height * 0.22,
width: size.width * 0.125,
constraints: const BoxConstraints(minWidth: 150, maxWidth: 250),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
color: Colors.lightBlue,
boxShadow: [
BoxShadow(
color: kBackgroundGrey.withValues(alpha: 0.5),
spreadRadius: 0.75,
blurRadius: 3.1,
offset: const Offset(0, 2.5), // changes position of shadow
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(child: CachedNetworkImage(imageUrl: "https://openweathermap.org/img/wn/${weatherForecastNextDay.weather!.first.icon!}@2x.png")),
Text('${weatherForecastNextDay.main!.temp!.round().toString()}°', style: const TextStyle(fontSize: 25.0, fontWeight: FontWeight.w600, color: Colors.white, fontFamily: "Roboto")),
Text(getTranslatedDayOfWeek(weatherForecastNextDay.dt!, appContext, true), style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.w400, color: Colors.white, fontFamily: "Roboto")),
],
),
),
);
},
),
),
),
],
),
),
)
],
),
),
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)
),
)
),
),
],
);
}
}
//_webView

View File

@ -0,0 +1,242 @@
import 'dart:convert';
import 'dart:ui' as ui;
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/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
class WebPage extends StatefulWidget {
final WebDTO section;
WebPage({required this.section});
@override
_WebPage createState() => _WebPage();
}
class _WebPage extends State<WebPage> {
//final IFrameElement _iframeElement = IFrameElement();
//WebView _webView;
WebDTO webDTO = WebDTO();
WebViewController? controller;
PlatformWebViewController? controllerWeb;
bool isLoading = true;
@override
void initState() {
//print(widget.section!.data);
webDTO = widget.section;
//webDTO = WebDTO.fromJson(jsonDecode(widget.section!.data!))!;
print(webDTO);
if(kIsWeb) {
/*_iframeElement.src = webDTO.source_!;
_iframeElement.style.border = 'none';
//ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
webDTO.source_!, //use source as registered key to ensure uniqueness
(int viewId) => _iframeElement,
);*/
} else {
if(webDTO.source_ != null && webDTO.source_!.length > 0) {
try {
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
},
onPageStarted: (String url) {},
onPageFinished: (String url) {
setState(() {
isLoading = false;
});
},
onWebResourceError: (WebResourceError error) {},
onNavigationRequest: (NavigationRequest request) {
Uri sourceUri = Uri.parse(webDTO.source_!);
Uri requestUri = Uri.parse(request.url);
if (requestUri.host != sourceUri.host) { // handle navigation to site
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(webDTO.source_!));
} catch (e) {
print("Invalid source ${webDTO.source_}");
}
}
}
super.initState();
/*_webView = WebView(
initialUrl: webDTO.source_, //"https://my.matterport.com/show/?m=k8bvdezfHbT"
javascriptMode: JavascriptMode.unrestricted,
navigationDelegate: (NavigationRequest request) {
print(request.url);
print(webDTO.source_);
if (request.url != webDTO.source_) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
print('allowing navigation to $request');
return NavigationDecision.navigate;
}
);*/
}
@override
void dispose() {
//_webView = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final appContext = Provider.of<AppContext>(context);
var title = TranslationHelper.get(widget.section.title, appContext.getContext());
String cleanedTitle = title.replaceAll('\n', ' ').replaceAll('<br>', ' ');
return webDTO.source_ != null && webDTO.source_!.isNotEmpty ?
kIsWeb ?
HtmlElementView(
key: UniqueKey(),
viewType: webDTO.source_!,
) :
Stack(
children: [
Container(
height: size.height * 0.28,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1), // changes position of shadow
),
],
gradient: const LinearGradient(
begin: Alignment.centerRight,
end: Alignment.centerLeft,
colors: [
/*Color(0xFFDD79C2),
Color(0xFFB65FBE),
Color(0xFF9146BA),
Color(0xFF7633B8),
Color(0xFF6528B6),
Color(0xFF6025B6)*/
kMainColor0, //Color(0xFFf6b3c4)
kMainColor1,
kMainColor2,
],
),
image: widget.section.imageSource != null ? DecorationImage(
fit: BoxFit.cover,
opacity: 0.65,
image: NetworkImage(
widget.section.imageSource!,
),
): null,
),
),
Column(
children: <Widget>[
SizedBox(
height: size.height * 0.11,
width: size.width,
child: Stack(
fit: StackFit.expand,
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(top: 22.0),
child: SizedBox(
width: size.width *0.7,
child: HtmlWidget(
cleanedTitle,
textStyle: const TextStyle(color: Colors.white, fontFamily: 'Roboto', fontSize: 20),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
},
),
),
),
),
Positioned(
top: 35,
left: 10,
child: SizedBox(
width: 50,
height: 50,
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
decoration: const BoxDecoration(
color: kMainColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
),
)
),
),
],
),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: kMainGrey,
spreadRadius: 0.5,
blurRadius: 2,
offset: Offset(0, 1), // changes position of shadow
),
],
color: kBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
child: isLoading ? Center(child: SizedBox(height: size.height * 0.15, child: const LoadingCommon())) : WebViewWidget(controller: controller!))
),
),
],
),
],
) :
const Center(child: Text("La page internet ne peut pas être affichée, l'url est incorrecte ou vide", style: TextStyle(fontSize: kNoneInfoOrIncorrect)));
}
} //_webView

View File

@ -1,190 +0,0 @@
import 'package:diacritic/diacritic.dart';
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Components/loading_common.dart';
import 'package:mymuseum_visitapp/Components/SearchBox.dart';
import 'package:mymuseum_visitapp/Components/SearchNumberBox.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Article/article_page.dart';
import 'package:mymuseum_visitapp/Screens/Quizz/quizz_page.dart';
import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart';
import 'section_card.dart';
class Body extends StatefulWidget {
const Body({Key? key, required this.configurationId}) : super(key: key);
final String? configurationId;
@override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
List<SectionDTO> sections = [];
List<SectionDTO> sectionsToDisplay = [];
String? searchValue;
int? searchNumberValue;
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
Size size = MediaQuery.of(context).size;
return SafeArea(
bottom: false,
child: Column(
children: <Widget>[
Row(
children: [
SearchBox(onChanged: (value) {
setState(() {
if(value != null && value != "") {
searchValue = value;
} else {
searchValue = null;
}
});
}),
Expanded(
child: SearchNumberBox(onChanged: (value) {
setState(() {
if(value != null && value != "") {
searchNumberValue = int.parse(value);
} else {
searchNumberValue = null;
FocusScope.of(context).unfocus();
}
});
}),
),
],
),
//const SizedBox(height: kDefaultPadding / 2),
Expanded(
child: Stack(
children: <Widget>[
// Our background
Container(
margin: const EdgeInsets.only(top: 0),
decoration: const BoxDecoration(
color: kBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(40),
topRight: Radius.circular(40),
),
),
),
FutureBuilder(
future: getSections(appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
/*print("SECTIONTODISPA");
print(sectionsToDisplay);*/
return Padding(
padding: const EdgeInsets.only(bottom: 0),
child: RefreshIndicator(
onRefresh: () async {
if(!(appContext.getContext() as VisitAppContext).configuration!.isOffline!) {
// Force refresh if online
setState(() {});
} },
child: ListView.builder(
itemCount: sectionsToDisplay.length,
itemBuilder: (context, index) => SectionCard(
itemCount: sectionsToDisplay.length,
itemIndex: index,
sectionDTO: sectionsToDisplay[index],
press: () {
switch(sectionsToDisplay[index].type) {
case SectionType.Article:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ArticlePage(
visitAppContextIn: appContext.getContext(),
articleId: sectionsToDisplay[index].id!,
),
),
);
break;
case SectionType.Quizz:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuizzPage(
visitAppContextIn: appContext.getContext(),
sectionId: sectionsToDisplay[index].id!,
),
),
);
break;
default:
// TODO HANDLE, SHOW NOT SUPPORTED
break;
}
},
),
),
),
);
} else if (snapshot.connectionState == ConnectionState.none) {
return Text(TranslationHelper.getFromLocale("noData", appContext.getContext()));
} else {
return Center(
child: Container(
height: size.height * 0.15,
child: LoadingCommon()
)
);
}
}
)
],
),
),
],
),
);
}
getSections(AppContext appContext) async {
VisitAppContext visitAppContext = (appContext.getContext() as VisitAppContext);
if(visitAppContext.configuration!.isOffline!)
{
// OFFLINE
sections = List<SectionDTO>.from(await DatabaseHelper.instance.getData(DatabaseTableType.sections));
}
else
{
// ONLINE
List<SectionDTO>? sectionsDownloaded = await ApiService.getAllSections(visitAppContext.clientAPI, visitAppContext.configuration!.id!);
//print(sectionsDownloaded);
if(sectionsDownloaded!.isNotEmpty) {
sections = sectionsDownloaded.where((s) => s.type == SectionType.Article || s.type == SectionType.Quizz).toList(); // TODO Support more than Article and Quizz section type
//print(sections);
}
}
sections = sections.where((s) => s.configurationId == widget.configurationId).toList();
sections.sort((a,b) => a.order!.compareTo(b.order!));
sectionsToDisplay = sections;
visitAppContext.currentSections = sectionsToDisplay;
if(searchValue != '' && searchValue != null) {
sectionsToDisplay = sections.where((s) => removeDiacritics(TranslationHelper.get(s.title, appContext.getContext()).toLowerCase()).contains(removeDiacritics(searchValue.toString().toLowerCase()))).toList();
} else {
if(searchNumberValue != null) {
sectionsToDisplay = sections.where((s) => s.order!+1 == searchNumberValue).toList();
} else {
sectionsToDisplay = sections;
}
}
}
}

View File

@ -0,0 +1,367 @@
import 'dart:convert';
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';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
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/Agenda/agenda_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.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/Menu/menu_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_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Video/video_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Weather/weather_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: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);
final Object? rawSection;
final String sectionId;
final ConfigurationDTO configuration;
final VisitAppContext visitAppContextIn;
@override
State<SectionPage> createState() => _SectionPageState();
}
class _SectionPageState extends State<SectionPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
SectionDTO? sectionDTO;
late VisitAppContext visitAppContext;
late dynamic rawSectionData;
List<ResourceModel?> resourcesModel = <ResourceModel?>[];
late final MapContext mapContext = MapContext(null);
List<Map<String, dynamic>>? icons;
String? mainAudioId;
@override
void initState() {
widget.visitAppContextIn.isContentCurrentlyShown = true;
super.initState();
}
@override
void dispose() {
visitAppContext.isContentCurrentlyShown = false;
super.dispose();
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
Size size = MediaQuery.of(context).size;
visitAppContext = appContext.getContext();
var test = SectionDTO.fromJson(jsonDecode(jsonEncode(widget.rawSection)));
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomInset: false,
body: MediaQuery.removeViewInsets(
context: context,
removeBottom: true,
child: OrientationBuilder(
builder: (context, orientation) {
return FutureBuilder(
future: getSectionDetail(appContext, visitAppContext.clientAPI, widget.sectionId),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
var sectionResult = snapshot.data;
if(sectionDTO != null && sectionResult != null) {
switch(sectionDTO!.type) {
case SectionType.Agenda:
AgendaDTO agendaDTO = AgendaDTO.fromJson(sectionResult)!;
return AgendaPage(section: agendaDTO);
case SectionType.Article:
ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!;
return ArticlePage(
visitAppContextIn: widget.visitAppContextIn,
articleDTO: articleDTO,
resourcesModel: resourcesModel,
mainAudioId: mainAudioId,
);
case SectionType.Map:
MapDTO mapDTO = MapDTO.fromJson(sectionResult)!;
return ChangeNotifierProvider<MapContext>.value(
value: mapContext,
child: MapPage(section: mapDTO, icons: icons ?? []),
);
case SectionType.Menu:
MenuDTO menuDTO = MenuDTO.fromJson(sectionResult)!;
return MenuPage(section: menuDTO, isImageBackground: widget.configuration.isSectionImageBackground!);
case SectionType.Pdf:
PdfDTO pdfDTO = PdfDTO.fromJson(sectionResult)!;
return PDFPage(section: pdfDTO);
case SectionType.Puzzle:
PuzzleDTO puzzleDTO = PuzzleDTO.fromJson(sectionResult)!;
return PuzzlePage(section: puzzleDTO);
case SectionType.Quiz:
QuizDTO quizDTO = QuizDTO.fromJson(sectionResult)!;
return QuizPage(
visitAppContextIn: widget.visitAppContextIn,
quizDTO: quizDTO,
resourcesModel: resourcesModel,
);
case SectionType.Slider:
SliderDTO sliderDTO = SliderDTO.fromJson(sectionResult)!;
return SliderPage(section: sliderDTO);
case SectionType.Video:
VideoDTO videoDTO = VideoDTO.fromJson(sectionResult)!;
return VideoPage(section: videoDTO);
case SectionType.Weather:
WeatherDTO weatherDTO = WeatherDTO.fromJson(sectionResult)!;
return WeatherPage(section: weatherDTO);
case SectionType.Web:
WebDTO webDTO = WebDTO.fromJson(sectionResult)!;
return WebPage(section: webDTO);
default:
return const Center(child: Text("Unsupported type"));
}
} else {
return const LoadingCommon();
}
}
);
}
),
)
);
}
Future<dynamic> getSectionDetail(AppContext appContext, Client client, String sectionId) async {
try {
bool isConfigOffline = widget.configuration.isOffline!;
if(widget.rawSection == null) {
if(isConfigOffline)
{
// OFFLINE
List<Map<String, dynamic>> sectionTest = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.sections, sectionId);
if(sectionTest.isNotEmpty) {
sectionDTO = DatabaseHelper.instance.getSectionFromDB(sectionTest.first);
try {
SectionRead sectionRead = SectionRead(id: sectionDTO!.id!, readTime: DateTime.now().millisecondsSinceEpoch);
await DatabaseHelper.instance.insert(DatabaseTableType.articleRead, sectionRead.toMap());
visitAppContext.readSections.add(sectionRead);
appContext.setContext(visitAppContext);
} catch (e) {
print("DATABASE ERROR SECTIONREAD");
print(e);
}
} else {
print("EMPTY SECTION");
}
} else
{
// ONLINE
rawSectionData = await client.sectionApi!.sectionGetDetail(sectionId);
SectionDTO sectionOnline = jsonDecode(jsonEncode(rawSectionData)).map((json) => SectionDTO.fromJson(json)).whereType<SectionDTO>().toList();
sectionDTO = sectionOnline;
}
/*setState(() {
//print(sectionDTO!.title);
});*/
} else {
rawSectionData = widget.rawSection;
sectionDTO = SectionDTO.fromJson(jsonDecode(jsonEncode(rawSectionData)));
}
switch(sectionDTO!.type)
{
case SectionType.Quiz:
QuizDTO? quizDTO = QuizDTO.fromJson(rawSectionData);
if(quizDTO != null) {
quizDTO.questions!.sort((a, b) => a.order!.compareTo(b.order!));
if(quizDTO.questions != null && quizDTO.questions!.isNotEmpty) {
quizDTO.questions!.sort((a, b) => a.order!.compareTo(b.order!));
for (var question in quizDTO.questions!) {
if(isConfigOffline)
{
// OFFLINE
if(question.imageBackgroundResourceId != null) {
List<Map<String, dynamic>> ressourceQuizz = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, question.imageBackgroundResourceId!);
if(ressourceQuizz.isNotEmpty) {
resourcesModel.add(DatabaseHelper.instance.getResourceFromDB(ressourceQuizz.first));
} else {
print("EMPTY resourcesModel - second");
}
}
}
else
{
// ONLINE
if(question.imageBackgroundResourceId != null) {
resourcesModel.add(ResourceModel(id: question.imageBackgroundResourceId, source: question.imageBackgroundResourceUrl, type: ResourceType.Image));
}
}
}
}
}
break;
case SectionType.Article:
ArticleDTO articleDTO = ArticleDTO.fromJson(rawSectionData)!;
var audioToDownload = articleDTO.audioIds!.firstWhere((a) => a.language == visitAppContext.language!).value;
var othersToDownload = articleDTO.contents!.map((c) => c.resourceId);
mainAudioId = audioToDownload;
var resourcesToDownload = [];
resourcesToDownload.add(audioToDownload);
resourcesToDownload.addAll(othersToDownload);
for (var resourceToDownload in resourcesToDownload) {
if(isConfigOffline)
{
// OFFLINE
List<Map<String, dynamic>> ressourceArticle = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, resourceToDownload);
if(ressourceArticle.isNotEmpty) {
resourcesModel.add(DatabaseHelper.instance.getResourceFromDB(ressourceArticle.first));
} else {
print("EMPTY resourcesModel - second");
}
}
else
{
// ONLINE
ResourceDTO? resourceDTO = await client.resourceApi!.resourceGetDetail(resourceToDownload);
if(resourceDTO != null && resourceDTO.url != null) {
// ONLINE
//ResourceModel? resourceAudioOnline = await ApiService.downloadAudio(client, resourceDTO.url!, resourceDTO.id!);
ResourceModel resourceAudioOnline = ResourceModel();
resourceAudioOnline.id = resourceDTO.id;
resourceAudioOnline.source = resourceDTO.url;
resourceAudioOnline.type = resourceDTO.type;
resourcesModel.add(resourceAudioOnline);
/*Uint8List base64String = base64Decode(resourceAudioOnline.path!); // GET FROM FILE
audiobytes = base64String;*/
} else {
print("EMPTY resourcesModel online - audio");
}
}
}
break;
case SectionType.Map:
MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!;
icons = await getByteIcons(visitAppContext, mapDTO);
break;
default:
break;
}
return rawSectionData;
} catch (e) {
print(e);
print("IN CATCH");
return null;
}
}
}
Future<Uint8List> 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<File?> _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<FileSystemEntity> 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<List<Map<String, dynamic>>> 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<Map<String, dynamic>> 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;
}

View File

@ -1,7 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/networkCheck.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
@ -22,7 +22,7 @@ class ApiService {
if(configurations.isNotEmpty) {
for(var configuration in configurations) {
if(configuration.imageId != null) {
await downloadAndPushLocalImage(client, ContentDTO(resourceUrl: configuration.imageSource, resourceId: configuration.imageId));
await downloadAndPushLocalImage(client, ContentDTO(resourceId: configuration.imageId, resource: ResourceDTO(url: configuration.imageSource, id: configuration.imageId)));
}
}
}
@ -40,12 +40,13 @@ class ApiService {
}
}
static Future<List<SectionDTO>?> getAllSections(Client client, String configurationId) async {
static Future<List<dynamic>?> getAllSections(Client client, String configurationId) async {
try {
bool isOnline = await hasNetwork();
if(isOnline) {
List<SectionDTO>? sections = await client.sectionApi!.sectionGetFromConfiguration(configurationId);
return sections;
final rawList = await client.sectionApi!.sectionGetFromConfigurationDetail(configurationId);
var sections = rawList.map((json) => SectionDTO.fromJson(json)).toList();
return rawList ?? [];
} else {
return []; // TODO return local list..
}
@ -67,7 +68,7 @@ class ApiService {
}
} catch (e) {
print(e);
print("getAllSections IN CATCH");
print("getAllBeacons IN CATCH");
return [];
}
}
@ -84,7 +85,7 @@ class ApiService {
if(isOnline) {
ResourceModel? resourceModel = await downloadImage(client, contentDTO);
if(resourceModel != null) {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
//await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
}
return true;
} else {
@ -98,13 +99,13 @@ class ApiService {
}
}
static Future<ResourceModel?> downloadImage(Client client, ContentDTO contentDTO) async {
var source = contentDTO.resourceUrl;
static Future<ResourceModel?> downloadImage(Client client, ContentDTO contentDTO) async { // TODO CHECK wtf mymuseum
var source = contentDTO.resource!.url;
//print("SOURCE getAndDownloadImage");
if(contentDTO.resourceUrl != null) {
if(contentDTO.resourceUrl!.contains("localhost:5000")){
if(contentDTO.resource!.url != null) {
if(contentDTO.resource!.url!.contains("localhost:5000")){
print("Contains localhost:5000");
source = contentDTO.resourceUrl!.replaceAll("http://localhost:5000", client.apiApi!.basePath);
source = contentDTO.resource!.url!.replaceAll("http://localhost:5000", client.apiApi!.basePath);
}
} else {
source = "https://api.mymuseum.be/api/Resource/"+contentDTO.resourceId!; // TODO UPDATE ROUTE
@ -118,7 +119,7 @@ class ApiService {
await for(dynamic d in response) { _downloadData.addAll(d); }
//print("AFTER");
final base64Str = base64.encode(_downloadData);
ResourceModel resourceModel = ResourceModel(id: contentDTO.resourceId, source: contentDTO.resourceUrl, path: base64Str, type: ResourceType.Image);
ResourceModel resourceModel = ResourceModel(id: contentDTO.resourceId, source: contentDTO.resource!.url, path: base64Str, type: ResourceType.Image);
return resourceModel;
}
@ -162,7 +163,7 @@ class ApiService {
}
static Future<File?> getResource(AppContext appContext, ConfigurationDTO configurationDTO, String imageId) async {
if((appContext.getContext() as VisitAppContext).configuration == null || (appContext.getContext() as VisitAppContext).configuration!.isOffline!)
if(configurationDTO.isOffline!)
{
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
@ -192,8 +193,7 @@ class ApiService {
try {
bool isOnline = await hasNetwork();
if(isOnline) {
ExportConfigurationDTO? exportConfiguration = await client.configurationApi!.configurationExport(configurationId, language);
ExportConfigurationDTO? exportConfiguration = await client.configurationApi!.configurationExport(configurationId, language: language);
return exportConfiguration;
} else {
return null; // TODO return local list..

View File

@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/modelsHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
@ -61,7 +61,7 @@ class _DownloadConfigurationWidgetState extends State<DownloadConfigurationWidge
ExportConfigurationDTO? exportConfigurationDTO;
try{
// Retrieve all url from resource to download (get all resource from configuration en somme)
exportConfigurationDTO = await visitAppContext.clientAPI.configurationApi!.configurationExport(widget.configuration.id!, isAllLanguages ? null : visitAppContext.language); // tabletAppContext.configuration!.id! // 65c5f0ee4c030e63ce16bff5 TODO Remove
exportConfigurationDTO = await visitAppContext.clientAPI.configurationApi!.configurationExport(widget.configuration.id!, language: isAllLanguages ? null : visitAppContext.language); // tabletAppContext.configuration!.id! // 65c5f0ee4c030e63ce16bff5 TODO Remove
} catch(e) {
print("Erreur lors du téléchargement de la configuration et de ses ressources !");
print(e);
@ -148,7 +148,7 @@ class _DownloadConfigurationWidgetState extends State<DownloadConfigurationWidge
if(sections!.isNotEmpty) {
List<SectionDTO> sectionsInDB = await DatabaseHelper.instance.queryWithConfigurationId(DatabaseTableType.sections, widget.configuration.id!);
List<SectionDTO> sectionsToKeep = sections.where((s) => s.type == SectionType.Article || s.type == SectionType.Quizz).toList(); // TODO handle other type of section (for now, Article and Quizz)
List<SectionDTO> sectionsToKeep = sections.where((s) => s.type == SectionType.Article || s.type == SectionType.Quiz).toList(); // TODO handle other type of section (for now, Article and Quizz)
sectionsToKeep.sort((a,b) => a.order!.compareTo(b.order!));
int newOrder = 0;
@ -176,8 +176,10 @@ class _DownloadConfigurationWidgetState extends State<DownloadConfigurationWidge
}
}
// TODO Endpoint back with all resources ? Just all get all resourceDTO from a configuration..
// Download all images..
ArticleDTO? articleDTO = ArticleDTO.fromJson(jsonDecode(section.data!));
/*ArticleDTO? articleDTO = ArticleDTO.fromJson(jsonDecode(section.data!)); // TODO section data
if(articleDTO != null) {
for(var image in articleDTO.contents!) {
@ -200,7 +202,7 @@ class _DownloadConfigurationWidgetState extends State<DownloadConfigurationWidge
//audiosNotWorking = await importAudio(visitAppContext, exportConfigurationDTO, audioId.value!, audiosNotWorking);
}
}
}
}*/
newOrder = newOrder + 1;
}

View File

@ -1,12 +1,12 @@
// Openapi Generator last run: : 2025-05-27T14:44:29.936214
import 'package:openapi_generator_annotations/openapi_generator_annotations.dart';
@Openapi(
additionalProperties:
AdditionalProperties(pubName: 'manager_api', pubAuthor: 'Fransolet Thomas', useEnumExtension: true),
inputSpecFile: 'lib/api/swagger.yaml',
AdditionalProperties(pubName: 'manager_api_new', pubAuthor: 'Fransolet Thomas', useEnumExtension: true),
inputSpec: InputSpec(path: 'lib/api/swagger.yaml'),
generatorName: Generator.dart,
alwaysRun: true,
outputDirectory: 'manager_api')
outputDirectory: 'manager_api_new')
class Example extends OpenapiGeneratorConfig {}
/*

View File

@ -1,7 +1,7 @@
//import 'package:managerapi/api.dart';
//import 'package:openapi_dart_common/openapi.dart';
import 'package:manager_api/api.dart';
import 'package:manager_api_new/api.dart';
class Client {
ApiClient? _apiClient;

View File

@ -5,9 +5,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_beacon/flutter_beacon.dart';
import 'package:get/get.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
import 'package:mymuseum_visitapp/Helpers/requirement_state_controller.dart';
import 'package:mymuseum_visitapp/Models/articleRead.dart';
import 'package:mymuseum_visitapp/Screens/Home/home.dart';
import 'package:mymuseum_visitapp/Screens/Home/home_3.0.dart';
import 'package:provider/provider.dart';
import 'Helpers/DatabaseHelper.dart';
import 'Models/visitContext.dart';
@ -25,6 +27,8 @@ void main() async {
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
MapboxOptions.setAccessToken("pk.eyJ1IjoidGZyYW5zb2xldCIsImEiOiJjbHRpcGNvZDYwYWhkMnFxdmF0ampveW10In0.7xHN0NGvUfQu5ThS3RGJRw"); // TODO put in json file or resource file
if(localContext != null) {
print("we've got an local db !");
print(localContext);
@ -32,7 +36,7 @@ void main() async {
List<SectionRead> articleReadTest = List<SectionRead>.from(await DatabaseHelper.instance.getData(DatabaseTableType.articleRead));
localContext.readSections = articleReadTest;
} else {
localContext = VisitAppContext(language: "FR", id: "UserId_Init", instanceId: "633ee379d9405f32f166f047", isAdmin: false, isAllLanguages: false); // 63514fd67ed8c735aaa4b8f2 MyInfoMate test instance -- 633ee379d9405f32f166f047 MyMuseum Fort Saint-Héribert - MyMuseum instance 63514fd67ed8c735aaa4b8f1 // MDLF instance 65ccc67265373befd15be511
localContext = VisitAppContext(language: "FR", id: "UserId_Init", instanceId: "63514fd67ed8c735aaa4b8f2", isAdmin: false, isAllLanguages: false); // 63514fd67ed8c735aaa4b8f2 MyInfoMate test instance -- 633ee379d9405f32f166f047 MyMuseum Fort Saint-Héribert - MyMuseum instance 63514fd67ed8c735aaa4b8f1 // MDLF instance 65ccc67265373befd15be511
DatabaseHelper.instance.insert(DatabaseTableType.main, localContext.toMap());
List<SectionRead> articleReadTest = List<SectionRead>.from(await DatabaseHelper.instance.getData(DatabaseTableType.articleRead));
@ -71,6 +75,12 @@ void main() async {
//);
//AudioPlayer.global.setGlobalAudioContext(audioContext);
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent, // important
statusBarColor: Colors.transparent,
));
runApp(myApp);
}
@ -93,7 +103,7 @@ class _MyAppState extends State<MyApp> {
create: (_) => AppContext(widget.visitAppContext),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Fort Saint Héribert', //'Musée de la fraise'
title: 'Carnaval de Marche', //'Musée de la fraise' // Autres // 'Fort Saint Héribert'
initialRoute: widget.initialRoute,
localizationsDelegates: const [
AppLocalizations.delegate,
@ -101,9 +111,9 @@ class _MyAppState extends State<MyApp> {
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('fr', ''),
supportedLocales: const[
Locale('en', ''),
Locale('fr', ''),
],
theme: ThemeData(
primarySwatch: Colors.blue,
@ -116,7 +126,7 @@ class _MyAppState extends State<MyApp> {
),
),
routes: {
'/home': (context) => const HomePage(),
'/home': (context) => const HomePage3(),
}
),
);

View File

@ -25,7 +25,17 @@ List<Translation> translations = [
"restart": "Recommencer",
"downloadInProgress": "Téléchargement en cours",
"downloadFinish": "Téléchargement terminé",
"upToDate": "Tout est à jour"
"upToDate": "Tout est à jour",
"weather.hourly": "Prochaines heures",
"weather.nextdays": "Prochains jours",
"agenda.all": "Tout",
"monday": "Lundi",
"tuesday": "Mardi",
"wednesday": "Mercredi",
"thursday": "Jeudi",
"friday": "Vendredi",
"saturday": "Samedi",
"sunday": "Dimanche"
}),
Translation(language: "EN", data: {
"visitTitle": "List of tours",
@ -51,7 +61,17 @@ List<Translation> translations = [
"restart": "Restart",
"downloadInProgress": "Download in progress",
"downloadFinish": "Download complete",
"upToDate": "Up to date"
"upToDate": "Up to date",
"weather.hourly": "Hourly",
"weather.nextdays": "Next days",
"agenda.all": "All",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday"
}),
Translation(language: "DE", data: {
"visitTitle": "Liste der Touren",
@ -77,7 +97,17 @@ List<Translation> translations = [
"restart": "Neu starten",
"downloadInProgress": "Download läuft",
"downloadFinish": "Download abgeschlossen",
"upToDate": "Alles ist auf dem neuesten Stand"
"upToDate": "Alles ist auf dem neuesten Stand",
"weather.hourly": "Nächste Stunden",
"weather.nextdays": "Nächsten Tage",
"agenda.all": "Alle",
"monday": "Montag",
"tuesday": "Dienstag",
"wednesday": "Mittwoch",
"thursday": "Donnerstag",
"friday": "Freitag",
"saturday": "Samstag",
"sunday": "Sonntag"
}),
Translation(language: "NL", data: {
"visitTitle": "Lijst met rondleidingen",
@ -103,7 +133,17 @@ List<Translation> translations = [
"restart": "Herstarten",
"downloadInProgress": "Download bezig",
"downloadFinish": "Download voltooid",
"upToDate": "Alles is up-to-date"
"upToDate": "Alles is up-to-date",
"weather.hourly": "Volgende uren",
"weather.nextdays": "Volgende dagen",
"agenda.all": "Alle",
"monday": "Maandag",
"tuesday": "Dinsdag",
"wednesday": "Woensdag",
"thursday": "Donderdag",
"friday": "Vrijdag",
"saturday": "Zaterdag",
"sunday": "Zondag"
}),
Translation(language: "IT", data: {
"visitTitle": "Elenco dei tour",
@ -129,7 +169,17 @@ List<Translation> translations = [
"restart": "Ricomincia",
"downloadInProgress": "Download in corso",
"downloadFinish": "Download completato",
"upToDate": "Tutto è aggiornato"
"upToDate": "Tutto è aggiornato",
"weather.hourly": "Le prossime ore",
"weather.nextdays": "Prossimi giorni",
"agenda.all": "Tutto",
"monday": "Lunedì",
"tuesday": "Martedì",
"wednesday": "Mercoledì",
"thursday": "Giovedì",
"friday": "Venerdì",
"saturday": "Sabato",
"sunday": "Domenica"
}),
Translation(language: "ES", data: {
"visitTitle": "Lista de recorridos",
@ -155,7 +205,17 @@ List<Translation> translations = [
"restart": "Reanudar",
"downloadInProgress": "Descarga en curso",
"downloadFinish": "Descarga completada",
"upToDate": "Todo está al día"
"upToDate": "Todo está al día",
"weather.hourly": "Próximas horas",
"weather.nextdays": "Proximos dias",
"agenda.all": "Todo",
"monday": "Lunes",
"tuesday": "Martes",
"wednesday": "Miércoles",
"thursday": "Jueves",
"friday": "Viernes",
"saturday": "Sábado",
"sunday": "Domingo"
}),
Translation(language: "PL", data: {
"visitTitle": "Lista wycieczek",
@ -181,7 +241,17 @@ List<Translation> translations = [
"restart": "Uruchom ponownie",
"downloadInProgress": "Pobieranie w toku",
"downloadFinish": "Pobieranie zakończone",
"upToDate": "Wszystko jest aktualne"
"upToDate": "Wszystko jest aktualne",
"weather.hourly": "Następne godziny",
"weather.nextdays": "Następne dni",
"agenda.all": "Wszystko",
"monday": "Poniedziałek",
"tuesday": "Wtorek",
"wednesday": "Środa",
"thursday": "Czwartek",
"friday": "Piątek",
"saturday": "Sobota",
"sunday": "Niedziela"
}),
Translation(language: "CN", data: {
"visitTitle": "旅游清单",
@ -207,7 +277,17 @@ List<Translation> translations = [
"restart": "重新开始",
"downloadInProgress": "下载中",
"downloadFinish": "下载完成",
"upToDate": "已是最新"
"upToDate": "已是最新",
"weather.hourly": "接下来的几个小时",
"weather.nextdays": "未来几天",
"agenda.all": "全部",
"monday": "星期一",
"tuesday": "星期二",
"wednesday": "星期三",
"thursday": "星期四",
"friday": "星期五",
"saturday": "星期六",
"sunday": "星期日"
}),
Translation(language: "UK", data: {
"visitTitle": "Список турів",
@ -233,7 +313,17 @@ List<Translation> translations = [
"restart": "Перезапустіть",
"downloadInProgress": "Завантаження триває",
"downloadFinish": "Завантаження завершено",
"upToDate": "Все актуально"
"upToDate": "Все актуально",
"weather.hourly": "Наступні години",
"weather.nextdays": "Наступні дні",
"agenda.all": "все",
"monday": "Понеділок",
"tuesday": "Вівторок",
"wednesday": "Середа",
"thursday": "Четвер",
"friday": "П'ятниця",
"saturday": "Субота",
"sunday": "Неділя"
}),
Translation(language: "AR", data: {
"visitTitle": "قائمة الجولات",
@ -259,6 +349,16 @@ List<Translation> translations = [
"restart": "إعادة تشغيل",
"downloadInProgress": "جارٍ التنزيل",
"downloadFinish": "اكتمل التنزيل",
"upToDate": "كل شيء محدث"
"upToDate": "كل شيء محدث",
"weather.hourly": "الساعات القادمة",
"weather.nextdays": "الايام القادمة",
"agenda.all": "الجميع",
"monday": "الإثنين",
"tuesday": "الثلاثاء",
"wednesday": "الأربعاء",
"thursday": "الخميس",
"friday": "الجمعة",
"saturday": "السبت",
"sunday": "الأحد"
}),
];

View File

@ -1 +0,0 @@
unset

View File

@ -1,192 +0,0 @@
# manager_api
API Manager Service
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: Version Alpha
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
Dart 2.12 or later
## Installation & Usage
### Github
If this Dart package is published to Github, add the following dependency to your pubspec.yaml
```
dependencies:
manager_api:
git: https://github.com/GIT_USER_ID/GIT_REPO_ID.git
```
### Local
To use the package in your local drive, add the following dependency to your pubspec.yaml
```
dependencies:
manager_api:
path: /path/to/manager_api
```
## Tests
TODO
## Getting Started
Please follow the [installation procedure](#installation--usage) and then run the following:
```dart
import 'package:manager_api/api.dart';
// TODO Configure OAuth2 access token for authorization: bearer
//defaultApiClient.getAuthentication<OAuth>('bearer').accessToken = 'YOUR_ACCESS_TOKEN';
final api_instance = AuthenticationApi();
final grantType = grantType_example; // String |
final username = username_example; // String |
final password = password_example; // String |
final clientId = clientId_example; // String |
final clientSecret = clientSecret_example; // String |
try {
final result = api_instance.authenticationAuthenticateWithForm(grantType, username, password, clientId, clientSecret);
print(result);
} catch (e) {
print('Exception when calling AuthenticationApi->authenticationAuthenticateWithForm: $e\n');
}
```
## Documentation for API Endpoints
All URIs are relative to *https://api.myinfomate.be*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*AuthenticationApi* | [**authenticationAuthenticateWithForm**](doc\/AuthenticationApi.md#authenticationauthenticatewithform) | **POST** /api/Authentication/Token |
*AuthenticationApi* | [**authenticationAuthenticateWithJson**](doc\/AuthenticationApi.md#authenticationauthenticatewithjson) | **POST** /api/Authentication/Authenticate |
*ConfigurationApi* | [**configurationCreate**](doc\/ConfigurationApi.md#configurationcreate) | **POST** /api/Configuration |
*ConfigurationApi* | [**configurationDelete**](doc\/ConfigurationApi.md#configurationdelete) | **DELETE** /api/Configuration/{id} |
*ConfigurationApi* | [**configurationExport**](doc\/ConfigurationApi.md#configurationexport) | **GET** /api/Configuration/{id}/export |
*ConfigurationApi* | [**configurationGet**](doc\/ConfigurationApi.md#configurationget) | **GET** /api/Configuration |
*ConfigurationApi* | [**configurationGetConfigurationsByPinCode**](doc\/ConfigurationApi.md#configurationgetconfigurationsbypincode) | **GET** /api/Configuration/byPin |
*ConfigurationApi* | [**configurationGetDetail**](doc\/ConfigurationApi.md#configurationgetdetail) | **GET** /api/Configuration/{id} |
*ConfigurationApi* | [**configurationImport**](doc\/ConfigurationApi.md#configurationimport) | **POST** /api/Configuration/import |
*ConfigurationApi* | [**configurationUpdate**](doc\/ConfigurationApi.md#configurationupdate) | **PUT** /api/Configuration |
*DeviceApi* | [**deviceCreate**](doc\/DeviceApi.md#devicecreate) | **POST** /api/Device |
*DeviceApi* | [**deviceDelete**](doc\/DeviceApi.md#devicedelete) | **DELETE** /api/Device/{id} |
*DeviceApi* | [**deviceGet**](doc\/DeviceApi.md#deviceget) | **GET** /api/Device |
*DeviceApi* | [**deviceGetDetail**](doc\/DeviceApi.md#devicegetdetail) | **GET** /api/Device/{id}/detail |
*DeviceApi* | [**deviceUpdate**](doc\/DeviceApi.md#deviceupdate) | **PUT** /api/Device |
*DeviceApi* | [**deviceUpdateMainInfos**](doc\/DeviceApi.md#deviceupdatemaininfos) | **PUT** /api/Device/mainInfos |
*InstanceApi* | [**instanceCreateInstance**](doc\/InstanceApi.md#instancecreateinstance) | **POST** /api/Instance |
*InstanceApi* | [**instanceDeleteInstance**](doc\/InstanceApi.md#instancedeleteinstance) | **DELETE** /api/Instance/{id} |
*InstanceApi* | [**instanceGet**](doc\/InstanceApi.md#instanceget) | **GET** /api/Instance |
*InstanceApi* | [**instanceGetDetail**](doc\/InstanceApi.md#instancegetdetail) | **GET** /api/Instance/{id} |
*InstanceApi* | [**instanceGetInstanceByPinCode**](doc\/InstanceApi.md#instancegetinstancebypincode) | **GET** /api/Instance/byPin |
*InstanceApi* | [**instanceUpdateinstance**](doc\/InstanceApi.md#instanceupdateinstance) | **PUT** /api/Instance |
*ResourceApi* | [**resourceCreate**](doc\/ResourceApi.md#resourcecreate) | **POST** /api/Resource |
*ResourceApi* | [**resourceDelete**](doc\/ResourceApi.md#resourcedelete) | **DELETE** /api/Resource/{id} |
*ResourceApi* | [**resourceGet**](doc\/ResourceApi.md#resourceget) | **GET** /api/Resource |
*ResourceApi* | [**resourceGetDetail**](doc\/ResourceApi.md#resourcegetdetail) | **GET** /api/Resource/{id}/detail |
*ResourceApi* | [**resourceShow**](doc\/ResourceApi.md#resourceshow) | **GET** /api/Resource/{id} |
*ResourceApi* | [**resourceUpdate**](doc\/ResourceApi.md#resourceupdate) | **PUT** /api/Resource |
*ResourceApi* | [**resourceUpload**](doc\/ResourceApi.md#resourceupload) | **POST** /api/Resource/upload |
*SectionApi* | [**sectionCreate**](doc\/SectionApi.md#sectioncreate) | **POST** /api/Section |
*SectionApi* | [**sectionDelete**](doc\/SectionApi.md#sectiondelete) | **DELETE** /api/Section/{id} |
*SectionApi* | [**sectionDeleteAllForConfiguration**](doc\/SectionApi.md#sectiondeleteallforconfiguration) | **DELETE** /api/Section/configuration/{id} |
*SectionApi* | [**sectionGet**](doc\/SectionApi.md#sectionget) | **GET** /api/Section |
*SectionApi* | [**sectionGetAgendaDTO**](doc\/SectionApi.md#sectiongetagendadto) | **GET** /api/Section/AgendaDTO |
*SectionApi* | [**sectionGetAllBeaconsForInstance**](doc\/SectionApi.md#sectiongetallbeaconsforinstance) | **GET** /api/Section/beacons/{instanceId} |
*SectionApi* | [**sectionGetAllSectionSubSections**](doc\/SectionApi.md#sectiongetallsectionsubsections) | **GET** /api/Section/{id}/subsections |
*SectionApi* | [**sectionGetArticleDTO**](doc\/SectionApi.md#sectiongetarticledto) | **GET** /api/Section/ArticleDTO |
*SectionApi* | [**sectionGetDetail**](doc\/SectionApi.md#sectiongetdetail) | **GET** /api/Section/{id} |
*SectionApi* | [**sectionGetFromConfiguration**](doc\/SectionApi.md#sectiongetfromconfiguration) | **GET** /api/Section/configuration/{id} |
*SectionApi* | [**sectionGetMapDTO**](doc\/SectionApi.md#sectiongetmapdto) | **GET** /api/Section/MapDTO |
*SectionApi* | [**sectionGetMenuDTO**](doc\/SectionApi.md#sectiongetmenudto) | **GET** /api/Section/MenuDTO |
*SectionApi* | [**sectionGetPdfDTO**](doc\/SectionApi.md#sectiongetpdfdto) | **GET** /api/Section/PdfDTO |
*SectionApi* | [**sectionGetPuzzleDTO**](doc\/SectionApi.md#sectiongetpuzzledto) | **GET** /api/Section/PuzzleDTO |
*SectionApi* | [**sectionGetQuizzDTO**](doc\/SectionApi.md#sectiongetquizzdto) | **GET** /api/Section/QuizzDTO |
*SectionApi* | [**sectionGetSliderDTO**](doc\/SectionApi.md#sectiongetsliderdto) | **GET** /api/Section/SliderDTO |
*SectionApi* | [**sectionGetVideoDTO**](doc\/SectionApi.md#sectiongetvideodto) | **GET** /api/Section/VideoDTO |
*SectionApi* | [**sectionGetWeatherDTO**](doc\/SectionApi.md#sectiongetweatherdto) | **GET** /api/Section/WeatherDTO |
*SectionApi* | [**sectionGetWebDTO**](doc\/SectionApi.md#sectiongetwebdto) | **GET** /api/Section/WebDTO |
*SectionApi* | [**sectionPlayerMessageDTO**](doc\/SectionApi.md#sectionplayermessagedto) | **GET** /api/Section/PlayerMessageDTO |
*SectionApi* | [**sectionUpdate**](doc\/SectionApi.md#sectionupdate) | **PUT** /api/Section |
*SectionApi* | [**sectionUpdateOrder**](doc\/SectionApi.md#sectionupdateorder) | **PUT** /api/Section/order |
*UserApi* | [**userCreateUser**](doc\/UserApi.md#usercreateuser) | **POST** /api/User |
*UserApi* | [**userDeleteUser**](doc\/UserApi.md#userdeleteuser) | **DELETE** /api/User/{id} |
*UserApi* | [**userGet**](doc\/UserApi.md#userget) | **GET** /api/User |
*UserApi* | [**userGetDetail**](doc\/UserApi.md#usergetdetail) | **GET** /api/User/{id} |
*UserApi* | [**userUpdateUser**](doc\/UserApi.md#userupdateuser) | **PUT** /api/User |
## Documentation For Models
- [AgendaDTO](doc\/AgendaDTO.md)
- [ArticleDTO](doc\/ArticleDTO.md)
- [CategorieDTO](doc\/CategorieDTO.md)
- [ConfigurationDTO](doc\/ConfigurationDTO.md)
- [ContentDTO](doc\/ContentDTO.md)
- [ContentGeoPoint](doc\/ContentGeoPoint.md)
- [DeviceDTO](doc\/DeviceDTO.md)
- [DeviceDetailDTO](doc\/DeviceDetailDTO.md)
- [DeviceDetailDTOAllOf](doc\/DeviceDetailDTOAllOf.md)
- [ExportConfigurationDTO](doc\/ExportConfigurationDTO.md)
- [ExportConfigurationDTOAllOf](doc\/ExportConfigurationDTOAllOf.md)
- [GeoPointDTO](doc\/GeoPointDTO.md)
- [GeoPointDTOCategorie](doc\/GeoPointDTOCategorie.md)
- [Instance](doc\/Instance.md)
- [InstanceDTO](doc\/InstanceDTO.md)
- [LevelDTO](doc\/LevelDTO.md)
- [LoginDTO](doc\/LoginDTO.md)
- [MapDTO](doc\/MapDTO.md)
- [MapDTOMapProvider](doc\/MapDTOMapProvider.md)
- [MapDTOMapType](doc\/MapDTOMapType.md)
- [MapDTOMapTypeMapbox](doc\/MapDTOMapTypeMapbox.md)
- [MapProvider](doc\/MapProvider.md)
- [MapTypeApp](doc\/MapTypeApp.md)
- [MapTypeMapBox](doc\/MapTypeMapBox.md)
- [MenuDTO](doc\/MenuDTO.md)
- [PDFFileDTO](doc\/PDFFileDTO.md)
- [PdfDTO](doc\/PdfDTO.md)
- [PlayerMessageDTO](doc\/PlayerMessageDTO.md)
- [PuzzleDTO](doc\/PuzzleDTO.md)
- [PuzzleDTOImage](doc\/PuzzleDTOImage.md)
- [QuestionDTO](doc\/QuestionDTO.md)
- [QuizzDTO](doc\/QuizzDTO.md)
- [QuizzDTOBadLevel](doc\/QuizzDTOBadLevel.md)
- [ResourceDTO](doc\/ResourceDTO.md)
- [ResourceType](doc\/ResourceType.md)
- [ResponseDTO](doc\/ResponseDTO.md)
- [SectionDTO](doc\/SectionDTO.md)
- [SectionType](doc\/SectionType.md)
- [SliderDTO](doc\/SliderDTO.md)
- [TokenDTO](doc\/TokenDTO.md)
- [TranslationAndResourceDTO](doc\/TranslationAndResourceDTO.md)
- [TranslationDTO](doc\/TranslationDTO.md)
- [User](doc\/User.md)
- [UserDetailDTO](doc\/UserDetailDTO.md)
- [VideoDTO](doc\/VideoDTO.md)
- [WeatherDTO](doc\/WeatherDTO.md)
- [WebDTO](doc\/WebDTO.md)
## Documentation For Authorization
Authentication schemes defined for the API:
### bearer
- **Type**: OAuth
- **Flow**: password
- **Authorization URL**: /authentication/Token
- **Scopes**:
- **Manager-api**: Manager WebAPI
## Author

View File

@ -1,19 +0,0 @@
# manager_api.model.ArticleDTO
## Load the model package
```dart
import 'package:manager_api/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**content** | [**List<TranslationDTO>**](TranslationDTO.md) | | [optional] [default to const []]
**isContentTop** | **bool** | | [optional]
**audioIds** | [**List<TranslationDTO>**](TranslationDTO.md) | | [optional] [default to const []]
**isReadAudioAuto** | **bool** | | [optional]
**contents** | [**List<ContentDTO>**](ContentDTO.md) | | [optional] [default to const []]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -1,24 +0,0 @@
# manager_api.model.MapDTO
## Load the model package
```dart
import 'package:manager_api/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**zoom** | **int** | | [optional]
**mapType** | [**MapDTOMapType**](MapDTOMapType.md) | | [optional]
**mapTypeMapbox** | [**MapDTOMapTypeMapbox**](MapDTOMapTypeMapbox.md) | | [optional]
**mapProvider** | [**MapDTOMapProvider**](MapDTOMapProvider.md) | | [optional]
**points** | [**List<GeoPointDTO>**](GeoPointDTO.md) | | [optional] [default to const []]
**iconResourceId** | **String** | | [optional]
**iconSource** | **String** | | [optional]
**categories** | [**List<CategorieDTO>**](CategorieDTO.md) | | [optional] [default to const []]
**latitude** | **String** | | [optional]
**longitude** | **String** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -1,19 +0,0 @@
# manager_api.model.PuzzleDTO
## Load the model package
```dart
import 'package:manager_api/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**messageDebut** | [**List<TranslationAndResourceDTO>**](TranslationAndResourceDTO.md) | | [optional] [default to const []]
**messageFin** | [**List<TranslationAndResourceDTO>**](TranslationAndResourceDTO.md) | | [optional] [default to const []]
**image** | [**PuzzleDTOImage**](PuzzleDTOImage.md) | | [optional]
**rows** | **int** | | [optional]
**cols** | **int** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -1,15 +0,0 @@
# manager_api.model.SliderDTO
## Load the model package
```dart
import 'package:manager_api/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**contents** | [**List<ContentDTO>**](ContentDTO.md) | | [optional] [default to const []]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

Some files were not shown because too many files have changed in this diff Show More