update logic from resource in DB to resources in file + others

This commit is contained in:
Thomas Fransolet 2024-07-07 20:32:34 +02:00
parent 14ba287872
commit fb8b564456
25 changed files with 811 additions and 306 deletions

View File

@ -21,7 +21,7 @@ class AdminPopup extends StatefulWidget {
class _AdminPopupState extends State<AdminPopup> { class _AdminPopupState extends State<AdminPopup> {
final TextEditingController _controller = TextEditingController(); final TextEditingController _controller = TextEditingController();
bool isPasswordOk = false; bool isPasswordOk = false;
String password = "FORT2023!"; String password = "ADMIN2024!"; //FORT2023!
VisitAppContext? visitAppContext; VisitAppContext? visitAppContext;
@override @override

View File

@ -36,7 +36,7 @@ class BuilderImageSlider extends StatelessWidget {
//width: size.width * 0.95, //width: size.width * 0.95,
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(15.0),
child: Image.memory(base64Decode(i.data!))/*PhotoView( child: Image.memory(base64Decode(i.path!))/*PhotoView(
imageProvider: Image.memory(base64Decode(i!.data!)).image, imageProvider: Image.memory(base64Decode(i!.data!)).image,
minScale: PhotoViewComputedScale.contained * 0.8, minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.contained * 3.0, maxScale: PhotoViewComputedScale.contained * 3.0,

View File

@ -28,6 +28,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context); final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext(); VisitAppContext visitAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
//final notchInset = MediaQuery.of(context).padding; //final notchInset = MediaQuery.of(context).padding;
return AppBar( return AppBar(
@ -45,7 +46,17 @@ class _CustomAppBarState extends State<CustomAppBar> {
), context: context ), context: context
); );
}, },
child: HtmlWidget(widget.title, textStyle: TextStyle(color: Colors.white),), child: SizedBox(
width: widget.isHomeButton ? size.width * 0.8 : null,
child: HtmlWidget(
widget.title,
textStyle: const TextStyle(color: Colors.white),
customStylesBuilder: (element)
{
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2",};
}
),
),
), ),
centerTitle: true, centerTitle: true,
leading: widget.isHomeButton ? IconButton( leading: widget.isHomeButton ? IconButton(
@ -73,7 +84,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
}, },
child: SizedBox( child: SizedBox(
width: 50, width: 50,
child: visitAppContext.isMaximizeTextSize ? const Icon(Icons.text_fields) : const Icon(Icons.format_size) child: visitAppContext.isMaximizeTextSize ? const Icon(Icons.text_fields, color: Colors.white) : const Icon(Icons.format_size, color: Colors.white)
), ),
), ),
Padding( Padding(
@ -97,8 +108,10 @@ class _CustomAppBarState extends State<CustomAppBar> {
Color(0xFF7633B8), Color(0xFF7633B8),
Color(0xFF6528B6), Color(0xFF6528B6),
Color(0xFF6025B6)*/ Color(0xFF6025B6)*/
Color(0xFFf6b3c4),
kMainColor1, kMainColor1,
kMainColor2, kMainColor2,
], ],
), ),
), ),

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
@ -10,7 +11,7 @@ import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
void showImagePopup(ContentDTO contentDTO, ResourceModel resourceModel, AppContext appContext, BuildContext context, Size size) { void showImagePopup(ContentDTO contentDTO, ResourceModel resourceModel, AppContext appContext, BuildContext context, Size size, File? resourceModelFile) {
showDialog( showDialog(
builder: (BuildContext context) => AlertDialog( builder: (BuildContext context) => AlertDialog(
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
@ -33,8 +34,8 @@ void showImagePopup(ContentDTO contentDTO, ResourceModel resourceModel, AppConte
child: Padding( child: Padding(
padding: const EdgeInsets.only(left:8.0, right: 8.0, bottom: 8.0, top: 8.0), padding: const EdgeInsets.only(left:8.0, right: 8.0, bottom: 8.0, top: 8.0),
child: PhotoView( child: PhotoView(
imageProvider: (appContext.getContext() as VisitAppContext).configuration!.isOffline! ? imageProvider: (appContext.getContext() as VisitAppContext).configuration!.isOffline! && resourceModelFile != null ?
Image.memory(base64Decode(resourceModel.data!)).image : Image.file(resourceModelFile).image : // GET FROM FILE
Image.network(resourceModel.source!).image, Image.network(resourceModel.source!).image,
minScale: PhotoViewComputedScale.contained * 1.0, minScale: PhotoViewComputedScale.contained * 1.0,
maxScale: PhotoViewComputedScale.contained * 3.0, maxScale: PhotoViewComputedScale.contained * 3.0,

View File

@ -5,6 +5,7 @@ import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Components/ShowImagePopup.dart'; import 'package:mymuseum_visitapp/Components/ShowImagePopup.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart'; 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/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -69,18 +70,25 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
return Builder( return Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
//print(widget.imagesDTO[currentIndex-1]); //print(widget.imagesDTO[currentIndex-1]);
return FutureBuilder(
future: ApiService.getResource(appContext, visitAppContext.configuration!, i!.id!),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 5.0), padding: const EdgeInsets.only(top: 5.0),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
showImagePopup(widget.contentsDTO[currentIndex.value-1]!, i!, appContext, context, size); showImagePopup(widget.contentsDTO[currentIndex.value-1]!, i, appContext, context, size, snapshot.data);
}, },
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(15.0),
child: visitAppContext.configuration!.isOffline! ? child: visitAppContext.configuration!.isOffline! ?
Image.memory(base64Decode(i!.data!)) : snapshot.data != null ?
Image.file(
snapshot.data!,
fit: BoxFit.cover,
) : null :
Image.network( Image.network(
i!.source!, i.source!,
loadingBuilder: (BuildContext context, Widget child, loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) { ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) { if (loadingProgress == null) {
@ -109,6 +117,8 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
), ),
), ),
); );
}
);
}, },
); );
}).toList(), }).toList(),

View File

