Puzzle working + slider wip + misc

This commit is contained in:
Thomas Fransolet 2025-06-12 17:27:53 +02:00
parent bddde86974
commit 7aca0638ce
17 changed files with 1888 additions and 2 deletions

View File

@ -0,0 +1,265 @@
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";
@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();
}
@override
void dispose() {
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();
return FloatingActionButton(
backgroundColor: Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)).withValues(alpha: 0.7),
onPressed: () 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: isplaying ? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.pause),
Text(currentpostlabel),
],
) : audioplayed ? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.play_arrow),
Text(currentpostlabel),
],
): const Icon(Icons.play_arrow),
/*Column(
children: [
//Text(currentpostlabel, style: const TextStyle(fontSize: 25)),
Wrap(
spacing: 10,
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: kSecondColor, // Background color
),
onPressed: () async {
if(!isplaying && !audioplayed){
//player.play(BytesSource(audiobytes));
await player.setAudioSource(LoadedSource(audiobytes));
player.play();
setState(() {
isplaying = true;
audioplayed = true;
});
}else if(audioplayed && !isplaying){
//player.resume();
player.play();
setState(() {
isplaying = true;
audioplayed = true;
});
}else{
player.pause();
setState(() {
isplaying = false;
});
}
},
icon: Icon(isplaying?Icons.pause:Icons.play_arrow),
//label:Text(isplaying?TranslationHelper.getFromLocale("pause", appContext.getContext()):TranslationHelper.getFromLocale("play", appContext.getContext()))
),
/*ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: kSecondColor, // Background color
),
onPressed: () async {
player.stop();
player.seek(const Duration(seconds: 0));
setState(() {
isplaying = false;
audioplayed = false;
currentpostlabel = "00:00";
});
},
icon: const Icon(Icons.stop),
//label: Text(TranslationHelper.getFromLocale("stop", appContext.getContext()))
),*/
],
)
],
),*/
);
}
}
// Feed your own stream of bytes into the player
class LoadedSource extends StreamAudioSource {
final List<int> bytes;
LoadedSource(this.bytes);
@override
Future<StreamAudioResponse> request([int? start, int? end]) async {
start ??= 0;
end ??= bytes.length;
return StreamAudioResponse(
sourceLength: bytes.length,
contentLength: end - start,
offset: start,
stream: Stream.value(bytes.sublist(start, end)),
contentType: 'audio/mpeg',
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,6 +23,8 @@ class VisitAppContext with ChangeNotifier {
bool isScanBeaconAlreadyAllowed = false; bool isScanBeaconAlreadyAllowed = false;
bool isMaximizeTextSize = false; bool isMaximizeTextSize = false;
Size? puzzleSize;
List<ResourceModel> audiosNotWorking = []; List<ResourceModel> audiosNotWorking = [];
bool? isAdmin = false; bool? isAdmin = false;

View File

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

View File

@ -0,0 +1,93 @@
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(
//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(
//color: Colors.green,
height: size.height *0.3,
width: size.width *0.5,
child: Center(
child: Align(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: HtmlWidget(
translationAndResourceDTO.value!,
customStylesBuilder: (element) {
return {'text-align': 'center', 'font-family': "Roboto"};
},
textStyle: const TextStyle(fontSize: kDescriptionSize),
),/*Text(
resourceDTO.label == null ? "" : resourceDTO.label,
style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)),*/
),
),
),
),
],
),
),
/*actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: AlignmentDirectional.bottomEnd,
child: Container(
width: 175,
height: 70,
child: RoundedButton(
text: "Merci",
icon: Icons.undo,
color: kSecondGrey,
press: () {
Navigator.of(context).pop();
},
fontSize: 20,
),
),
),
),
],
),
],*/
), context: context
);
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,336 @@
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 visitAppContex = appContext.getContext() as VisitAppContext;
Color? primaryColor = visitAppContex.configuration!.primaryColor != null ? Color(int.parse(visitAppContex.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : null;
configurationDTO = appContext.getContext().configuration;
return Stack(
children: [
if(sliderDTO.contents != null && sliderDTO.contents!.isNotEmpty)
CarouselSlider(
carouselController: sliderController,
options: CarouselOptions(
onPageChanged: (int index, CarouselPageChangedReason reason) {
currentIndex.value = index + 1;
},
height: MediaQuery.of(context).size.height * 1.0,
enlargeCenterPage: false,
reverse: false,
),
items: sliderDTO.contents!.map<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: Colors.green,
//color: configurationDTO.imageId == null ? configurationDTO.secondaryColor != null ? new Color(int.parse(configurationDTO.secondaryColor!.split('(0x')[1].split(')')[0], radix: 16)): kBackgroundGrey : null,
borderRadius: BorderRadius.circular(visitAppContex.configuration!.roundedValue?.toDouble() ?? 10.0),
//border: Border.all(width: 0.3, color: kSecondGrey),
),
child: Column(
//crossAxisAlignment: CrossAxisAlignment.center,
//mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
color: Colors.orange,
height: MediaQuery.of(context).size.height * 0.6,
width: MediaQuery.of(context).size.width * 0.72,
/*decoration: BoxDecoration(
color: kBackgroundLight,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(20.0),
/*image: i.source_ != null ? new DecorationImage(
fit: BoxFit.cover,
image: new NetworkImage(
i.source_,
),
): null,*/
boxShadow: [
BoxShadow(
color: kBackgroundSecondGrey,
spreadRadius: 0.5,
blurRadius: 5,
offset: Offset(0, 1.5), // changes position of shadow
),
],
),*/
child: Stack(
children: [
getElementForResource(appContext, i),
Positioned(
bottom: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: HtmlWidget(
i.title!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.title!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "",
textStyle: const TextStyle(fontSize: kTitleSize, color: kBackgroundLight),
),
)
)
]
),/**/
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Container(
height: MediaQuery.of(context).size.height *0.25,
width: MediaQuery.of(context).size.width *0.7,
decoration: BoxDecoration(
color: Colors.blue,// kBackgroundLight,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(visitAppContex.configuration!.roundedValue?.toDouble() ?? 10.0),
boxShadow: const [
BoxShadow(
color: kBackgroundSecondGrey,
spreadRadius: 0.3,
blurRadius: 4,
offset: Offset(0, 2), // changes position of shadow
),
],
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: HtmlWidget(
i.description!.where((translation) => translation.language == appContext.getContext().language).firstOrNull?.value != null ? i.description!.firstWhere((translation) => translation.language == appContext.getContext().language).value! : "",
textStyle: const TextStyle(fontSize: kDescriptionSize),
customStylesBuilder: (element) {
return {'text-align': 'center', 'font-family': "Roboto"};
},
),
),
),
),
),
),
],
)
);
},
);
}).toList(),
),
/*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: const ExpandingDotsEffect(activeDotColor: kMainColor),
);
/*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: kBackgroundSecondGrey,
shape: BoxShape.rectangle,
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: kBackgroundSecondGrey,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 15.0),
),
);
break;
case ResourceType.Video:
case ResourceType.VideoUrl:
case ResourceType.Audio:
widgetToInclude = Container(
decoration: BoxDecoration(
//color: kBackgroundSecondGrey,
//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,
color: Colors.yellow,
child: AspectRatio(
aspectRatio: 16 / 9,
child: ClipRect(
child: widgetToInclude,
),
),
),
);
}
}

View File

@ -15,8 +15,10 @@ import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart'; import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/PDF/pdf_view.dart'; import 'package:mymuseum_visitapp/Screens/Sections/PDF/pdf_view.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/Quiz/quizz_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Video/video_view.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Slider/slider_view.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Video/video_page.dart';
import 'package:mymuseum_visitapp/Screens/Sections/Web/web_page.dart'; import 'package:mymuseum_visitapp/Screens/Sections/Web/web_page.dart';
import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/client.dart'; import 'package:mymuseum_visitapp/client.dart';
@ -64,7 +66,7 @@ class _SectionPageState extends State<SectionPage> {
return Scaffold( return Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
appBar: test!.type != SectionType.Quiz && test.type != SectionType.Article && test.type != SectionType.Web && test.type != SectionType.Pdf && test.type != SectionType.Video ? CustomAppBar( appBar: test!.type != SectionType.Quiz && test.type != SectionType.Article && test.type != SectionType.Web && test.type != SectionType.Pdf && test.type != SectionType.Video && test.type != SectionType.Puzzle && test.type != SectionType.Slider ? CustomAppBar(
title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "", title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "",
isHomeButton: false, isHomeButton: false,
) : null, ) : null,
@ -91,6 +93,12 @@ class _SectionPageState extends State<SectionPage> {
case SectionType.Video: case SectionType.Video:
VideoDTO videoDTO = VideoDTO.fromJson(sectionResult)!; VideoDTO videoDTO = VideoDTO.fromJson(sectionResult)!;
return VideoPage(section: videoDTO); return VideoPage(section: videoDTO);
case SectionType.Puzzle:
PuzzleDTO puzzleDTO = PuzzleDTO.fromJson(sectionResult)!;
return PuzzlePage(section: puzzleDTO);
case SectionType.Slider:
SliderDTO sliderDTO = SliderDTO.fromJson(sectionResult)!;
return SliderPage(section: sliderDTO);
default: default:
return const Center(child: Text("Unsupported type")); return const Center(child: Text("Unsupported type"));
} }

View File

@ -1081,6 +1081,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
smooth_page_indicator:
dependency: "direct main"
description:
name: smooth_page_indicator
sha256: b21ebb8bc39cf72d11c7cfd809162a48c3800668ced1c9da3aade13a32cf6c1c
url: "https://pub.dev"
source: hosted
version: "1.2.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:

View File

@ -61,6 +61,8 @@ dependencies:
flutter_beacon: ^0.5.1 #not in web flutter_beacon: ^0.5.1 #not in web
flutter_staggered_grid_view: ^0.7.0 flutter_staggered_grid_view: ^0.7.0
smooth_page_indicator: ^1.2.1
manager_api_new: manager_api_new:
path: manager_api_new path: manager_api_new
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.