Compare commits
No commits in common. "MyInfoMate_3.0" and "master" have entirely different histories.
MyInfoMate
...
master
@ -1,117 +0,0 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,144 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
@ -1,170 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
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);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,139 +0,0 @@
|
||||
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);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,21 +39,13 @@ apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"*/
|
||||
|
||||
android {
|
||||
namespace = "be.unov.mymuseum.fortsaintheribert"
|
||||
compileSdkVersion 35
|
||||
compileSdkVersion 34
|
||||
|
||||
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'
|
||||
}
|
||||
@ -65,7 +57,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 24// flutter.minSdkVersion
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
||||
@ -51,8 +51,7 @@
|
||||
<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" />
|
||||
|
||||
@ -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-8.0-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
|
||||
|
||||
@ -31,7 +31,7 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.1.0" apply false
|
||||
id "com.android.application" version "7.2.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.9.0" apply false
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@ -1,202 +0,0 @@
|
||||
<!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>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 KiB |
149
lib/Components/Carousel/carousel_controller.dart
Normal file
149
lib/Components/Carousel/carousel_controller.dart
Normal file
@ -0,0 +1,149 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
223
lib/Components/Carousel/carousel_options.dart
Normal file
223
lib/Components/Carousel/carousel_options.dart
Normal file
@ -0,0 +1,223 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
396
lib/Components/Carousel/carousel_slider.dart
Normal file
396
lib/Components/Carousel/carousel_slider.dart
Normal file
@ -0,0 +1,396 @@
|
||||
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 {}
|
||||
43
lib/Components/Carousel/carousel_state.dart
Normal file
43
lib/Components/Carousel/carousel_state.dart
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
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);
|
||||
}
|
||||
23
lib/Components/Carousel/utils.dart
Normal file
23
lib/Components/Carousel/utils.dart
Normal file
@ -0,0 +1,23 @@
|
||||
/// 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;
|
||||
}
|
||||
@ -4,7 +4,6 @@ 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';
|
||||
@ -78,7 +77,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
|
||||
visitAppContext.isScanningBeacons = false;
|
||||
//Navigator.of(context).pop();
|
||||
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(
|
||||
builder: (context) => const HomePage3(),
|
||||
builder: (context) => const HomePage(),
|
||||
),(route) => false);
|
||||
});
|
||||
}
|
||||
@ -97,14 +96,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(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
//import 'package:flutter_svg_provider/flutter_svg_provider.dart';
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||||
import 'package:mymuseum_visitapp/app_context.dart';
|
||||
import 'package:mymuseum_visitapp/constants.dart';
|
||||
@ -48,10 +48,9 @@ class _LanguageSelection extends State<LanguageSelection> with TickerProviderSta
|
||||
}
|
||||
|
||||
return PopupMenuButton(
|
||||
color: kMainColor.withValues(alpha: 0.65),
|
||||
icon: Container(
|
||||
height: size.height *0.08,
|
||||
width: size.width *0.08,
|
||||
height: size.height *0.07,
|
||||
width: size.width *0.07,
|
||||
decoration: flagDecoration(selectedLanguage!),
|
||||
),
|
||||
itemBuilder: (context){
|
||||
|
||||
@ -26,16 +26,9 @@ class _ScannerBoutonState extends State<ScannerBouton> {
|
||||
return InkWell(
|
||||
onTap: _onItemTapped,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
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
|
||||
)
|
||||
],
|
||||
color: kMainColor1,
|
||||
),
|
||||
height: 85.0,
|
||||
width: 85.0,
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:mymuseum_visitapp/Components/SlideFromRouteRight.dart';
|
||||
import 'package:manager_api/api.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/Screens/Article/article_page.dart';
|
||||
import 'package:mymuseum_visitapp/Screens/Quizz/quizz_page.dart';
|
||||
import 'package:mymuseum_visitapp/app_context.dart';
|
||||
import 'package:mymuseum_visitapp/constants.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
|
||||
|
||||
class ScannerDialog extends StatefulWidget {
|
||||
const ScannerDialog({Key? key, required this.appContext}) : super(key: key);
|
||||
@ -20,16 +22,19 @@ class ScannerDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ScannerDialogState extends State<ScannerDialog> {
|
||||
final MobileScannerController controller = MobileScannerController();
|
||||
bool isProcessing = false;
|
||||
Barcode? result;
|
||||
QRViewController? controller;
|
||||
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
||||
|
||||
// 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.stop();
|
||||
controller!.pauseCamera();
|
||||
}
|
||||
controller.start();
|
||||
controller!.resumeCamera();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -37,133 +42,208 @@ class _ScannerDialogState extends State<ScannerDialog> {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
|
||||
return Container(
|
||||
height: size.height * 0.5,
|
||||
width: size.width * 0.9,
|
||||
height: size.height *0.5,
|
||||
width: size.width *0.9,
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
child: MobileScanner(
|
||||
controller: controller,
|
||||
//allowDuplicates: false,
|
||||
onDetect: (barcodes) => _onDetect(barcodes),
|
||||
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);
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
_buildControlButton(
|
||||
icon: Icons.flash_on,
|
||||
onTap: () => controller.toggleTorch(),
|
||||
alignment: Alignment.topRight,
|
||||
),
|
||||
_buildControlButton(
|
||||
icon: Icons.flip_camera_android,
|
||||
onTap: () => controller.switchCamera(),
|
||||
alignment: Alignment.bottomRight,
|
||||
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);
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
),
|
||||
),
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
void _onDetect(BarcodeCapture capture) {
|
||||
if (isProcessing) return;
|
||||
_onQRViewCreated(QRViewController controller) {
|
||||
setState(() {
|
||||
this.controller = controller;
|
||||
});
|
||||
if (Platform.isAndroid) {
|
||||
controller.pauseCamera();
|
||||
}
|
||||
controller.resumeCamera();
|
||||
controller.scannedDataStream.listen((scanData) {
|
||||
setState(() {
|
||||
result = scanData;
|
||||
|
||||
final barcode = capture.barcodes.first;
|
||||
final code = barcode.rawValue ?? "";
|
||||
var code = result == null ? "" : result!.code.toString();
|
||||
if(result!.format == BarcodeFormat.qrcode) {
|
||||
controller.pauseCamera();
|
||||
|
||||
if (barcode.format == BarcodeFormat.qrCode && code.isNotEmpty) {
|
||||
isProcessing = true;
|
||||
RegExp regExp = RegExp(r'^(?:https:\/\/web\.myinfomate\.be\/([^\/]+)\/([^\/]+)\/([^\/]+)|([^\/]+))$');
|
||||
var match = regExp.firstMatch(code);
|
||||
String? instanceId;
|
||||
String? configurationId;
|
||||
String? sectionId;
|
||||
|
||||
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) {
|
||||
instanceId = match.group(1);
|
||||
configurationId = match.group(2);
|
||||
sectionId = match.group(3) ?? match.group(4);
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
if ((match == null && match2 == null) || sectionId == null) {
|
||||
|
||||
//print("QR CODE FOUND");
|
||||
/*ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('QR CODE FOUND - ${code.toString()}')),
|
||||
);*/
|
||||
|
||||
VisitAppContext visitAppContext = widget.appContext!.getContext();
|
||||
|
||||
if(!visitAppContext.sectionIds!.contains(sectionId) || sectionId == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(TranslationHelper.getFromLocale('invalidQRCode', widget.appContext!.getContext())), backgroundColor: kMainColor2),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
|
||||
} else {
|
||||
SectionDTO section = visitAppContext.currentSections!.firstWhere((cs) => cs!.id == sectionId)!;
|
||||
switch(section.type) {
|
||||
case SectionType.Article:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ArticlePage(visitAppContextIn: visitAppContext, articleId: section.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(
|
||||
SnackBar(content: Text("L'URL ne correspond pas au format attendu."), backgroundColor: kMainColor2),
|
||||
const SnackBar(content: Text('no Permission')),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
VisitAppContext visitAppContext = widget.appContext!.getContext();
|
||||
|
||||
if (visitAppContext.sectionIds == null || !visitAppContext.sectionIds!.contains(sectionId)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(TranslationHelper.getFromLocale('invalidQRCode', visitAppContext)), backgroundColor: kMainColor2),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
dynamic rawSection = visitAppContext.currentSections!.firstWhere((cs) => cs!['id'] == sectionId)!;
|
||||
Navigator.of(context).pop();
|
||||
Navigator.push(
|
||||
context,
|
||||
SlideFromRightRoute(page: SectionPage(
|
||||
configuration: visitAppContext.configuration!,
|
||||
rawSection: rawSection,
|
||||
visitAppContextIn: visitAppContext,
|
||||
sectionId: rawSection['id'],
|
||||
)),
|
||||
);
|
||||
// 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) {
|
||||
showScannerDialog (BuildContext context, AppContext appContext) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
content: ScannerDialog(appContext: appContext),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0))
|
||||
),
|
||||
content: ScannerDialog(appContext: appContext),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
), context: context
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -8,11 +8,9 @@ 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();
|
||||
@ -27,18 +25,17 @@ class _SearchBoxState extends State<SearchBox> {
|
||||
final appContext = Provider.of<AppContext>(context);
|
||||
|
||||
return Container(
|
||||
width: widget.width ?? size.width*0.65,
|
||||
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.withValues(alpha: 0.4),
|
||||
color: Colors.white.withOpacity(0.4),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: TextFormField(
|
||||
cursorColor: kMainColor,
|
||||
controller: _controller,
|
||||
onChanged: widget.onChanged,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
|
||||
@ -26,11 +26,10 @@ class _SearchNumberBoxState extends State<SearchNumberBox> {
|
||||
vertical: kDefaultPadding / 4, // 5 top and bottom
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.4),
|
||||
color: Colors.white.withOpacity(0.4),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: TextFormField(
|
||||
cursorColor: kMainColor,
|
||||
controller: _controller,
|
||||
onChanged: widget.onChanged,
|
||||
keyboardType: TextInputType.number,
|
||||
|
||||
@ -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_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
|
||||
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
|
||||
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -1,11 +1,10 @@
|
||||
import 'dart:convert';
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:mymuseum_visitapp/Components/Carousel/carousel_slider.dart' as cs;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/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';
|
||||
@ -23,12 +22,12 @@ class SliderImagesWidget extends StatefulWidget {
|
||||
|
||||
class _SliderImagesWidget extends State<SliderImagesWidget> {
|
||||
List<ResourceModel?> resourcesInWidget = [];
|
||||
late CarouselSliderController? sliderController;
|
||||
late cs.CarouselController? sliderController;
|
||||
final ValueNotifier<int> currentIndex = ValueNotifier<int>(1);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
sliderController = CarouselSliderController();
|
||||
sliderController = cs.CarouselController();
|
||||
resourcesInWidget = widget.resources;
|
||||
super.initState();
|
||||
}
|
||||
@ -54,10 +53,10 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if(resourcesInWidget.isNotEmpty)
|
||||
CarouselSlider(
|
||||
cs.CarouselSlider(
|
||||
carouselController: sliderController,
|
||||
options: CarouselOptions(
|
||||
onPageChanged: (int index, CarouselPageChangedReason reason) {
|
||||
options: cs.CarouselOptions(
|
||||
onPageChanged: (int index, cs.CarouselPageChangedReason reason) {
|
||||
//setState(() {
|
||||
//print("SET STATE");
|
||||
currentIndex.value = index + 1;
|
||||
@ -70,12 +69,8 @@ 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(
|
||||
@ -123,7 +118,7 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
|
||||
),
|
||||
);
|
||||
}
|
||||
);*/
|
||||
);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
|
||||
@ -1,299 +0,0 @@
|
||||
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',
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
23
lib/Components/fade_route.dart
Normal file
23
lib/Components/fade_route.dart
Normal file
@ -0,0 +1,23 @@
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -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_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||||
import 'package:mymuseum_visitapp/app_context.dart';
|
||||
import 'package:mymuseum_visitapp/constants.dart';
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
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),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,99 +0,0 @@
|
||||
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)));
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/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"], // TODO section data
|
||||
data: element["data"],
|
||||
dateCreation: DateTime.tryParse(element["dateCreation"]),
|
||||
order: int.parse(element["orderOfElement"]),
|
||||
);
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/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, // TODO section data
|
||||
'data': section.data,
|
||||
'dateCreation': section.dateCreation!.toUtc().toIso8601String(),
|
||||
'orderOfElement': section.order,
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||||
import 'package:mymuseum_visitapp/translations.dart';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
|
||||
class ResponseSubDTO {
|
||||
List<TranslationAndResourceDTO>? label;
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
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'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
|
||||
class SectionRead {
|
||||
String id = "";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
|
||||
class BeaconSection {
|
||||
int? minorBeaconId;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
|
||||
class ResourceModel {
|
||||
String? id = "";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
|
||||
class Translation {
|
||||
String? language = "";
|
||||
|
||||
@ -1,30 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/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.mymuseum.be"); // Replace by https://api.mymuseum.be //http://192.168.31.140:8089 // https://api.myinfomate.be // http://192.168.31.228:5000
|
||||
Client clientAPI = Client("https://api.myinfomate.be"); // Replace by https://api.mymuseum.be //http://192.168.31.140:8089
|
||||
|
||||
String? id = "";
|
||||
String? language = "";
|
||||
String? instanceId = "63514fd67ed8c735aaa4b8f2"; // 63514fd67ed8c735aaa4b8f2 MyInfoMate test instance -- Fort de Saint-Héribert Mymuseum instance id : 633ee379d9405f32f166f047 // 63514fd67ed8c735aaa4b8f1 Mymuseum test // MDLF instance 65ccc67265373befd15be511
|
||||
String? instanceId = "633ee379d9405f32f166f047"; // 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<dynamic>? currentSections;
|
||||
List<SectionDTO?>? currentSections;
|
||||
List<SectionRead> readSections = [];
|
||||
bool isContentCurrentlyShown = false;
|
||||
bool isScanningBeacons = false;
|
||||
bool isScanBeaconAlreadyAllowed = false;
|
||||
bool isMaximizeTextSize = false;
|
||||
|
||||
Size? puzzleSize;
|
||||
|
||||
List<ResourceModel> audiosNotWorking = [];
|
||||
|
||||
bool? isAdmin = false;
|
||||
|
||||
@ -1,217 +0,0 @@
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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_new/api.dart';
|
||||
import 'package:manager_api/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/Sections/Article/audio_player.dart';
|
||||
import 'package:mymuseum_visitapp/Screens/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,31 +24,27 @@ 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.articleDTO, required this.resourcesModel, this.mainAudioId}) : super(key: key);
|
||||
const ArticlePage({Key? key, required this.visitAppContextIn, required this.articleId}) : super(key: key);
|
||||
|
||||
final ArticleDTO articleDTO;
|
||||
final String articleId;
|
||||
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();
|
||||
}
|
||||
|
||||
@ -66,179 +62,73 @@ 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,
|
||||
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
|
||||
),
|
||||
],
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.centerRight,
|
||||
end: Alignment.centerLeft,
|
||||
colors: [
|
||||
kMainColor0,
|
||||
kMainColor1,
|
||||
kMainColor2,
|
||||
appBar: CustomAppBar(
|
||||
title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "",
|
||||
isHomeButton: false,
|
||||
isTextSizeButton: true,
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: getArticle(appContext, visitAppContext.clientAPI, widget.articleId, false),
|
||||
builder: (context, AsyncSnapshot<dynamic> snapshot) {
|
||||
if(articleDTO != null && sectionDTO != null) {
|
||||
if(size.height > size.width) {
|
||||
return Column(
|
||||
children: [
|
||||
if(articleDTO!.isContentTop!)
|
||||
getContent(size, appContext),
|
||||
if(articleDTO!.isContentTop! && resourcesModel.isNotEmpty)
|
||||
getImages(size, articleDTO!.isContentTop!),
|
||||
|
||||
if(!articleDTO!.isContentTop! && resourcesModel.isNotEmpty)
|
||||
getImages(size, articleDTO!.isContentTop!),
|
||||
if(!articleDTO!.isContentTop!)
|
||||
getContent(size, appContext),
|
||||
|
||||
/*if(audioResourceModel != null)
|
||||
AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!),*/
|
||||
],
|
||||
),
|
||||
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,
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
child: Column(
|
||||
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)
|
||||
),
|
||||
)
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if(articleDTO!.isContentTop!)
|
||||
getContent(size, appContext),
|
||||
if(articleDTO!.isContentTop! && resourcesModel.isNotEmpty)
|
||||
getImages(size, articleDTO!.isContentTop!),
|
||||
|
||||
if(!articleDTO!.isContentTop! && resourcesModel.isNotEmpty)
|
||||
getImages(size, articleDTO!.isContentTop!),
|
||||
if(!articleDTO!.isContentTop!)
|
||||
getContent(size, appContext),
|
||||
],
|
||||
),
|
||||
/*if(audioResourceModel != null)
|
||||
AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!)*/
|
||||
],
|
||||
),
|
||||
),
|
||||
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(widget.articleDTO.isContentTop!)
|
||||
getContent(size, appContext),
|
||||
if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty)
|
||||
getImages(size, widget.articleDTO.isContentTop!),
|
||||
|
||||
if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty)
|
||||
getImages(size, widget.articleDTO.isContentTop!),
|
||||
if(!widget.articleDTO.isContentTop!)
|
||||
getContent(size, appContext),
|
||||
|
||||
/*if(audioResourceModel != null)
|
||||
AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!),*/
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if(widget.articleDTO.isContentTop!)
|
||||
getContent(size, appContext),
|
||||
if(widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty)
|
||||
getImages(size, widget.articleDTO.isContentTop!),
|
||||
|
||||
if(!widget.articleDTO.isContentTop! && resourcesModelToShow.isNotEmpty)
|
||||
getImages(size, widget.articleDTO.isContentTop!),
|
||||
if(!widget.articleDTO.isContentTop!)
|
||||
getContent(size, appContext),
|
||||
],
|
||||
),
|
||||
/*if(audioResourceModel != null)
|
||||
AudioPlayerContainer(audioBytes: audiobytes, isAuto: articleDTO!.isReadAudioAuto!)*/
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const LoadingCommon();
|
||||
}
|
||||
}
|
||||
),
|
||||
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,
|
||||
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,
|
||||
);
|
||||
}
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.miniEndFloat, //miniEndTop
|
||||
);
|
||||
@ -259,13 +149,13 @@ class _ArticlePageState extends State<ArticlePage> {
|
||||
),
|
||||
color: Colors.white,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
boxShadow: const [kDefaultShadow],
|
||||
),
|
||||
child: SliderImagesWidget(
|
||||
resources: resourcesModelToShow,
|
||||
resources: resourcesModel,
|
||||
height: size.height * 0.29,
|
||||
contentsDTO: widget.articleDTO.contents!,
|
||||
contentsDTO: articleDTO!.contents!,
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -284,13 +174,13 @@ class _ArticlePageState extends State<ArticlePage> {
|
||||
),
|
||||
color: Colors.white,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
boxShadow: const [kDefaultShadow],
|
||||
),
|
||||
child: SliderImagesWidget(
|
||||
resources: resourcesModelToShow,
|
||||
resources: resourcesModel,
|
||||
height: size.height * 0.29,
|
||||
contentsDTO: widget.articleDTO.contents!,
|
||||
contentsDTO: articleDTO!.contents!,
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -306,7 +196,7 @@ class _ArticlePageState extends State<ArticlePage> {
|
||||
height: size.height * 0.76,
|
||||
//color: Colors.blueAccent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0, bottom: 8.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
@ -315,14 +205,14 @@ class _ArticlePageState extends State<ArticlePage> {
|
||||
),
|
||||
color: Colors.white,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
boxShadow: const [kDefaultShadow],
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: HtmlWidget(
|
||||
TranslationHelper.get(widget.articleDTO.content, appContext.getContext()),
|
||||
TranslationHelper.get(articleDTO!.content, appContext.getContext()),
|
||||
textStyle: TextStyle(fontSize: (appContext.getContext() as VisitAppContext).isMaximizeTextSize ? kArticleContentBiggerSize : kArticleContentSize),
|
||||
customStylesBuilder: (element)
|
||||
{
|
||||
@ -343,7 +233,7 @@ class _ArticlePageState extends State<ArticlePage> {
|
||||
//height: size.height * 0.65,
|
||||
//color: Colors.blueAccent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0, bottom: 8.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
@ -352,14 +242,14 @@ class _ArticlePageState extends State<ArticlePage> {
|
||||
),
|
||||
color: Colors.white,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
boxShadow: const [kDefaultShadow],
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.5),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: HtmlWidget(
|
||||
TranslationHelper.get(widget.articleDTO.content, appContext.getContext()),
|
||||
TranslationHelper.get(articleDTO!.content, appContext.getContext()),
|
||||
//textAlign: TextAlign.left,
|
||||
textStyle: TextStyle(fontSize: (appContext.getContext() as VisitAppContext).isMaximizeTextSize ? kArticleContentBiggerSize : kArticleContentSize, fontFamily: "Arial"),
|
||||
),
|
||||
@ -372,7 +262,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!;
|
||||
@ -497,7 +387,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.resource?.url, type: ResourceType.Image));
|
||||
resourcesModel.add(ResourceModel(id: image.resourceId, source: image.resourceUrl, type: ResourceType.Image));
|
||||
/*} else {
|
||||
print("EMPTY resourcesModel online - audio");
|
||||
}*/
|
||||
@ -517,5 +407,5 @@ class _ArticlePageState extends State<ArticlePage> {
|
||||
print("IN CATCH");
|
||||
return null;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@ -1,294 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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_new/api.dart';
|
||||
import 'package:manager_api/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/ConfigurationPage/configuration_page.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';
|
||||
@ -79,7 +79,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
|
||||
|
||||
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ConfigurationPage(configuration: configurations[index], isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
|
||||
VisitPage(configurationId: configurations[index].id!, isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
|
||||
));
|
||||
|
||||
}
|
||||
@ -110,7 +110,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
|
||||
|
||||
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ConfigurationPage(configuration: configurations[index], isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
|
||||
VisitPage(configurationId: configurations[index].id!, isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,8 @@ 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_new/api.dart';
|
||||
import 'package:manager_api/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';
|
||||
@ -15,12 +14,14 @@ 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 {
|
||||
@ -49,66 +50,43 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
isHomeButton: false,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
child: FutureBuilder(
|
||||
future: 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();
|
||||
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: SizedBox(
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
child: FutureBuilder(
|
||||
future: 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();
|
||||
return RefreshIndicator (
|
||||
onRefresh: () {
|
||||
setState(() {});
|
||||
return Future(() => null);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
},
|
||||
child: const SizedBox(
|
||||
height: 50,
|
||||
width: 10,
|
||||
child: Text('TEST XR'),
|
||||
),
|
||||
),*/
|
||||
],
|
||||
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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
)
|
||||
),
|
||||
// floatingActionButton: ScannerBouton(appContext: appContext),
|
||||
//floatingActionButton: ScannerBouton(appContext: appContext),
|
||||
//floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterFloat,
|
||||
/*bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: currentIndex,
|
||||
|
||||
@ -1,454 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import 'package:carousel_slider/carousel_controller.dart';
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:mymuseum_visitapp/Components/Carousel/carousel_slider.dart' as cs;
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||
@ -24,7 +23,7 @@ class QuestionsListWidget extends StatefulWidget {
|
||||
|
||||
class _QuestionsListWidget extends State<QuestionsListWidget> {
|
||||
List<QuestionSubDTO> _questionsSubDTO = <QuestionSubDTO>[];
|
||||
CarouselSliderController? sliderController;
|
||||
cs.CarouselController? sliderController;
|
||||
int currentIndex = 1;
|
||||
|
||||
bool kIsWeb = false;
|
||||
@ -32,7 +31,7 @@ class _QuestionsListWidget extends State<QuestionsListWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
sliderController = CarouselSliderController();
|
||||
sliderController = cs.CarouselController();
|
||||
_questionsSubDTO = widget.questionsSubDTO!;
|
||||
}
|
||||
|
||||
@ -58,8 +57,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 * 0.88:
|
||||
MediaQuery.of(context).size.height * 0.88,
|
||||
MediaQuery.of(context).size.height :
|
||||
MediaQuery.of(context).size.height,
|
||||
width: double.infinity,
|
||||
//color: Colors.orange,
|
||||
child: Stack(
|
||||
@ -70,10 +69,10 @@ class _QuestionsListWidget extends State<QuestionsListWidget> {
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
if(_questionsSubDTO.isNotEmpty)
|
||||
CarouselSlider(
|
||||
cs.CarouselSlider(
|
||||
carouselController: sliderController,
|
||||
options: CarouselOptions(
|
||||
onPageChanged: (int index, CarouselPageChangedReason reason) {
|
||||
options: cs.CarouselOptions(
|
||||
onPageChanged: (int index, cs.CarouselPageChangedReason reason) {
|
||||
setState(() {
|
||||
currentIndex = index + 1;
|
||||
});
|
||||
443
lib/Screens/Quizz/quizz_page.dart
Normal file
443
lib/Screens/Quizz/quizz_page.dart
Normal file
@ -0,0 +1,443 @@
|
||||
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
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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/section_page.dart';
|
||||
import 'package:mymuseum_visitapp/Screens/Article/article_page.dart';
|
||||
import 'package:mymuseum_visitapp/app_context.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 SectionPage(configuration: ConfigurationDTO(), rawSection: null, sectionId: code, visitAppContextIn: VisitAppContext()); // will not work..
|
||||
return ArticlePage(articleId: code, visitAppContextIn: VisitAppContext()); // will not work..
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -1,198 +0,0 @@
|
||||
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
|
||||
@ -1,163 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),*/
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,475 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,207 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -1,173 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,136 +0,0 @@
|
||||
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();
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,389 +0,0 @@
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,239 +0,0 @@
|
||||
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();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),*/
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,236 +0,0 @@
|
||||
|
||||
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);
|
||||
}),
|
||||
),
|
||||
),*/
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,188 +0,0 @@
|
||||
//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));
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
@ -1,619 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,375 +0,0 @@
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,349 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
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),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
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
|
||||
);
|
||||
}
|
||||
@ -1,357 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,287 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,506 +0,0 @@
|
||||
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
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,359 +0,0 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,263 +0,0 @@
|
||||
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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,382 +0,0 @@
|
||||
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
|
||||
@ -1,242 +0,0 @@
|
||||
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
|
||||
@ -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_new/api.dart';
|
||||
import 'package:manager_api/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/Sections/Article/article_page.dart';
|
||||
import 'package:mymuseum_visitapp/Screens/Article/article_page.dart';
|
||||
import 'package:mymuseum_visitapp/Services/apiService.dart';
|
||||
import 'package:mymuseum_visitapp/app_context.dart';
|
||||
import 'package:mymuseum_visitapp/client.dart';
|
||||
190
lib/Screens/Visit/components/body.dart
Normal file
190
lib/Screens/Visit/components/body.dart
Normal file
@ -0,0 +1,190 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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_new/api.dart';
|
||||
import 'package:manager_api/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,14 +16,12 @@ 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;
|
||||
@ -35,7 +33,7 @@ class SectionCard extends StatelessWidget {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
final appContext = Provider.of<AppContext>(context);
|
||||
VisitAppContext visitAppContext = appContext.getContext();
|
||||
bool isOffline = configuration.isOffline! ?? false;
|
||||
bool isOffline = (appContext.getContext() as VisitAppContext).configuration!.isOffline!;
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
@ -71,7 +69,7 @@ class SectionCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if(sectionDTO.imageId != null ) // && (appContext.getContext() as VisitAppContext).configuration != null
|
||||
if(sectionDTO.imageId != null)
|
||||
// section main image
|
||||
Positioned(
|
||||
top: kDefaultPadding +4.0,
|
||||
@ -84,7 +82,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, configuration, sectionDTO.imageId!),
|
||||
future: ApiService.getResource(appContext, (appContext.getContext() as VisitAppContext).configuration!, sectionDTO.imageId!),
|
||||
builder: (context, AsyncSnapshot<dynamic> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
return ClipRRect(
|
||||
@ -144,6 +142,10 @@ 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(),
|
||||
@ -169,16 +171,6 @@ 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)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -6,17 +6,16 @@ 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_new/api.dart';
|
||||
import 'package:manager_api/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/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/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/app_context.dart';
|
||||
import 'package:mymuseum_visitapp/constants.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
@ -24,18 +23,18 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'components/body.dart';
|
||||
|
||||
class ConfigurationPage extends StatefulWidget {
|
||||
const ConfigurationPage({Key? key,required this.configuration, required this.isAlreadyAllowed}) : super(key: key);
|
||||
class VisitPage extends StatefulWidget {
|
||||
const VisitPage({Key? key, required this.configurationId, required this.isAlreadyAllowed}) : super(key: key);
|
||||
|
||||
final ConfigurationDTO configuration;
|
||||
final String configurationId;
|
||||
final bool isAlreadyAllowed;
|
||||
|
||||
@override
|
||||
State<ConfigurationPage> createState() => _ConfigurationPageState();
|
||||
State<VisitPage> createState() => _VisitPageState();
|
||||
}
|
||||
|
||||
class _ConfigurationPageState extends State<ConfigurationPage> with WidgetsBindingObserver {
|
||||
//ConfigurationDTO? configuration;
|
||||
class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
|
||||
ConfigurationDTO? configuration;
|
||||
|
||||
int timeBetweenBeaconPopUp = 20000; // 20 sec
|
||||
int meterToBeacon = 100; // 15 meters
|
||||
@ -73,14 +72,6 @@ class _ConfigurationPageState extends State<ConfigurationPage> with WidgetsBindi
|
||||
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();
|
||||
}
|
||||
|
||||
@ -296,17 +287,33 @@ class _ConfigurationPageState extends State<ConfigurationPage> with WidgetsBindi
|
||||
//visitAppContext.isArticleCurrentlyShown = true;
|
||||
lastTimePopUpWasClosed = DateTime.now();
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SectionPage(
|
||||
configuration: widget.configuration,
|
||||
rawSection: null,
|
||||
visitAppContextIn: visitAppContext,
|
||||
sectionId: beaconSection!.sectionId!,
|
||||
),
|
||||
),
|
||||
);
|
||||
switch(beaconSection!.sectionType!) {
|
||||
case SectionType.Article:
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ArticlePage(
|
||||
visitAppContextIn: visitAppContext,
|
||||
articleId: 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;
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
@ -330,7 +337,7 @@ class _ConfigurationPageState extends State<ConfigurationPage> with WidgetsBindi
|
||||
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);
|
||||
@ -341,15 +348,17 @@ class _ConfigurationPageState extends State<ConfigurationPage> with WidgetsBindi
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
return PopScope(
|
||||
canPop: true,
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
/*appBar: CustomAppBar(
|
||||
appBar: CustomAppBar(
|
||||
title: TranslationHelper.get(configuration!.title, visitAppContext),
|
||||
isHomeButton: true,
|
||||
),*/
|
||||
),
|
||||
backgroundColor: kBackgroundGrey,
|
||||
body: Body(configuration: widget.configuration),
|
||||
body: Body(configurationId: configuration!.id), // TODO handle error..
|
||||
floatingActionButton: Stack(
|
||||
children: [
|
||||
visitAppContext.beaconSections != null && visitAppContext.beaconSections!.where((bs) => bs!.configurationId == visitAppContext.configuration!.id).isNotEmpty ? Align(
|
||||
@ -1,367 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/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(resourceId: configuration.imageId, resource: ResourceDTO(url: configuration.imageSource, id: configuration.imageId)));
|
||||
await downloadAndPushLocalImage(client, ContentDTO(resourceUrl: configuration.imageSource, resourceId: configuration.imageId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,13 +40,12 @@ class ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<dynamic>?> getAllSections(Client client, String configurationId) async {
|
||||
static Future<List<SectionDTO>?> getAllSections(Client client, String configurationId) async {
|
||||
try {
|
||||
bool isOnline = await hasNetwork();
|
||||
if(isOnline) {
|
||||
final rawList = await client.sectionApi!.sectionGetFromConfigurationDetail(configurationId);
|
||||
var sections = rawList.map((json) => SectionDTO.fromJson(json)).toList();
|
||||
return rawList ?? [];
|
||||
List<SectionDTO>? sections = await client.sectionApi!.sectionGetFromConfiguration(configurationId);
|
||||
return sections;
|
||||
} else {
|
||||
return []; // TODO return local list..
|
||||
}
|
||||
@ -68,7 +67,7 @@ class ApiService {
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
print("getAllBeacons IN CATCH");
|
||||
print("getAllSections IN CATCH");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@ -85,7 +84,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 {
|
||||
@ -99,13 +98,13 @@ class ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<ResourceModel?> downloadImage(Client client, ContentDTO contentDTO) async { // TODO CHECK wtf mymuseum
|
||||
var source = contentDTO.resource!.url;
|
||||
static Future<ResourceModel?> downloadImage(Client client, ContentDTO contentDTO) async {
|
||||
var source = contentDTO.resourceUrl;
|
||||
//print("SOURCE getAndDownloadImage");
|
||||
if(contentDTO.resource!.url != null) {
|
||||
if(contentDTO.resource!.url!.contains("localhost:5000")){
|
||||
if(contentDTO.resourceUrl != null) {
|
||||
if(contentDTO.resourceUrl!.contains("localhost:5000")){
|
||||
print("Contains localhost:5000");
|
||||
source = contentDTO.resource!.url!.replaceAll("http://localhost:5000", client.apiApi!.basePath);
|
||||
source = contentDTO.resourceUrl!.replaceAll("http://localhost:5000", client.apiApi!.basePath);
|
||||
}
|
||||
} else {
|
||||
source = "https://api.mymuseum.be/api/Resource/"+contentDTO.resourceId!; // TODO UPDATE ROUTE
|
||||
@ -119,7 +118,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.resource!.url, path: base64Str, type: ResourceType.Image);
|
||||
ResourceModel resourceModel = ResourceModel(id: contentDTO.resourceId, source: contentDTO.resourceUrl, path: base64Str, type: ResourceType.Image);
|
||||
return resourceModel;
|
||||
}
|
||||
|
||||
@ -163,7 +162,7 @@ class ApiService {
|
||||
}
|
||||
|
||||
static Future<File?> getResource(AppContext appContext, ConfigurationDTO configurationDTO, String imageId) async {
|
||||
if(configurationDTO.isOffline!)
|
||||
if((appContext.getContext() as VisitAppContext).configuration == null || (appContext.getContext() as VisitAppContext).configuration!.isOffline!)
|
||||
{
|
||||
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
|
||||
String localPath = appDocumentsDirectory!.path;
|
||||
@ -193,7 +192,8 @@ class ApiService {
|
||||
try {
|
||||
bool isOnline = await hasNetwork();
|
||||
if(isOnline) {
|
||||
ExportConfigurationDTO? exportConfiguration = await client.configurationApi!.configurationExport(configurationId, language: language);
|
||||
ExportConfigurationDTO? exportConfiguration = await client.configurationApi!.configurationExport(configurationId, language);
|
||||
|
||||
return exportConfiguration;
|
||||
} else {
|
||||
return null; // TODO return local list..
|
||||
|
||||
@ -2,7 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/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!, language: isAllLanguages ? null : visitAppContext.language); // tabletAppContext.configuration!.id! // 65c5f0ee4c030e63ce16bff5 TODO Remove
|
||||
exportConfigurationDTO = await visitAppContext.clientAPI.configurationApi!.configurationExport(widget.configuration.id!, 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.Quiz).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.Quizz).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,10 +176,8 @@ 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!)); // TODO section data
|
||||
ArticleDTO? articleDTO = ArticleDTO.fromJson(jsonDecode(section.data!));
|
||||
|
||||
if(articleDTO != null) {
|
||||
for(var image in articleDTO.contents!) {
|
||||
@ -202,7 +200,7 @@ class _DownloadConfigurationWidgetState extends State<DownloadConfigurationWidge
|
||||
//audiosNotWorking = await importAudio(visitAppContext, exportConfigurationDTO, audioId.value!, audiosNotWorking);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
newOrder = newOrder + 1;
|
||||
}
|
||||
|
||||
|
||||
@ -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_new', pubAuthor: 'Fransolet Thomas', useEnumExtension: true),
|
||||
inputSpec: InputSpec(path: 'lib/api/swagger.yaml'),
|
||||
AdditionalProperties(pubName: 'manager_api', pubAuthor: 'Fransolet Thomas', useEnumExtension: true),
|
||||
inputSpecFile: 'lib/api/swagger.yaml',
|
||||
generatorName: Generator.dart,
|
||||
outputDirectory: 'manager_api_new')
|
||||
alwaysRun: true,
|
||||
outputDirectory: 'manager_api')
|
||||
class Example extends OpenapiGeneratorConfig {}
|
||||
|
||||
/*
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//import 'package:managerapi/api.dart';
|
||||
//import 'package:openapi_dart_common/openapi.dart';
|
||||
|
||||
import 'package:manager_api_new/api.dart';
|
||||
import 'package:manager_api/api.dart';
|
||||
|
||||
class Client {
|
||||
ApiClient? _apiClient;
|
||||
|
||||
@ -5,11 +5,9 @@ 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';
|
||||
@ -27,8 +25,6 @@ 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);
|
||||
@ -36,7 +32,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: "63514fd67ed8c735aaa4b8f2", 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: "633ee379d9405f32f166f047", 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));
|
||||
@ -75,12 +71,6 @@ void main() async {
|
||||
//);
|
||||
//AudioPlayer.global.setGlobalAudioContext(audioContext);
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.transparent, // ← important
|
||||
statusBarColor: Colors.transparent,
|
||||
));
|
||||
|
||||
runApp(myApp);
|
||||
}
|
||||
|
||||
@ -103,7 +93,7 @@ class _MyAppState extends State<MyApp> {
|
||||
create: (_) => AppContext(widget.visitAppContext),
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Carnaval de Marche', //'Musée de la fraise' // Autres // 'Fort Saint Héribert'
|
||||
title: 'Fort Saint Héribert', //'Musée de la fraise'
|
||||
initialRoute: widget.initialRoute,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
@ -111,9 +101,9 @@ class _MyAppState extends State<MyApp> {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const[
|
||||
Locale('en', ''),
|
||||
Locale('fr', ''),
|
||||
supportedLocales: [
|
||||
const Locale('en', ''),
|
||||
const Locale('fr', ''),
|
||||
],
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
@ -126,7 +116,7 @@ class _MyAppState extends State<MyApp> {
|
||||
),
|
||||
),
|
||||
routes: {
|
||||
'/home': (context) => const HomePage3(),
|
||||
'/home': (context) => const HomePage(),
|
||||
}
|
||||
),
|
||||
);
|
||||
|
||||
@ -25,17 +25,7 @@ List<Translation> translations = [
|
||||
"restart": "Recommencer",
|
||||
"downloadInProgress": "Téléchargement en cours",
|
||||
"downloadFinish": "Téléchargement terminé",
|
||||
"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"
|
||||
"upToDate": "Tout est à jour"
|
||||
}),
|
||||
Translation(language: "EN", data: {
|
||||
"visitTitle": "List of tours",
|
||||
@ -61,17 +51,7 @@ List<Translation> translations = [
|
||||
"restart": "Restart",
|
||||
"downloadInProgress": "Download in progress",
|
||||
"downloadFinish": "Download complete",
|
||||
"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"
|
||||
"upToDate": "Up to date"
|
||||
}),
|
||||
Translation(language: "DE", data: {
|
||||
"visitTitle": "Liste der Touren",
|
||||
@ -97,17 +77,7 @@ List<Translation> translations = [
|
||||
"restart": "Neu starten",
|
||||
"downloadInProgress": "Download läuft",
|
||||
"downloadFinish": "Download abgeschlossen",
|
||||
"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"
|
||||
"upToDate": "Alles ist auf dem neuesten Stand"
|
||||
}),
|
||||
Translation(language: "NL", data: {
|
||||
"visitTitle": "Lijst met rondleidingen",
|
||||
@ -133,17 +103,7 @@ List<Translation> translations = [
|
||||
"restart": "Herstarten",
|
||||
"downloadInProgress": "Download bezig",
|
||||
"downloadFinish": "Download voltooid",
|
||||
"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"
|
||||
"upToDate": "Alles is up-to-date"
|
||||
}),
|
||||
Translation(language: "IT", data: {
|
||||
"visitTitle": "Elenco dei tour",
|
||||
@ -169,17 +129,7 @@ List<Translation> translations = [
|
||||
"restart": "Ricomincia",
|
||||
"downloadInProgress": "Download in corso",
|
||||
"downloadFinish": "Download completato",
|
||||
"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"
|
||||
"upToDate": "Tutto è aggiornato"
|
||||
}),
|
||||
Translation(language: "ES", data: {
|
||||
"visitTitle": "Lista de recorridos",
|
||||
@ -205,17 +155,7 @@ List<Translation> translations = [
|
||||
"restart": "Reanudar",
|
||||
"downloadInProgress": "Descarga en curso",
|
||||
"downloadFinish": "Descarga completada",
|
||||
"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"
|
||||
"upToDate": "Todo está al día"
|
||||
}),
|
||||
Translation(language: "PL", data: {
|
||||
"visitTitle": "Lista wycieczek",
|
||||
@ -241,17 +181,7 @@ List<Translation> translations = [
|
||||
"restart": "Uruchom ponownie",
|
||||
"downloadInProgress": "Pobieranie w toku",
|
||||
"downloadFinish": "Pobieranie zakończone",
|
||||
"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"
|
||||
"upToDate": "Wszystko jest aktualne"
|
||||
}),
|
||||
Translation(language: "CN", data: {
|
||||
"visitTitle": "旅游清单",
|
||||
@ -277,17 +207,7 @@ List<Translation> translations = [
|
||||
"restart": "重新开始",
|
||||
"downloadInProgress": "下载中",
|
||||
"downloadFinish": "下载完成",
|
||||
"upToDate": "已是最新",
|
||||
"weather.hourly": "接下来的几个小时",
|
||||
"weather.nextdays": "未来几天",
|
||||
"agenda.all": "全部",
|
||||
"monday": "星期一",
|
||||
"tuesday": "星期二",
|
||||
"wednesday": "星期三",
|
||||
"thursday": "星期四",
|
||||
"friday": "星期五",
|
||||
"saturday": "星期六",
|
||||
"sunday": "星期日"
|
||||
"upToDate": "已是最新"
|
||||
}),
|
||||
Translation(language: "UK", data: {
|
||||
"visitTitle": "Список турів",
|
||||
@ -313,17 +233,7 @@ List<Translation> translations = [
|
||||
"restart": "Перезапустіть",
|
||||
"downloadInProgress": "Завантаження триває",
|
||||
"downloadFinish": "Завантаження завершено",
|
||||
"upToDate": "Все актуально",
|
||||
"weather.hourly": "Наступні години",
|
||||
"weather.nextdays": "Наступні дні",
|
||||
"agenda.all": "все",
|
||||
"monday": "Понеділок",
|
||||
"tuesday": "Вівторок",
|
||||
"wednesday": "Середа",
|
||||
"thursday": "Четвер",
|
||||
"friday": "П'ятниця",
|
||||
"saturday": "Субота",
|
||||
"sunday": "Неділя"
|
||||
"upToDate": "Все актуально"
|
||||
}),
|
||||
Translation(language: "AR", data: {
|
||||
"visitTitle": "قائمة الجولات",
|
||||
@ -349,16 +259,6 @@ List<Translation> translations = [
|
||||
"restart": "إعادة تشغيل",
|
||||
"downloadInProgress": "جارٍ التنزيل",
|
||||
"downloadFinish": "اكتمل التنزيل",
|
||||
"upToDate": "كل شيء محدث",
|
||||
"weather.hourly": "الساعات القادمة",
|
||||
"weather.nextdays": "الايام القادمة",
|
||||
"agenda.all": "الجميع",
|
||||
"monday": "الإثنين",
|
||||
"tuesday": "الثلاثاء",
|
||||
"wednesday": "الأربعاء",
|
||||
"thursday": "الخميس",
|
||||
"friday": "الجمعة",
|
||||
"saturday": "السبت",
|
||||
"sunday": "الأحد"
|
||||
"upToDate": "كل شيء محدث"
|
||||
}),
|
||||
];
|
||||
@ -3,9 +3,7 @@
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
# Except for application packages
|
||||
pubspec.lock
|
||||
pubspec.lock # Except for application packages
|
||||
|
||||
doc/api/
|
||||
|
||||
@ -3,56 +3,48 @@
|
||||
README.md
|
||||
analysis_options.yaml
|
||||
doc/AgendaDTO.md
|
||||
doc/AgendaDTOAllOfAgendaMapProvider.md
|
||||
doc/ArticleDTO.md
|
||||
doc/AuthenticationApi.md
|
||||
doc/CategorieDTO.md
|
||||
doc/ConfigurationApi.md
|
||||
doc/ConfigurationDTO.md
|
||||
doc/ContentDTO.md
|
||||
doc/ContentDTOResource.md
|
||||
doc/ContentGeoPoint.md
|
||||
doc/DeviceApi.md
|
||||
doc/DeviceDTO.md
|
||||
doc/DeviceDetailDTO.md
|
||||
doc/DeviceDetailDTOAllOf.md
|
||||
doc/ExportConfigurationDTO.md
|
||||
doc/GeoPoint.md
|
||||
doc/ExportConfigurationDTOAllOf.md
|
||||
doc/GeoPointDTO.md
|
||||
doc/GeoPointSectionMap.md
|
||||
doc/GeoPointDTOCategorie.md
|
||||
doc/Instance.md
|
||||
doc/InstanceApi.md
|
||||
doc/InstanceDTO.md
|
||||
doc/LevelDTO.md
|
||||
doc/LoginDTO.md
|
||||
doc/MapDTO.md
|
||||
doc/MapDTOAllOfMapProvider.md
|
||||
doc/MapDTOAllOfMapType.md
|
||||
doc/MapDTOAllOfMapTypeMapbox.md
|
||||
doc/MapDTOMapProvider.md
|
||||
doc/MapDTOMapType.md
|
||||
doc/MapDTOMapTypeMapbox.md
|
||||
doc/MapProvider.md
|
||||
doc/MapTypeApp.md
|
||||
doc/MapTypeMapBox.md
|
||||
doc/MenuDTO.md
|
||||
doc/OrderedTranslationAndResourceDTO.md
|
||||
doc/PDFFileDTO.md
|
||||
doc/PdfDTO.md
|
||||
doc/PlayerMessageDTO.md
|
||||
doc/PuzzleDTO.md
|
||||
doc/PuzzleDTOAllOfPuzzleImage.md
|
||||
doc/PuzzleDTOImage.md
|
||||
doc/QuestionDTO.md
|
||||
doc/QuestionDTOImageBackgroundResourceType.md
|
||||
doc/QuizDTO.md
|
||||
doc/Resource.md
|
||||
doc/QuizzDTO.md
|
||||
doc/QuizzDTOBadLevel.md
|
||||
doc/ResourceApi.md
|
||||
doc/ResourceDTO.md
|
||||
doc/ResourceType.md
|
||||
doc/ResponseDTO.md
|
||||
doc/Section.md
|
||||
doc/SectionApi.md
|
||||
doc/SectionDTO.md
|
||||
doc/SectionMap.md
|
||||
doc/SectionMapAllOfMapMapProvider.md
|
||||
doc/SectionMapAllOfMapMapType.md
|
||||
doc/SectionMapAllOfMapResource.md
|
||||
doc/SectionMapAllOfMapTypeMapbox.md
|
||||
doc/SectionMapApi.md
|
||||
doc/SectionQuizApi.md
|
||||
doc/SectionType.md
|
||||
doc/SliderDTO.md
|
||||
doc/TokenDTO.md
|
||||
@ -72,8 +64,6 @@ lib/api/device_api.dart
|
||||
lib/api/instance_api.dart
|
||||
lib/api/resource_api.dart
|
||||
lib/api/section_api.dart
|
||||
lib/api/section_map_api.dart
|
||||
lib/api/section_quiz_api.dart
|
||||
lib/api/user_api.dart
|
||||
lib/api_client.dart
|
||||
lib/api_exception.dart
|
||||
@ -84,48 +74,42 @@ lib/auth/http_basic_auth.dart
|
||||
lib/auth/http_bearer_auth.dart
|
||||
lib/auth/oauth.dart
|
||||
lib/model/agenda_dto.dart
|
||||
lib/model/agenda_dto_all_of_agenda_map_provider.dart
|
||||
lib/model/article_dto.dart
|
||||
lib/model/categorie_dto.dart
|
||||
lib/model/configuration_dto.dart
|
||||
lib/model/content_dto.dart
|
||||
lib/model/content_dto_resource.dart
|
||||
lib/model/content_geo_point.dart
|
||||
lib/model/device_detail_dto.dart
|
||||
lib/model/device_detail_dto_all_of.dart
|
||||
lib/model/device_dto.dart
|
||||
lib/model/export_configuration_dto.dart
|
||||
lib/model/geo_point.dart
|
||||
lib/model/export_configuration_dto_all_of.dart
|
||||
lib/model/geo_point_dto.dart
|
||||
lib/model/geo_point_section_map.dart
|
||||
lib/model/geo_point_dto_categorie.dart
|
||||
lib/model/instance.dart
|
||||
lib/model/instance_dto.dart
|
||||
lib/model/level_dto.dart
|
||||
lib/model/login_dto.dart
|
||||
lib/model/map_dto.dart
|
||||
lib/model/map_dto_all_of_map_provider.dart
|
||||
lib/model/map_dto_all_of_map_type.dart
|
||||
lib/model/map_dto_all_of_map_type_mapbox.dart
|
||||
lib/model/map_dto_map_provider.dart
|
||||
lib/model/map_dto_map_type.dart
|
||||
lib/model/map_dto_map_type_mapbox.dart
|
||||
lib/model/map_provider.dart
|
||||
lib/model/map_type_app.dart
|
||||
lib/model/map_type_map_box.dart
|
||||
lib/model/menu_dto.dart
|
||||
lib/model/ordered_translation_and_resource_dto.dart
|
||||
lib/model/pdf_dto.dart
|
||||
lib/model/pdf_file_dto.dart
|
||||
lib/model/player_message_dto.dart
|
||||
lib/model/puzzle_dto.dart
|
||||
lib/model/puzzle_dto_all_of_puzzle_image.dart
|
||||
lib/model/puzzle_dto_image.dart
|
||||
lib/model/question_dto.dart
|
||||
lib/model/question_dto_image_background_resource_type.dart
|
||||
lib/model/quiz_dto.dart
|
||||
lib/model/resource.dart
|
||||
lib/model/quizz_dto.dart
|
||||
lib/model/quizz_dto_bad_level.dart
|
||||
lib/model/resource_dto.dart
|
||||
lib/model/resource_type.dart
|
||||
lib/model/response_dto.dart
|
||||
lib/model/section.dart
|
||||
lib/model/section_dto.dart
|
||||
lib/model/section_map.dart
|
||||
lib/model/section_map_all_of_map_map_provider.dart
|
||||
lib/model/section_map_all_of_map_map_type.dart
|
||||
lib/model/section_map_all_of_map_resource.dart
|
||||
lib/model/section_map_all_of_map_type_mapbox.dart
|
||||
lib/model/section_type.dart
|
||||
lib/model/slider_dto.dart
|
||||
lib/model/token_dto.dart
|
||||
@ -137,3 +121,5 @@ lib/model/video_dto.dart
|
||||
lib/model/weather_dto.dart
|
||||
lib/model/web_dto.dart
|
||||
pubspec.yaml
|
||||
test/pdf_file_dto_test.dart
|
||||
test/weather_dto_test.dart
|
||||
1
manager_api/.openapi-generator/VERSION
Normal file
1
manager_api/.openapi-generator/VERSION
Normal file
@ -0,0 +1 @@
|
||||
unset
|
||||
192
manager_api/README.md
Normal file
192
manager_api/README.md
Normal file
@ -0,0 +1,192 @@
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user