@ -27,6 +27,7 @@ class DatabaseHelper {
static const columnLabel = 'label'; static const columnLabel = 'label';
static const columnId = 'id'; static const columnId = 'id';
static const columnInstanceId = 'instanceId'; static const columnInstanceId = 'instanceId';
static const columnPath = 'path';
static const columnData = 'data'; static const columnData = 'data';
static const columnType = 'type'; static const columnType = 'type';
static const columnDateCreation = 'dateCreation'; static const columnDateCreation = 'dateCreation';
@ -204,7 +205,7 @@ class DatabaseHelper {
db.execute(''' db.execute('''
CREATE TABLE $resourcesTable ( CREATE TABLE $resourcesTable (
$columnId TEXT NOT NULL PRIMARY KEY, $columnId TEXT NOT NULL PRIMARY KEY,
$columnData TEXT NOT NULL, $columnPath TEXT NOT NULL,
$columnSource TEXT NOT NULL, $columnSource TEXT NOT NULL,
$columnType INT NOT NULL $columnType INT NOT NULL
) )
@ -406,9 +407,10 @@ class DatabaseHelper {
} }
ResourceModel getResourceFromDB(dynamic element) { ResourceModel getResourceFromDB(dynamic element) {
// Here retrieve file from path ?
return ResourceModel( return ResourceModel(
id: element["id"], id: element["id"],
data: element["data"], path: element["path"],
source: element["source"], source: element["source"],
type: ResourceType.values[element["type"]] type: ResourceType.values[element["type"]]
); );

View File

@ -1,6 +1,5 @@
import 'package:manager_api/api.dart'; import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Models/visitContext.dart'; import 'package:mymuseum_visitapp/Models/visitContext.dart';
import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/translations.dart'; import 'package:mymuseum_visitapp/translations.dart';
class TranslationHelper { class TranslationHelper {

View File

@ -3,17 +3,17 @@ import 'package:manager_api/api.dart';
class ResourceModel { class ResourceModel {
String? id = ""; String? id = "";
String? data = ""; String? path = "";
String? source = ""; String? source = "";
String? label = ""; String? label = "";
ResourceType? type; ResourceType? type;
ResourceModel({this.id, this.data, this.source, this.type}); ResourceModel({this.id, this.path, this.source, this.type});
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'id': id, 'id': id,
'data': data, 'path': path,
'source': source, 'source': source,
'type': type?.value 'type': type?.value
}; };
@ -22,7 +22,7 @@ class ResourceModel {
factory ResourceModel.fromJson(Map<String, dynamic> json) { factory ResourceModel.fromJson(Map<String, dynamic> json) {
return ResourceModel( return ResourceModel(
id: json['id'] as String, id: json['id'] as String,
data: json['data'] as String, path: json['path'] as String,
source: json['source'] as String, source: json['source'] as String,
type: json['type'] as ResourceType type: json['type'] as ResourceType
); );
@ -30,6 +30,6 @@ class ResourceModel {
@override @override
String toString() { String toString() {
return 'ResourceModel{id: $id, type: $type, source: $source, data: $data, label: $label}'; return 'ResourceModel{id: $id, type: $type, source: $source, path: $path, label: $label}';
} }
} }

View File

@ -3,13 +3,15 @@ import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Models/articleRead.dart'; import 'package:mymuseum_visitapp/Models/articleRead.dart';
import 'package:mymuseum_visitapp/Models/beaconSection.dart'; import 'package:mymuseum_visitapp/Models/beaconSection.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart'; import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/client.dart';
class VisitAppContext with ChangeNotifier {
Client clientAPI = Client("https://api.myinfomate.be"); // Replace by https://api.mymuseum.be //http://192.168.31.140:8089
class VisitAppContext with ChangeNotifier{
String? id = ""; String? id = "";
String? language = ""; String? language = "";
String? instanceId = "63514fd67ed8c735aaa4b8f2"; // 63514fd67ed8c735aaa4b8f2 MyInfoMate test instance -- Fort de Saint-Héribert Mymuseum instance id : 633ee379d9405f32f166f047 // 63514fd67ed8c735aaa4b8f1 Mymuseum test String? instanceId = "63514fd67ed8c735aaa4b8f2"; // 63514fd67ed8c735aaa4b8f2 MyInfoMate test instance -- Fort de Saint-Héribert Mymuseum instance id : 633ee379d9405f32f166f047 // 63514fd67ed8c735aaa4b8f1 Mymuseum test
List<ConfigurationDTO>? configurations; List<ConfigurationDTO>? configurations;
ConfigurationDTO? configuration; ConfigurationDTO? configuration;
List<String?>? sectionIds; // Use to valid QR code found List<String?>? sectionIds; // Use to valid QR code found
@ -26,6 +28,8 @@ class VisitAppContext with ChangeNotifier{
bool? isAdmin = false; bool? isAdmin = false;
bool? isAllLanguages = false; bool? isAllLanguages = false;
String? localPath;
VisitAppContext({this.language, this.id, this.configuration, this.isAdmin, this.isAllLanguages, this.instanceId}); VisitAppContext({this.language, this.id, this.configuration, this.isAdmin, this.isAllLanguages, this.instanceId});
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -18,6 +19,7 @@ import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/client.dart'; import 'package:mymuseum_visitapp/client.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'audio_player_floating.dart'; import 'audio_player_floating.dart';
@ -37,8 +39,8 @@ class _ArticlePageState extends State<ArticlePage> {
List<ResourceModel?> resourcesModel = <ResourceModel?>[]; List<ResourceModel?> resourcesModel = <ResourceModel?>[];
ResourceModel? audioResourceModel; ResourceModel? audioResourceModel;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
late Uint8List audiobytes; late File audioFile;
VisitAppContext? visitAppContext; late VisitAppContext visitAppContext;
@override @override
void initState() { void initState() {
@ -48,7 +50,7 @@ class _ArticlePageState extends State<ArticlePage> {
@override @override
void dispose() { void dispose() {
visitAppContext!.isContentCurrentlyShown = false; visitAppContext.isContentCurrentlyShown = false;
super.dispose(); super.dispose();
} }
@ -63,12 +65,12 @@ class _ArticlePageState extends State<ArticlePage> {
return Scaffold( return Scaffold(
key: _scaffoldKey, key: _scaffoldKey,
appBar: CustomAppBar( appBar: CustomAppBar(
title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext!) : "", title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "",
isHomeButton: false, isHomeButton: false,
isTextSizeButton: true, isTextSizeButton: true,
), ),
body: FutureBuilder( body: FutureBuilder(
future: getArticle(appContext, appContext.clientAPI, widget.articleId, false), future: getArticle(appContext, visitAppContext.clientAPI, widget.articleId, false),
builder: (context, AsyncSnapshot<dynamic> snapshot) { builder: (context, AsyncSnapshot<dynamic> snapshot) {
if(articleDTO != null && sectionDTO != null) { if(articleDTO != null && sectionDTO != null) {
if(size.height > size.width) { if(size.height > size.width) {
@ -89,7 +91,7 @@ class _ArticlePageState extends State<ArticlePage> {
], ],
); );
} else { } else {
return Container( return SizedBox(
height: size.height, height: size.height,
width: size.width, width: size.width,
child: Column( child: Column(
@ -120,11 +122,11 @@ class _ArticlePageState extends State<ArticlePage> {
} }
), ),
floatingActionButton: FutureBuilder( floatingActionButton: FutureBuilder(
future: getArticle(appContext, appContext.clientAPI, widget.articleId, true), future: getArticle(appContext, visitAppContext.clientAPI, widget.articleId, true),
builder: (context, AsyncSnapshot<dynamic> snapshot) { builder: (context, AsyncSnapshot<dynamic> snapshot) {
return Padding( return Padding(
padding: EdgeInsets.only(right: 0, top: 0), //size.height*0.1 padding: EdgeInsets.only(right: 0, top: 0), //size.height*0.1
child: audioResourceModel != null && audioResourceModel!.source != null ? AudioPlayerFloatingContainer(file: null, audioBytes: null, resourceURl: audioResourceModel!.source!, isAuto: articleDTO!.isReadAudioAuto!) : null, child: audioResourceModel != null && audioResourceModel!.source != null ? AudioPlayerFloatingContainer(file: audioFile, resourceURl: "", isAuto: articleDTO!.isReadAudioAuto!) : null,
); );
} }
), ),
@ -212,6 +214,10 @@ class _ArticlePageState extends State<ArticlePage> {
child: HtmlWidget( child: HtmlWidget(
TranslationHelper.get(articleDTO!.content, appContext.getContext()), TranslationHelper.get(articleDTO!.content, appContext.getContext()),
textStyle: TextStyle(fontSize: (appContext.getContext() as VisitAppContext).isMaximizeTextSize ? kArticleContentBiggerSize : kArticleContentSize), textStyle: TextStyle(fontSize: (appContext.getContext() as VisitAppContext).isMaximizeTextSize ? kArticleContentBiggerSize : kArticleContentSize),
customStylesBuilder: (element)
{
return {'font-family': "Roboto"};
}
//textAlign: TextAlign.left, //textAlign: TextAlign.left,
), ),
), ),
@ -245,7 +251,7 @@ class _ArticlePageState extends State<ArticlePage> {
child: HtmlWidget( child: HtmlWidget(
TranslationHelper.get(articleDTO!.content, appContext.getContext()), TranslationHelper.get(articleDTO!.content, appContext.getContext()),
//textAlign: TextAlign.left, //textAlign: TextAlign.left,
textStyle: TextStyle(fontSize: (appContext.getContext() as VisitAppContext).isMaximizeTextSize ? kArticleContentBiggerSize : kArticleContentSize) textStyle: TextStyle(fontSize: (appContext.getContext() as VisitAppContext).isMaximizeTextSize ? kArticleContentBiggerSize : kArticleContentSize, fontFamily: "Arial"),
), ),
), ),
) )
@ -270,9 +276,9 @@ class _ArticlePageState extends State<ArticlePage> {
try { try {
SectionRead articleRead = SectionRead(id: sectionDTO!.id!, readTime: DateTime.now().millisecondsSinceEpoch); SectionRead articleRead = SectionRead(id: sectionDTO!.id!, readTime: DateTime.now().millisecondsSinceEpoch);
await DatabaseHelper.instance.insert(DatabaseTableType.articleRead, articleRead.toMap()); await DatabaseHelper.instance.insert(DatabaseTableType.articleRead, articleRead.toMap());
visitAppContext!.readSections.add(articleRead); visitAppContext.readSections.add(articleRead);
appContext.setContext(visitAppContext!); appContext.setContext(visitAppContext);
} catch (e) { } catch (e) {
print("DATABASE ERROR ARTICLEREAD"); print("DATABASE ERROR ARTICLEREAD");
print(e); print(e);
@ -289,9 +295,9 @@ class _ArticlePageState extends State<ArticlePage> {
try { try {
SectionRead articleRead = SectionRead(id: sectionDTO!.id!, readTime: DateTime.now().millisecondsSinceEpoch); SectionRead articleRead = SectionRead(id: sectionDTO!.id!, readTime: DateTime.now().millisecondsSinceEpoch);
await DatabaseHelper.instance.insert(DatabaseTableType.articleRead, articleRead.toMap()); await DatabaseHelper.instance.insert(DatabaseTableType.articleRead, articleRead.toMap());
visitAppContext!.readSections.add(articleRead); visitAppContext.readSections.add(articleRead);
appContext.setContext(visitAppContext!); appContext.setContext(visitAppContext);
} catch (e) { } catch (e) {
print("DATABASE ERROR ARTICLEREAD"); print("DATABASE ERROR ARTICLEREAD");
print(e); print(e);
@ -311,32 +317,50 @@ class _ArticlePageState extends State<ArticlePage> {
{ {
try{ try{
// OFFLINE // OFFLINE
List<Map<String, dynamic>> ressourceTest = await DatabaseHelper List<Map<String, dynamic>> ressourceTest = await DatabaseHelper
.instance.queryWithColumnId( .instance.queryWithColumnId(
DatabaseTableType.resources, audioIdArticle.first.value!); DatabaseTableType.resources, audioIdArticle.first.value!);
if (ressourceTest.isNotEmpty) {
audioResourceModel = DatabaseHelper.instance.getResourceFromDB(ressourceTest.first);
print(audioResourceModel!.id);
if(audioResourceModel!.path != null) {
audioFile = File(audioResourceModel!.path!);
}
} else {
print("EMPTY resourcesModel - first");
}
/*List<Map<String, dynamic>> ressourceTest = await DatabaseHelper
.instance.queryWithColumnId(
DatabaseTableType.resources, audioIdArticle.first.value!);
if (ressourceTest.isNotEmpty) { if (ressourceTest.isNotEmpty) {
audioResourceModel = DatabaseHelper.instance.getResourceFromDB(ressourceTest.first); audioResourceModel = DatabaseHelper.instance.getResourceFromDB(ressourceTest.first);
print(audioResourceModel!.id); print(audioResourceModel!.id);
Uint8List base64String = base64Decode(audioResourceModel!.data!); Uint8List base64String = base64Decode(audioResourceModel!.path!); // TODO get from file
audiobytes = base64String; audiobytes = base64String;
} else { } else {
print("EMPTY resourcesModel - first"); print("EMPTY resourcesModel - first");
} }*/
} catch(e) { } catch(e) {
print("Error in audio loading: " + e.toString()); print("Error in audio loading: " + e.toString());
} }
} }
else else
{ {
// TODO Get file instead.. if exist
ResourceDTO? resourceDTO = await client.resourceApi!.resourceGetDetail(audioIdArticle.first.value!); ResourceDTO? resourceDTO = await client.resourceApi!.resourceGetDetail(audioIdArticle.first.value!);
if(resourceDTO != null && resourceDTO.url != null) {
// ONLINE // ONLINE
ResourceModel? resourceAudioOnline = await ApiService.downloadAudio(client, audioIdArticle.first.value!); //ResourceModel? resourceAudioOnline = await ApiService.downloadAudio(client, resourceDTO.url!, resourceDTO.id!);
if(resourceAudioOnline != null) { ResourceModel resourceAudioOnline = ResourceModel();
resourceAudioOnline.source = resourceDTO!.url; resourceAudioOnline.source = resourceDTO.url;
audioResourceModel = resourceAudioOnline; audioResourceModel = resourceAudioOnline;
Uint8List base64String = base64Decode(resourceAudioOnline.data!);
audiobytes = base64String; /*Uint8List base64String = base64Decode(resourceAudioOnline.path!); // GET FROM FILE
audiobytes = base64String;*/
} else { } else {
print("EMPTY resourcesModel online - audio"); print("EMPTY resourcesModel online - audio");
} }

View File

@ -11,10 +11,9 @@ import 'package:just_audio_cache/just_audio_cache.dart';
class AudioPlayerFloatingContainer extends StatefulWidget { class AudioPlayerFloatingContainer extends StatefulWidget {
const AudioPlayerFloatingContainer({Key? key, required this.file, required this.audioBytes, required this.resourceURl, required this.isAuto}) : super(key: key); const AudioPlayerFloatingContainer({Key? key, required this.file, required this.resourceURl, required this.isAuto}) : super(key: key);
final File? file; final File? file;
final Uint8List? audioBytes;
final String resourceURl; final String resourceURl;
final bool isAuto; final bool isAuto;
@ -36,10 +35,6 @@ class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContai
void initState() { void initState() {
//print("IN INITSTATE AUDDDIOOOO"); //print("IN INITSTATE AUDDDIOOOO");
Future.delayed(Duration.zero, () async { Future.delayed(Duration.zero, () async {
if(widget.audioBytes != null) {
audiobytes = widget.audioBytes!;
}
if(widget.file != null) { if(widget.file != null) {
audiobytes = await fileToUint8List(widget.file!); audiobytes = await fileToUint8List(widget.file!);
} }
@ -174,16 +169,16 @@ class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContai
child: isplaying ? Column( child: isplaying ? Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.pause), const Icon(Icons.pause, color: Colors.white),
Text(currentpostlabel), Text(currentpostlabel, style: const TextStyle(color: Colors.white)),
], ],
) : audioplayed ? Column( ) : audioplayed ? Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.play_arrow), const Icon(Icons.play_arrow, color: Colors.white),
Text(currentpostlabel), Text(currentpostlabel, style: const TextStyle(color: Colors.white)),
], ],
): const Icon(Icons.play_arrow), ): const Icon(Icons.play_arrow, color: Colors.white),
/*Column( /*Column(
children: [ children: [

View File

@ -36,6 +36,8 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
List<String?> alreadyDownloaded = []; List<String?> alreadyDownloaded = [];
VisitAppContext? visitAppContext; VisitAppContext? visitAppContext;
bool isDialogOpen = false;
@override @override
void initState() { void initState() {
configurations = widget.configurations; configurations = widget.configurations;
@ -130,22 +132,21 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
width: size.width * 0.3, width: size.width * 0.3,
child: FutureBuilder( child: FutureBuilder(
future: ApiService.getResource( future: ApiService.getResource(
appContext, configurations[index].imageId!), appContext, configurations[index], configurations[index].imageId!),
builder: builder:
(context, AsyncSnapshot<dynamic> snapshot) { (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == if (snapshot.connectionState ==
ConnectionState.done) { ConnectionState.done) {
return snapshot.data != null return ClipRRect(
? ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20), topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20)), bottomLeft: Radius.circular(20)),
child: snapshot.data.data != null child: snapshot.data != null
? Image.memory( ? Image.file(
base64Decode( snapshot.data!,
snapshot.data.data!), fit: BoxFit.cover,
fit: BoxFit.cover) ):
: Image.network( Image.network(
configurations[index] configurations[index]
.imageSource!, .imageSource!,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -174,8 +175,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
); );
}, },
), ),
) );
: const Text("");
} else if (snapshot.connectionState == } else if (snapshot.connectionState ==
ConnectionState.none) { ConnectionState.none) {
return Text(TranslationHelper.getFromLocale( return Text(TranslationHelper.getFromLocale(
@ -270,7 +270,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
: TranslationHelper.getFromLocale( : TranslationHelper.getFromLocale(
"downloadPromptUpdate", "downloadPromptUpdate",
appContext.getContext()), appContext.getContext()),
style: const TextStyle(color: kMainColor), style: const TextStyle(color: kSecondGrey),
textAlign: TextAlign.center), textAlign: TextAlign.center),
), ),
const SizedBox( const SizedBox(
@ -279,7 +279,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
Text( Text(
TranslationHelper.getFromLocale( TranslationHelper.getFromLocale(
"downloadLanguage", appContext.getContext()), "downloadLanguage", appContext.getContext()),
style: const TextStyle(color: kMainColor), style: const TextStyle(color: kSecondGrey),
textAlign: TextAlign.center), textAlign: TextAlign.center),
const SizedBox( const SizedBox(
height: 25, height: 25,
@ -293,7 +293,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
child: Text( child: Text(
TranslationHelper.getFromLocale( TranslationHelper.getFromLocale(
"close", appContext.getContext()), "close", appContext.getContext()),
style: TextStyle(color: kMainColor)), style: TextStyle(color: kSecondGrey)),
onPressed: () { onPressed: () {
isCancel = true; isCancel = true;
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -303,7 +303,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
child: Text( child: Text(
TranslationHelper.getFromLocale( TranslationHelper.getFromLocale(
"download", appContext.getContext()), "download", appContext.getContext()),
style: TextStyle(color: kMainColor)), style: TextStyle(color: kSecondGrey)),
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -316,7 +316,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
//} //}
if (!isCancel) { if (!isCancel) {
String loadingText = TranslationHelper.getFromLocale( /*String loadingText = TranslationHelper.getFromLocale(
"downloadConfiguration", appContext.getContext()); "downloadConfiguration", appContext.getContext());
showDialog( showDialog(
barrierDismissible: false, barrierDismissible: false,
@ -338,18 +338,40 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
), ),
), ),
); );
}); });*/
var audiosNotWorking = await DownloadConfiguration.download(buildContext, appContext, configuration); // DIALOG HERE
if(!isDialogOpen) {
isDialogOpen = true;
print("C'EST FINI. - nombre d'audios qui ne fonctionnent pas"); var result = await showDialog(
print(audiosNotWorking.length); builder: (BuildContext dialogContext) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))
),
content: Container(
width: 400,
height: 200,
child: DownloadConfigurationWidget(configuration: configuration),
),
actions: <Widget>[],
), context: context
);
isDialogOpen = false;
} else {
print("ALREADY OPEN LAAAA");
}
Navigator.of(context).pop(); //var audiosNotWorking = await DownloadConfiguration.download(buildContext, appContext, configuration);
/*print("C'EST FINI. - nombre d'audios qui ne fonctionnent pas");
print(audiosNotWorking.length);*/
//Navigator.of(context).pop();
VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext; VisitAppContext visitAppContext = appContext.getContext() as VisitAppContext;
visitAppContext.audiosNotWorking = []; visitAppContext.audiosNotWorking = [];
visitAppContext.audiosNotWorking = audiosNotWorking; //visitAppContext.audiosNotWorking = audiosNotWorking;
appContext.setContext(visitAppContext); appContext.setContext(visitAppContext);
} }
} }

View File

@ -36,7 +36,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
List<ConfigurationDTO> configurations = []; List<ConfigurationDTO> configurations = [];
List<String?> alreadyDownloaded = []; List<String?> alreadyDownloaded = [];
VisitAppContext? visitAppContext; late VisitAppContext visitAppContext;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -54,7 +54,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
width: size.width, width: size.width,
height: size.height, height: size.height,
child: FutureBuilder( child: FutureBuilder(
future: getConfigurationsCall(appContext.clientAPI, appContext), future: getConfigurationsCall(visitAppContext.clientAPI, appContext),
builder: (context, AsyncSnapshot<dynamic> snapshot) { builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
configurations = List<ConfigurationDTO>.from(snapshot.data).where((configuration) => configuration.isMobile!).toList(); configurations = List<ConfigurationDTO>.from(snapshot.data).where((configuration) => configuration.isMobile!).toList();

View File

@ -40,7 +40,7 @@ class _QuizzPageState extends State<QuizzPage> {
ResourceModel? audioResourceModel; ResourceModel? audioResourceModel;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
late Uint8List audiobytes; late Uint8List audiobytes;
VisitAppContext? visitAppContext; late VisitAppContext visitAppContext;
QuizzDTO? quizzDTO; QuizzDTO? quizzDTO;
List<QuestionSubDTO> _questionsSubDTO = <QuestionSubDTO>[]; List<QuestionSubDTO> _questionsSubDTO = <QuestionSubDTO>[];
@ -67,7 +67,7 @@ class _QuizzPageState extends State<QuizzPage> {
@override @override
void dispose() { void dispose() {
visitAppContext!.isContentCurrentlyShown = false; visitAppContext.isContentCurrentlyShown = false;
currentIndex = 1; currentIndex = 1;
//_controllerCenter!.dispose(); //_controllerCenter!.dispose();
if(quizzDTO != null) { if(quizzDTO != null) {
@ -94,7 +94,7 @@ class _QuizzPageState extends State<QuizzPage> {
body: OrientationBuilder( body: OrientationBuilder(
builder: (context, orientation) { builder: (context, orientation) {
return FutureBuilder( return FutureBuilder(
future: getQuizz(appContext, appContext.clientAPI, widget.sectionId), // MAYBE MOVE THAT TO PARENT .. future: getQuizz(appContext, visitAppContext.clientAPI, widget.sectionId), // MAYBE MOVE THAT TO PARENT ..
builder: (context, AsyncSnapshot<dynamic> snapshot) { builder: (context, AsyncSnapshot<dynamic> snapshot) {
if(quizzDTO != null && sectionDTO != null) { if(quizzDTO != null && sectionDTO != null) {

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
@ -18,6 +19,7 @@ import 'package:mymuseum_visitapp/client.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
class BeaconArticleFound extends StatefulWidget { class BeaconArticleFound extends StatefulWidget {
const BeaconArticleFound({Key? key, required this.beaconSection}) : super(key: key); const BeaconArticleFound({Key? key, required this.beaconSection}) : super(key: key);
@ -29,7 +31,7 @@ class BeaconArticleFound extends StatefulWidget {
} }
class _BeaconArticleFoundState extends State<BeaconArticleFound> { class _BeaconArticleFoundState extends State<BeaconArticleFound> {
VisitAppContext? visitAppContext; late VisitAppContext visitAppContext;
SectionDTO? sectionFound; SectionDTO? sectionFound;
@override @override
@ -39,15 +41,15 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
visitAppContext = appContext.getContext(); visitAppContext = appContext.getContext();
if(widget.beaconSection != null && visitAppContext!.currentSections != null && visitAppContext!.currentSections!.isNotEmpty) if(widget.beaconSection != null && visitAppContext.currentSections != null && visitAppContext.currentSections!.isNotEmpty)
{ {
var testSection = visitAppContext!.currentSections!.where((section) => section!.id.toString() == widget.beaconSection!.sectionId.toString()).toList(); var testSection = visitAppContext.currentSections!.where((section) => section!.id.toString() == widget.beaconSection!.sectionId.toString()).toList();
sectionFound = testSection.isNotEmpty ? testSection.first : null; sectionFound = testSection.isNotEmpty ? testSection.first : null;
} }
return FutureBuilder( return FutureBuilder(
future: getSectionImage(appContext, appContext.clientAPI, sectionFound), future: getSectionImage(appContext, visitAppContext.clientAPI, sectionFound),
builder: (context, AsyncSnapshot<ResourceModel?> snapshot) { builder: (context, AsyncSnapshot<dynamic> snapshot) {
return SizedBox( return SizedBox(
height: size.height *0.4, height: size.height *0.4,
width: size.width *0.9, width: size.width *0.9,
@ -56,7 +58,7 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if(snapshot.data != null && ((visitAppContext!.configuration!.isOffline! && snapshot.data!.data != null) || (visitAppContext!.configuration!.isOffline! && snapshot.data!.source != null))) if(snapshot.data != null && ((visitAppContext.configuration!.isOffline! && snapshot.data!.path != null) || (visitAppContext.configuration!.isOffline! && snapshot.data!.source != null)))
SizedBox( SizedBox(
height: size.height * 0.25, height: size.height * 0.25,
width: size.width * 0.75, width: size.width * 0.75,
@ -65,11 +67,12 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
padding: const EdgeInsets.only(left: 8.0, right: 8.0), padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(20)), borderRadius: const BorderRadius.all(Radius.circular(20)),
child: visitAppContext!.configuration!.isOffline! ? child: visitAppContext.configuration!.isOffline! ?
Image.memory( snapshot.data != null ?
base64Decode(snapshot.data!.data!), Image.file(
fit: BoxFit.cover snapshot.data!,
) : fit: BoxFit.cover,
) : null :
Image.network( Image.network(
snapshot.data!.source!, snapshot.data!.source!,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -120,7 +123,7 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
); );
} }
Future<ResourceModel?> getSectionImage(AppContext appContext, Client clientAPI, SectionDTO? sectionFound) async { Future<dynamic> getSectionImage(AppContext appContext, Client clientAPI, SectionDTO? sectionFound) async {
if(sectionFound == null) { if(sectionFound == null) {
return null; return null;
} }
@ -128,7 +131,16 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
if(isConfigOffline) { if(isConfigOffline) {
List<Map<String, dynamic>> resource = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, sectionFound.imageId!); List<Map<String, dynamic>> resource = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, sectionFound.imageId!);
if(resource.isNotEmpty) { if(resource.isNotEmpty) {
return DatabaseHelper.instance.getResourceFromDB(resource.first); Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
Directory configurationDirectory = Directory('$localPath/${sectionFound.configurationId}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
if(fileList.any((fileL) => fileL.uri.pathSegments.last.contains(sectionFound.imageId!))) {
File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(sectionFound.imageId!)).path);
return file;
}
return null;
} else { } else {
print("EMPTY resourcesModel - getSectionImage"); print("EMPTY resourcesModel - getSectionImage");
return null; return null;

View File

@ -163,7 +163,7 @@ class _BodyState extends State<Body> {
else else
{ {
// ONLINE // ONLINE
List<SectionDTO>? sectionsDownloaded = await ApiService.getAllSections(appContext.clientAPI, visitAppContext.configuration!.id!); List<SectionDTO>? sectionsDownloaded = await ApiService.getAllSections(visitAppContext.clientAPI, visitAppContext.configuration!.id!);
//print(sectionsDownloaded); //print(sectionsDownloaded);
if(sectionsDownloaded!.isNotEmpty) { 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 sections = sectionsDownloaded.where((s) => s.type == SectionType.Article || s.type == SectionType.Quizz).toList(); // TODO Support more than Article and Quizz section type

View File

@ -82,15 +82,15 @@ class SectionCard extends StatelessWidget {
// image is square but we add extra 20 + 20 padding thats why width is 200 // image is square but we add extra 20 + 20 padding thats why width is 200
width: size.width*0.5, width: size.width*0.5,
child: FutureBuilder( child: FutureBuilder(
future: ApiService.getResource(appContext, sectionDTO.imageId!), future: ApiService.getResource(appContext, (appContext.getContext() as VisitAppContext).configuration!, sectionDTO.imageId!),
builder: (context, AsyncSnapshot<dynamic> snapshot) { builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
return snapshot.data != null ? ClipRRect( return ClipRRect(
borderRadius: const BorderRadius.only(topRight: Radius.circular(20), bottomRight: Radius.circular(20)), borderRadius: const BorderRadius.only(topRight: Radius.circular(20), bottomRight: Radius.circular(20)),
child: isOffline ? child: isOffline && snapshot.data != null ?
Image.memory( Image.file(
base64Decode(snapshot.data!.data!), snapshot.data!,
fit: BoxFit.cover fit: BoxFit.cover,
) : ) :
Image.network( Image.network(
sectionDTO.imageSource!, sectionDTO.imageSource!,
@ -111,7 +111,7 @@ class SectionCard extends StatelessWidget {
); );
}, },
), ),
) : const Text(""); );
} else if (snapshot.connectionState == ConnectionState.none) { } else if (snapshot.connectionState == ConnectionState.none) {
return Text(TranslationHelper.getFromLocale("noData", appContext.getContext())); return Text(TranslationHelper.getFromLocale("noData", appContext.getContext()));
} else { } else {

View File

@ -272,7 +272,7 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
content: BeaconArticleFound(beaconSection: beaconSection), content: BeaconArticleFound(beaconSection: beaconSection),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
child: Text(TranslationHelper.getFromLocale("close", visitAppContext), style: TextStyle(color: kMainColor)), child: Text(TranslationHelper.getFromLocale("close", visitAppContext), style: const TextStyle(color: kSecondGrey)),
onPressed: () { onPressed: () {
_isDialogShowing = false; // set it `false` since dialog is closed _isDialogShowing = false; // set it `false` since dialog is closed
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -280,7 +280,7 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
}, },
), ),
TextButton( TextButton(
child: Text(TranslationHelper.getFromLocale("open", visitAppContext), style: TextStyle(color: kMainColor)), child: Text(TranslationHelper.getFromLocale("open", visitAppContext), style: const TextStyle(color: kSecondGrey)),
onPressed: () { onPressed: () {
_isDialogShowing = false; // set it `false` since dialog is closed _isDialogShowing = false; // set it `false` since dialog is closed
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -364,13 +364,8 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 90, bottom: 1), padding: const EdgeInsets.only(right: 90, bottom: 1),
child: SizedBox( child: InkWell(
height: 75.0, onTap: () async {
width: 65.0,
child: FittedBox(
child: FloatingActionButton(
heroTag: "beacon",
onPressed: () async {
bool isCancel = false; bool isCancel = false;
if(!controller.authorizationStatusOk) { if(!controller.authorizationStatusOk) {
@ -390,10 +385,10 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Icon(Icons.my_location, color: kMainColor), const Icon(Icons.my_location, color: kSecondGrey),
Padding( Padding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Text(TranslationHelper.getFromLocale("locationWarning", visitAppContext), style: const TextStyle(color: kMainColor), textAlign: TextAlign.center), child: Text(TranslationHelper.getFromLocale("locationWarning", visitAppContext), style: const TextStyle(color: kSecondGrey), textAlign: TextAlign.center),
), ),
], ],
), ),
@ -401,14 +396,14 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
), ),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
child: Text(TranslationHelper.getFromLocale("close", visitAppContext), style: TextStyle(color: kMainColor)), child: Text(TranslationHelper.getFromLocale("close", visitAppContext), style: TextStyle(color: kSecondGrey)),
onPressed: () { onPressed: () {
isCancel = true; isCancel = true;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
TextButton( TextButton(
child: Text(TranslationHelper.getFromLocale("ok", visitAppContext), style: TextStyle(color: kMainColor)), child: Text(TranslationHelper.getFromLocale("ok", visitAppContext), style: TextStyle(color: kSecondGrey)),
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -501,10 +496,14 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
} }
} }
}, },
tooltip: 'Beacon', child: Container(
backgroundColor: visitAppContext.isScanningBeacons ? kMainColor1 : Colors.grey, decoration: BoxDecoration(
child: const Icon(Icons.my_location), shape: BoxShape.circle,
color: visitAppContext.isScanningBeacons ? kMainColor1 : Colors.grey,
), ),
height: 75.0,
width: 65.0,
child: const Icon(Icons.my_location, color: Colors.white),
), ),
), ),
), ),

View File

@ -8,6 +8,7 @@ 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/app_context.dart'; import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/client.dart'; import 'package:mymuseum_visitapp/client.dart';
import 'package:path_provider/path_provider.dart';
class ApiService { class ApiService {
static Future<List<ConfigurationDTO>?> getConfigurations(Client client, VisitAppContext? visitAppContext) async { static Future<List<ConfigurationDTO>?> getConfigurations(Client client, VisitAppContext? visitAppContext) async {
@ -117,7 +118,7 @@ class ApiService {
await for(dynamic d in response) { _downloadData.addAll(d); } await for(dynamic d in response) { _downloadData.addAll(d); }
//print("AFTER"); //print("AFTER");
final base64Str = base64.encode(_downloadData); final base64Str = base64.encode(_downloadData);
ResourceModel resourceModel = ResourceModel(id: contentDTO.resourceId, source: contentDTO.resourceUrl, data: base64Str, type: ResourceType.Image); ResourceModel resourceModel = ResourceModel(id: contentDTO.resourceId, source: contentDTO.resourceUrl, path: base64Str, type: ResourceType.Image);
return resourceModel; return resourceModel;
} }
@ -132,7 +133,7 @@ class ApiService {
} else { } else {
bool isOnline = await hasNetwork(); bool isOnline = await hasNetwork();
if(isOnline) { if(isOnline) {
ResourceModel? resourceModel = await downloadAudio(client, audioId); ResourceModel? resourceModel = await downloadAudio(client, audioId, resourceTest.first[0]);
if(resourceModel != null) { if(resourceModel != null) {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap()); await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
} }
@ -148,33 +149,43 @@ class ApiService {
} }
} }
static Future<ResourceModel?> downloadAudio(Client client, String audioId) async { static Future<ResourceModel?> downloadAudio(Client client, String resourceUrl, String audioId) async {
var url = "https://api.myinfomate.be/api/Resource/"+audioId; // TO TEST TODO UPDATE ROUTE //var url = "https://api.myinfomate.be/api/Resource/"+audioId; // TO TEST TODO UPDATE ROUTE
HttpClient client2 = HttpClient(); HttpClient client2 = HttpClient();
var _downloadData = <int>[]; var _downloadData = <int>[];
final HttpClientRequest request = await client2.getUrl(Uri.parse(url)); final HttpClientRequest request = await client2.getUrl(Uri.parse(resourceUrl));
HttpClientResponse response = await request.close(); HttpClientResponse response = await request.close();
await for(dynamic d in response) { _downloadData.addAll(d); } await for(dynamic d in response) { _downloadData.addAll(d); }
final base64Str = base64.encode(_downloadData); final base64Str = base64.encode(_downloadData);
ResourceModel resourceModel = ResourceModel(id: audioId, data: base64Str, type: ResourceType.Audio, source: ""); ResourceModel resourceModel = ResourceModel(id: audioId, path: base64Str, type: ResourceType.Audio, source: "");
return resourceModel; return resourceModel;
} }
static Future<ResourceModel?> getResource(AppContext appContext, String imageId) async { static Future<File?> getResource(AppContext appContext, ConfigurationDTO configurationDTO, String imageId) async {
if((appContext.getContext() as VisitAppContext).configuration == null || (appContext.getContext() as VisitAppContext).configuration!.isOffline!) if((appContext.getContext() as VisitAppContext).configuration == null || (appContext.getContext() as VisitAppContext).configuration!.isOffline!)
{ {
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
Directory configurationDirectory = Directory('$localPath/${configurationDTO.id}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
if(fileList.any((fileL) => fileL.uri.pathSegments.last.contains(imageId))) {
File file = File(fileList.firstWhere((fileL) => fileL.uri.pathSegments.last.contains(imageId)).path);
return file;
}
// OFFLINE // OFFLINE
List<Map<String, dynamic>> resourceTest = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, imageId); /*List<Map<String, dynamic>> resourceTest = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, imageId);
if(resourceTest.isNotEmpty) { if(resourceTest.isNotEmpty) {
return DatabaseHelper.instance.getResourceFromDB(resourceTest.first); return DatabaseHelper.instance.getResourceFromDB(resourceTest.first);
} else { } else {
return null; return null;
}*/
} }
} else
{
// ONLINE // ONLINE
return ResourceModel(); // To mock return null;
}
} }
static Future<ExportConfigurationDTO?> exportConfiguration(Client client, String configurationId, String? language) async { static Future<ExportConfigurationDTO?> exportConfiguration(Client client, String configurationId, String? language) async {

View File

@ -1,17 +1,329 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:manager_api/api.dart'; import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart'; import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/modelsHelper.dart'; import 'package:mymuseum_visitapp/Helpers/modelsHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart'; 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/Services/apiService.dart'; import 'package:mymuseum_visitapp/Services/apiService.dart';
import 'package:mymuseum_visitapp/app_context.dart'; import 'package:mymuseum_visitapp/app_context.dart';
import 'package:mymuseum_visitapp/constants.dart'; import 'package:mymuseum_visitapp/constants.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:http/http.dart' as http;
import 'package:fluttertoast/fluttertoast.dart';
class DownloadConfiguration { class DownloadConfigurationWidget extends StatefulWidget {
DownloadConfigurationWidget({Key? key, required this.configuration}) : super(key: key);
final ConfigurationDTO configuration;
@override
State<DownloadConfigurationWidget> createState() => _DownloadConfigurationWidgetState();
}
class _DownloadConfigurationWidgetState extends State<DownloadConfigurationWidget> {
ValueNotifier<int> currentResourceIndex = ValueNotifier<int>(0);
ValueNotifier<int> currentResourceNbr = ValueNotifier<int>(-1);
bool isAlreadyDownloading = false;
//OtaEvent? currentEvent;
Future<bool> download(BuildContext buildContext, VisitAppContext visitAppContext) async {
bool isAllLanguages = true;
if(visitAppContext.isAllLanguages != null) {
isAllLanguages = visitAppContext.isAllLanguages!;
if(isAllLanguages) {
print("Admin here and all download all audios !");
/*ScaffoldMessenger.of(buildContext).showSnackBar(
const SnackBar(
content: Text("Tous les audios vont être téléchargés"),
backgroundColor: kMainColor),
);*/
}
}
if(!isAlreadyDownloading) {
isAlreadyDownloading = true;
// HERE CHECK VERSION APK
if(true) {
Map<Permission, PermissionStatus> statuses = await [
Permission.requestInstallPackages,
].request();
//tryOtaUpdate();
}
ExportConfigurationDTO? exportConfigurationDTO;
try{
// Retrieve all url from resource to download (get all resource from configuration en somme)
exportConfigurationDTO = await visitAppContext.clientAPI.configurationApi!.configurationExport(widget.configuration.id!, isAllLanguages ? null : visitAppContext.language); // tabletAppContext.configuration!.id! // 65c5f0ee4c030e63ce16bff5 TODO Remove
} catch(e) {
print("Erreur lors du téléchargement de la configuration et de ses ressources !");
print(e);
return false;
}
exportConfigurationDTO.resources!.forEach((element) {
print(element.id);
print(element.label);
});
if(exportConfigurationDTO.resources != null && exportConfigurationDTO.resources!.isNotEmpty) {
Map<Permission, PermissionStatus> statuses = await [
Permission.storage,
].request();
//if(statuses[Permission.storage] == PermissionStatus.granted) {
try{
try {
Directory directory = Directory('${visitAppContext.localPath}');
List<FileSystemEntity> allConfigurations = directory.listSync();
Directory configurationDirectory = Directory('${visitAppContext.localPath}/${widget.configuration.id}');
if(!allConfigurations.any((configurationDirectory) => configurationDirectory.uri.pathSegments.any((element) => element == widget.configuration.id))) {
// create directory
print("Trying to create directory");
configurationDirectory.createSync(recursive: true);
print('Répertoire créé avec succès.');
}
} catch(e) {
print("Listing failed, so try to create directory");
Directory configurationDirectory = Directory('${visitAppContext.localPath}/${widget.configuration.id}');
configurationDirectory.createSync(recursive: true);
print('Répertoire créé avec succès.');
}
Directory configurationDirectory = Directory('${visitAppContext.localPath}/${widget.configuration.id}');
List<FileSystemEntity> fileList = configurationDirectory.listSync();
for (var file in fileList) {
print(file.uri.pathSegments.last);
}
var resourcesToDownload = exportConfigurationDTO.resources!.where((resource) => resource.type != ResourceType.ImageUrl && resource.type != ResourceType.VideoUrl && resource.type != ResourceType.JsonUrl && resource.url != null && !fileList.any((fileL) => fileL.uri.pathSegments.last.contains(resource.id!)));
currentResourceNbr.value = resourcesToDownload.length;
// foreach ou on va tout télécharger - avec un joli etape 0 / length - on peut rendre tout lent on s'en fou ça ne ce fait qu'une fois
for (var resource in resourcesToDownload) {
String? filePath = await downloadResource(visitAppContext, widget.configuration, resource, visitAppContext.localPath!);
if (filePath != null)
{
// Insert in database
ResourceModel resourceModel = ResourceModel(id: resource.id, source: resource.url, path: filePath, type: resource.type);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
} catch (e) {
print("We got an issue inserting image metadata ${resource.id}");
}
currentResourceIndex.value++;
} else {
print("NOT SUCCESSS");
}
}
// Delete others that are no more used
var resourceToDelete = fileList.where((fileL) =>
!exportConfigurationDTO!.resources!.any((resource) =>
resource.id != null && fileL.uri.pathSegments.last.contains(resource.id!)));
for (var resource in resourceToDelete) {
print("resource to DELETE");
print(resource.path);
// resource.deleteSync();
// Preserve call to firebase // TODO uncomment if needed
}
await DatabaseHelper.instance.insert(DatabaseTableType.configurations, ModelsHelper.configurationToMap(widget.configuration));
List<SectionDTO>? sections = exportConfigurationDTO.sections;
List<String> usedImageOrAudioIds = [];
if(sections!.isNotEmpty) {
List<SectionDTO> sectionsInDB = await DatabaseHelper.instance.queryWithConfigurationId(DatabaseTableType.sections, widget.configuration.id!);
List<SectionDTO> sectionsToKeep = sections.where((s) => s.type == SectionType.Article || s.type == SectionType.Quizz).toList(); // TODO handle other type of section (for now, Article and Quizz)
sectionsToKeep.sort((a,b) => a.order!.compareTo(b.order!));
int newOrder = 0;
// Update local DB - Sections
for(var section in sectionsToKeep) {
section.order = newOrder;
try {
await DatabaseHelper.instance.insert(DatabaseTableType.sections, ModelsHelper.sectionToMap(section));
} catch (e) {
print("We got an issue inserting section data ${section.id}");
}
// Download section image
if(section.imageId != null) {
usedImageOrAudioIds.add(section.imageId!);
var imageData = exportConfigurationDTO.resources!.where((element) => element.id == section.imageId);
if(imageData.isNotEmpty) {
// TODO get all resources from API + store it in download directory of the app
ResourceModel resourceModel = ResourceModel(id: imageData.first.id, source: section.imageSource, /*data: imageData.first.data,*/ type: imageData.first.type);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
} catch (e) {
print("We got an issue inserting image data ${imageData.first.id}");
}
}
}
// Download all images..
ArticleDTO? articleDTO = ArticleDTO.fromJson(jsonDecode(section.data!));
if(articleDTO != null) {
for(var image in articleDTO.contents!) {
usedImageOrAudioIds.add(image.resourceId!);
/*var imageData = exportConfigurationDTO.resources!.where((element) => element.id == image.resourceId);
if(imageData.isNotEmpty) {
// TODO get all resources from API + store it in download directory of the app
ResourceModel resourceModel = ResourceModel(id: imageData.first.id, source: image.resourceUrl, /*data: imageData.first.data,*/ type: imageData.first.type);
try {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
} catch (e) {
print("We got an issue inserting image data ${imageData.first.id}");
}
}*/
}
var audioIdsArticle = isAllLanguages ? articleDTO.audioIds! : articleDTO.audioIds!.where((audioId) => audioId.language == visitAppContext.language);
for(var audioId in audioIdsArticle) {
if(audioId.value != null) {
usedImageOrAudioIds.add(audioId.value!);
//audiosNotWorking = await importAudio(visitAppContext, exportConfigurationDTO, audioId.value!, audiosNotWorking);
}
}
}
newOrder = newOrder + 1;
}
List<String?> sectionIdsToRemove = sectionsInDB.map((s) => s.id).where((sectionId) => !sectionsToKeep.map((sk) => sk.id).contains(sectionId)).toList();
for(var sectionIdToRemove in sectionIdsToRemove) {
print("section with id removed");
print(sectionIdToRemove);
try {
await DatabaseHelper.instance.delete(sectionIdToRemove!, DatabaseTableType.sections);
} catch (e) {
print("We got an issue deleting section id: ${sectionIdToRemove}");
}
}
// TODO CLEAN AND REMOVE FILES !
//cleanLocalResources(usedImageOrAudioIds, widget.configuration);
}
} catch(e) {
print("ERRORRRR");
print(e);
if(statuses[Permission.storage] != PermissionStatus.granted) {
Fluttertoast.showToast(
msg: "PermissionStatus not granted, issue may be linked to that. Please check os version (if less than 13, real issue).",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.redAccent,
textColor: Colors.white,
fontSize: 16.0
);
}
return false;
}
/*} else {
print("PermissionStatus.granted NOT GRANTED");
Fluttertoast.showToast(
msg: "PermissionStatus not granted",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.redAccent,
textColor: Colors.white,
fontSize: 16.0
);
return false;
}*/
}
}
return true;
}
@override
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Spacer(),
ValueListenableBuilder<int>(
valueListenable: currentResourceNbr,
builder: (context, valueNbr, _) {
return ValueListenableBuilder<int>(
valueListenable: currentResourceIndex,
builder: (context, valueIndex, _) {
return valueNbr != -1 && valueNbr != 0 ? Center(
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"${valueIndex.toString()}/${valueNbr.toString()}",
style: TextStyle(fontSize: 45, fontWeight: FontWeight.w500),
),
),
) : SizedBox(height: 0,width: 0);
}
);
}
),
FutureBuilder(future: download(context, visitAppContext), builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// Loader ou indicateur de chargement pendant la vérification
Color primaryColor = kMainColor1;
return Center(child: CircularProgressIndicator(color: primaryColor));
} else {
return ValueListenableBuilder<int>(
valueListenable: currentResourceNbr,
builder: (context, valueNbr, _) {
return ValueListenableBuilder<int>(
valueListenable: currentResourceIndex,
builder: (context, valueIndex, _) {
return Center(
child: Text(
valueIndex == valueNbr && valueNbr != -1 ? valueNbr == 0 ?
TranslationHelper.getFromLocale(
"upToDate",
appContext.getContext()) : TranslationHelper.getFromLocale(
"downloadFinish",
appContext.getContext()) : TranslationHelper.getFromLocale(
"downloadInProgress",
appContext.getContext()),
style: const TextStyle(fontSize: 20),
),
);
}
);
}
);
}
}),
Spacer()
],
);
}
}
/*class DownloadConfiguration {
static Future<List<ResourceModel>> download(BuildContext buildContext, AppContext appContext, ConfigurationDTO configuration) async { static Future<List<ResourceModel>> download(BuildContext buildContext, AppContext appContext, ConfigurationDTO configuration) async {
VisitAppContext visitAppContext = (appContext.getContext() as VisitAppContext); VisitAppContext visitAppContext = (appContext.getContext() as VisitAppContext);
@ -35,7 +347,7 @@ class DownloadConfiguration {
ExportConfigurationDTO? exportConfigurationDTO; ExportConfigurationDTO? exportConfigurationDTO;
try{ try{
exportConfigurationDTO = await ApiService.exportConfiguration(appContext.clientAPI, configuration.id!, isAllLanguages ? null : visitAppContext.language!); exportConfigurationDTO = await ApiService.exportConfiguration(visitAppContext.clientAPI, configuration.id!, isAllLanguages ? null : visitAppContext.language!);
} catch(e) { } catch(e) {
print("Erreur lors du téléchargement de la visite"); print("Erreur lors du téléchargement de la visite");
print(e); print(e);
@ -144,6 +456,56 @@ class DownloadConfiguration {
return audiosNotWorking; return audiosNotWorking;
} }
} }
*/
Future<String?> downloadResource(VisitAppContext visitAppContext, ConfigurationDTO configurationDTO, ResourceDTO resourceDTO, String localPath) async {
try {
// Téléchargement de la ressource depuis l'URL
http.Response response = await http.get(Uri.parse(resourceDTO.url!));
if (response.statusCode == 200) {
// Vérification de l'en-tête Content-Type
String contentType = response.headers["content-type"] ?? "";
// Déduction de l'extension en fonction du Content-Type
String extension = _getExtensionFromContentType(contentType);
print("LOCAL PATTH");
print(localPath);
File file = File('$localPath/${configurationDTO.id}/${resourceDTO.id}.$extension');
// Écriture du contenu téléchargé dans le fichier local
await file.writeAsBytes(response.bodyBytes);
return file.path;
} else {
print("Échec du téléchargement de la ressource - ${response.statusCode}");
return null;
}
} catch (e) {
print("Erreur lors du téléchargement de la ressource !");
print(e);
return null;
}
}
String _getExtensionFromContentType(String contentType) {
Map<String, String> contentTypeToExtension = {
"image/jpeg": "jpg",
"image/jpg": "jpg",
"image/png": "png",
"image/gif": "gif",
"audio/mp3": "mp3",
"video/mp4": "mp4",
"video/webm": "webm",
"video/avi": "avi",
"video/quicktime": "mov",
"application/pdf": "pdf",
"application/json": "json"
};
return contentTypeToExtension[contentType] ?? "unknown";
}
Future<List<ResourceModel>> importAudio(VisitAppContext visitAppContext, ExportConfigurationDTO exportConfigurationDTO, String audioId, List<ResourceModel> audiosNotWorking) async { Future<List<ResourceModel>> importAudio(VisitAppContext visitAppContext, ExportConfigurationDTO exportConfigurationDTO, String audioId, List<ResourceModel> audiosNotWorking) async {
var audioData = exportConfigurationDTO.resources!.where((element) => element.id == audioId); var audioData = exportConfigurationDTO.resources!.where((element) => element.id == audioId);
@ -192,3 +554,6 @@ void cleanLocalResources(List<String> usedImageIds, ConfigurationDTO configurati
} }
} }

View File

@ -5,8 +5,7 @@ import 'Models/visitContext.dart';
class AppContext with ChangeNotifier { class AppContext with ChangeNotifier {
VisitAppContext _visitContext; VisitAppContext? _visitContext;
Client clientAPI = Client("https://api.myinfomate.be"); // Replace by https://api.mymuseum.be //http://192.168.31.140:8089
AppContext(this._visitContext); AppContext(this._visitContext);

View File

@ -15,12 +15,16 @@ import 'app_context.dart';
import 'constants.dart'; import 'constants.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:path_provider/path_provider.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
String initialRoute; String initialRoute;
VisitAppContext? localContext = await DatabaseHelper.instance.getData(DatabaseTableType.main); VisitAppContext? localContext = await DatabaseHelper.instance.getData(DatabaseTableType.main);
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
if(localContext != null) { if(localContext != null) {
print("we've got an local db !"); print("we've got an local db !");
print(localContext); print(localContext);
@ -38,6 +42,9 @@ void main() async {
print("NO LOCAL DB !"); print("NO LOCAL DB !");
} }
localContext.localPath = localPath;
print("Local path $localPath");
initialRoute = '/home'; initialRoute = '/home';
final MyApp myApp = MyApp( final MyApp myApp = MyApp(
@ -104,6 +111,9 @@ class _MyAppState extends State<MyApp> {
//fontFamily: "Vollkorn", //fontFamily: "Vollkorn",
textTheme: TextTheme(bodyLarge: TextStyle(color: widget.visitAppContext.configuration != null ? widget.visitAppContext.configuration!.primaryColor != null ? Color(int.parse(widget.visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)): kMainColor1 : kMainColor1)), textTheme: TextTheme(bodyLarge: TextStyle(color: widget.visitAppContext.configuration != null ? widget.visitAppContext.configuration!.primaryColor != null ? Color(int.parse(widget.visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)): kMainColor1 : kMainColor1)),
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
appBarTheme: const AppBarTheme(
iconTheme: IconThemeData(color: Colors.white), // Change the color here
),
), ),
routes: { routes: {
'/home': (context) => const HomePage(), '/home': (context) => const HomePage(),

View File

@ -22,7 +22,10 @@ List<Translation> translations = [
"downloadLanguage": "Sélectionner la langue de la visite", "downloadLanguage": "Sélectionner la langue de la visite",
"ok": "OK", "ok": "OK",
"responses": "Réponses", "responses": "Réponses",
"restart": "Recommencer" "restart": "Recommencer",
"downloadInProgress": "Téléchargement en cours",
"downloadFinish": "Téléchargement terminé",
"upToDate": "Tout est à jour"
}), }),
Translation(language: "EN", data: { Translation(language: "EN", data: {
"visitTitle": "List of tours", "visitTitle": "List of tours",
@ -45,7 +48,10 @@ List<Translation> translations = [
"downloadLanguage": "Select the tour language", "downloadLanguage": "Select the tour language",
"ok": "OK", "ok": "OK",
"responses": "Answers", "responses": "Answers",
"restart": "Restart" "restart": "Restart",
"downloadInProgress": "Download in progress",
"downloadFinish": "Download complete",
"upToDate": "Up to date"
}), }),
Translation(language: "DE", data: { Translation(language: "DE", data: {
"visitTitle": "Liste der Touren", "visitTitle": "Liste der Touren",
@ -68,7 +74,10 @@ List<Translation> translations = [
"downloadLanguage": "Wählen Sie die Sprache des Besuchs aus", "downloadLanguage": "Wählen Sie die Sprache des Besuchs aus",
"ok": "OK", "ok": "OK",
"responses": "Antworten", "responses": "Antworten",
"restart": "Neu starten" "restart": "Neu starten",
"downloadInProgress": "Download läuft",
"downloadFinish": "Download abgeschlossen",
"upToDate": "Alles ist auf dem neuesten Stand"
}), }),
Translation(language: "NL", data: { Translation(language: "NL", data: {
"visitTitle": "Lijst met rondleidingen", "visitTitle": "Lijst met rondleidingen",
@ -91,7 +100,10 @@ List<Translation> translations = [
"downloadLanguage": "Selecteer de taal van de tour", "downloadLanguage": "Selecteer de taal van de tour",
"ok": "OK", "ok": "OK",
"responses": "Antwoorden", "responses": "Antwoorden",
"restart": "Herstarten" "restart": "Herstarten",
"downloadInProgress": "Download bezig",
"downloadFinish": "Download voltooid",
"upToDate": "Alles is up-to-date"
}), }),
Translation(language: "IT", data: { Translation(language: "IT", data: {
"visitTitle": "Elenco dei tour", "visitTitle": "Elenco dei tour",
@ -114,7 +126,10 @@ List<Translation> translations = [
"downloadLanguage": "Seleziona la lingua del tour", "downloadLanguage": "Seleziona la lingua del tour",
"ok": "OK", "ok": "OK",
"responses": "Risposte", "responses": "Risposte",
"restart": "Ricomincia" "restart": "Ricomincia",
"downloadInProgress": "Download in corso",
"downloadFinish": "Download completato",
"upToDate": "Tutto è aggiornato"
}), }),
Translation(language: "ES", data: { Translation(language: "ES", data: {
"visitTitle": "Lista de recorridos", "visitTitle": "Lista de recorridos",
@ -137,7 +152,10 @@ List<Translation> translations = [
"downloadLanguage": "Selecciona el idioma del tour", "downloadLanguage": "Selecciona el idioma del tour",
"ok": "Ok", "ok": "Ok",
"responses": "Respuestas", "responses": "Respuestas",
"restart": "Reanudar" "restart": "Reanudar",
"downloadInProgress": "Descarga en curso",
"downloadFinish": "Descarga completada",
"upToDate": "Todo está al día"
}), }),
Translation(language: "PL", data: { Translation(language: "PL", data: {
"visitTitle": "Lista wycieczek", "visitTitle": "Lista wycieczek",
@ -160,7 +178,10 @@ List<Translation> translations = [
"downloadLanguage": "Wybierz język wycieczki", "downloadLanguage": "Wybierz język wycieczki",
"ok": "OK", "ok": "OK",
"responses": "Odpowiedzi", "responses": "Odpowiedzi",
"restart": "Uruchom ponownie" "restart": "Uruchom ponownie",
"downloadInProgress": "Pobieranie w toku",
"downloadFinish": "Pobieranie zakończone",
"upToDate": "Wszystko jest aktualne"
}), }),
Translation(language: "CN", data: { Translation(language: "CN", data: {
"visitTitle": "旅游清单", "visitTitle": "旅游清单",
@ -183,7 +204,10 @@ List<Translation> translations = [
"downloadLanguage": "选择游览语言", "downloadLanguage": "选择游览语言",
"ok": "好的", "ok": "好的",
"responses": "答案", "responses": "答案",
"restart": "重新开始" "restart": "重新开始",
"downloadInProgress": "下载中",
"downloadFinish": "下载完成",
"upToDate": "已是最新"
}), }),
Translation(language: "UK", data: { Translation(language: "UK", data: {
"visitTitle": "Список турів", "visitTitle": "Список турів",
@ -206,7 +230,10 @@ List<Translation> translations = [
"downloadLanguage": "Виберіть мову туру", "downloadLanguage": "Виберіть мову туру",
"ok": "В порядку", "ok": "В порядку",
"responses": "Відповіді", "responses": "Відповіді",
"restart": "Перезапустіть" "restart": "Перезапустіть",
"downloadInProgress": "Завантаження триває",
"downloadFinish": "Завантаження завершено",
"upToDate": "Все актуально"
}), }),
Translation(language: "AR", data: { Translation(language: "AR", data: {
"visitTitle": "قائمة الجولات", "visitTitle": "قائمة الجولات",
@ -229,6 +256,9 @@ List<Translation> translations = [
"downloadLanguage": "حدد لغة الجولة", "downloadLanguage": "حدد لغة الجولة",
"ok": "نعم", "ok": "نعم",
"responses": "الإجابات", "responses": "الإجابات",
"restart": "إعادة تشغيل" "restart": "إعادة تشغيل",
"downloadInProgress": "جارٍ التنزيل",
"downloadFinish": "اكتمل التنزيل",
"upToDate": "كل شيء محدث"
}), }),
]; ];

View File

@ -378,6 +378,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.1" version: "0.15.1"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
sha256: "7eae679e596a44fdf761853a706f74979f8dd3cd92cf4e23cae161fda091b847"
url: "https://pub.dev"
source: hosted
version: "8.2.6"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:

View File

@ -38,6 +38,7 @@ dependencies:
openapi_generator_annotations: ^5.0.2 openapi_generator_annotations: ^5.0.2
sqflite: sqflite:
provider: ^6.1.2 provider: ^6.1.2
fluttertoast:
#carousel_slider: ^4.2.1 #carousel_slider: ^4.2.1
#flutter_svg_provider: ^1.0.3 #flutter_svg_provider: ^1.0.3
photo_view: ^0.15.0 photo_view: ^0.15.0