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> {
final TextEditingController _controller = TextEditingController();
bool isPasswordOk = false;
String password = "FORT2023!";
String password = "ADMIN2024!"; //FORT2023!
VisitAppContext? visitAppContext;
@override

View File

@ -36,7 +36,7 @@ class BuilderImageSlider extends StatelessWidget {
//width: size.width * 0.95,
child: ClipRRect(
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,
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.contained * 3.0,

View File

@ -28,6 +28,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
Widget build(BuildContext context) {
final appContext = Provider.of<AppContext>(context);
VisitAppContext visitAppContext = appContext.getContext();
Size size = MediaQuery.of(context).size;
//final notchInset = MediaQuery.of(context).padding;
return AppBar(
@ -45,7 +46,17 @@ class _CustomAppBarState extends State<CustomAppBar> {
), 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,
leading: widget.isHomeButton ? IconButton(
@ -73,7 +84,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
},
child: SizedBox(
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(
@ -97,8 +108,10 @@ class _CustomAppBarState extends State<CustomAppBar> {
Color(0xFF7633B8),
Color(0xFF6528B6),
Color(0xFF6025B6)*/
Color(0xFFf6b3c4),
kMainColor1,
kMainColor2,
],
),
),

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.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: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(
builder: (BuildContext context) => AlertDialog(
shape: const RoundedRectangleBorder(
@ -33,8 +34,8 @@ void showImagePopup(ContentDTO contentDTO, ResourceModel resourceModel, AppConte
child: Padding(
padding: const EdgeInsets.only(left:8.0, right: 8.0, bottom: 8.0, top: 8.0),
child: PhotoView(
imageProvider: (appContext.getContext() as VisitAppContext).configuration!.isOffline! ?
Image.memory(base64Decode(resourceModel.data!)).image :
imageProvider: (appContext.getContext() as VisitAppContext).configuration!.isOffline! && resourceModelFile != null ?
Image.file(resourceModelFile).image : // GET FROM FILE
Image.network(resourceModel.source!).image,
minScale: PhotoViewComputedScale.contained * 1.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/Models/resourceModel.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/constants.dart';
import 'package:provider/provider.dart';
@ -69,45 +70,54 @@ class _SliderImagesWidget extends State<SliderImagesWidget> {
return Builder(
builder: (BuildContext context) {
//print(widget.imagesDTO[currentIndex-1]);
return Padding(
padding: const EdgeInsets.only(top: 5.0),
child: InkWell(
onTap: () {
showImagePopup(widget.contentsDTO[currentIndex.value-1]!, i!, appContext, context, size);
},
child: ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: visitAppContext.configuration!.isOffline! ?
Image.memory(base64Decode(i!.data!)) :
Image.network(
i!.source!,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(
color: kMainColor1,
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
return FutureBuilder(
future: ApiService.getResource(appContext, visitAppContext.configuration!, i!.id!),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
return Padding(
padding: const EdgeInsets.only(top: 5.0),
child: InkWell(
onTap: () {
showImagePopup(widget.contentsDTO[currentIndex.value-1]!, i, appContext, context, size, snapshot.data);
},
child: ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: visitAppContext.configuration!.isOffline! ?
snapshot.data != null ?
Image.file(
snapshot.data!,
fit: BoxFit.cover,
) : null :
Image.network(
i.source!,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(
color: kMainColor1,
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
) /*PhotoView(
imageProvider: Image.memory(base64Decode(i!.data!)).image,
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.contained * 3.0,
backgroundDecoration: BoxDecoration(
color: Colors.transparent,
/*shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(15.0),*/
),
);
},
) /*PhotoView(
imageProvider: Image.memory(base64Decode(i!.data!)).image,
minScale: PhotoViewComputedScale.contained * 0.8,
maxScale: PhotoViewComputedScale.contained * 3.0,
backgroundDecoration: BoxDecoration(
color: Colors.transparent,
/*shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(15.0),*/
)*/,
),
)*/,
),
),
),
);
}
);
},
);

View File

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

View File

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

View File

@ -3,17 +3,17 @@ import 'package:manager_api/api.dart';
class ResourceModel {
String? id = "";
String? data = "";
String? path = "";
String? source = "";
String? label = "";
ResourceType? type;
ResourceModel({this.id, this.data, this.source, this.type});
ResourceModel({this.id, this.path, this.source, this.type});
Map<String, dynamic> toMap() {
return {
'id': id,
'data': data,
'path': path,
'source': source,
'type': type?.value
};
@ -22,7 +22,7 @@ class ResourceModel {
factory ResourceModel.fromJson(Map<String, dynamic> json) {
return ResourceModel(
id: json['id'] as String,
data: json['data'] as String,
path: json['path'] as String,
source: json['source'] as String,
type: json['type'] as ResourceType
);
@ -30,6 +30,6 @@ class ResourceModel {
@override
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/beaconSection.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.dart';
import 'package:mymuseum_visitapp/client.dart';
class VisitAppContext with ChangeNotifier {
Client clientAPI = Client("https://api.myinfomate.be"); // Replace by https://api.mymuseum.be //http://192.168.31.140:8089
class VisitAppContext with ChangeNotifier{
String? id = "";
String? language = "";
String? instanceId = "63514fd67ed8c735aaa4b8f2"; // 63514fd67ed8c735aaa4b8f2 MyInfoMate test instance -- Fort de Saint-Héribert Mymuseum instance id : 633ee379d9405f32f166f047 // 63514fd67ed8c735aaa4b8f1 Mymuseum test
List<ConfigurationDTO>? configurations;
ConfigurationDTO? configuration;
List<String?>? sectionIds; // Use to valid QR code found
@ -26,6 +28,8 @@ class VisitAppContext with ChangeNotifier{
bool? isAdmin = false;
bool? isAllLanguages = false;
String? localPath;
VisitAppContext({this.language, this.id, this.configuration, this.isAdmin, this.isAllLanguages, this.instanceId});
Map<String, dynamic> toMap() {

View File

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

View File

@ -11,10 +11,9 @@ 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);
const AudioPlayerFloatingContainer({Key? key, required this.file, required this.resourceURl, required this.isAuto}) : super(key: key);
final File? file;
final Uint8List? audioBytes;
final String resourceURl;
final bool isAuto;
@ -36,10 +35,6 @@ class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContai
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!);
}
@ -174,16 +169,16 @@ class _AudioPlayerFloatingContainerState extends State<AudioPlayerFloatingContai
child: isplaying ? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.pause),
Text(currentpostlabel),
const Icon(Icons.pause, color: Colors.white),
Text(currentpostlabel, style: const TextStyle(color: Colors.white)),
],
) : audioplayed ? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.play_arrow),
Text(currentpostlabel),
const Icon(Icons.play_arrow, color: Colors.white),
Text(currentpostlabel, style: const TextStyle(color: Colors.white)),
],
): const Icon(Icons.play_arrow),
): const Icon(Icons.play_arrow, color: Colors.white),
/*Column(
children: [

View File

@ -36,6 +36,8 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
List<String?> alreadyDownloaded = [];
VisitAppContext? visitAppContext;
bool isDialogOpen = false;
@override
void initState() {
configurations = widget.configurations;
@ -130,22 +132,21 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
width: size.width * 0.3,
child: FutureBuilder(
future: ApiService.getResource(
appContext, configurations[index].imageId!),
appContext, configurations[index], configurations[index].imageId!),
builder:
(context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
return snapshot.data != null
? ClipRRect(
return ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20)),
child: snapshot.data.data != null
? Image.memory(
base64Decode(
snapshot.data.data!),
fit: BoxFit.cover)
: Image.network(
child: snapshot.data != null
? Image.file(
snapshot.data!,
fit: BoxFit.cover,
):
Image.network(
configurations[index]
.imageSource!,
fit: BoxFit.cover,
@ -174,8 +175,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
);
},
),
)
: const Text("");
);
} else if (snapshot.connectionState ==
ConnectionState.none) {
return Text(TranslationHelper.getFromLocale(
@ -270,7 +270,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
: TranslationHelper.getFromLocale(
"downloadPromptUpdate",
appContext.getContext()),
style: const TextStyle(color: kMainColor),
style: const TextStyle(color: kSecondGrey),
textAlign: TextAlign.center),
),
const SizedBox(
@ -279,7 +279,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
Text(
TranslationHelper.getFromLocale(
"downloadLanguage", appContext.getContext()),
style: const TextStyle(color: kMainColor),
style: const TextStyle(color: kSecondGrey),
textAlign: TextAlign.center),
const SizedBox(
height: 25,
@ -293,7 +293,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
child: Text(
TranslationHelper.getFromLocale(
"close", appContext.getContext()),
style: TextStyle(color: kMainColor)),
style: TextStyle(color: kSecondGrey)),
onPressed: () {
isCancel = true;
Navigator.of(context).pop();
@ -303,7 +303,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
child: Text(
TranslationHelper.getFromLocale(
"download", appContext.getContext()),
style: TextStyle(color: kMainColor)),
style: TextStyle(color: kSecondGrey)),
onPressed: () async {
Navigator.of(context).pop();
},
@ -316,7 +316,7 @@ class _ConfigurationsListState extends State<ConfigurationsList> {
//}
if (!isCancel) {
String loadingText = TranslationHelper.getFromLocale(
/*String loadingText = TranslationHelper.getFromLocale(
"downloadConfiguration", appContext.getContext());
showDialog(
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");
print(audiosNotWorking.length);
var result = await showDialog(
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.audiosNotWorking = [];
visitAppContext.audiosNotWorking = audiosNotWorking;
//visitAppContext.audiosNotWorking = audiosNotWorking;
appContext.setContext(visitAppContext);
}
}

View File

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

View File

@ -40,7 +40,7 @@ class _QuizzPageState extends State<QuizzPage> {
ResourceModel? audioResourceModel;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
late Uint8List audiobytes;
VisitAppContext? visitAppContext;
late VisitAppContext visitAppContext;
QuizzDTO? quizzDTO;
List<QuestionSubDTO> _questionsSubDTO = <QuestionSubDTO>[];
@ -67,7 +67,7 @@ class _QuizzPageState extends State<QuizzPage> {
@override
void dispose() {
visitAppContext!.isContentCurrentlyShown = false;
visitAppContext.isContentCurrentlyShown = false;
currentIndex = 1;
//_controllerCenter!.dispose();
if(quizzDTO != null) {
@ -94,7 +94,7 @@ class _QuizzPageState extends State<QuizzPage> {
body: OrientationBuilder(
builder: (context, orientation) {
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) {
if(quizzDTO != null && sectionDTO != null) {

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.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:photo_view/photo_view.dart';
import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
class BeaconArticleFound extends StatefulWidget {
const BeaconArticleFound({Key? key, required this.beaconSection}) : super(key: key);
@ -29,7 +31,7 @@ class BeaconArticleFound extends StatefulWidget {
}
class _BeaconArticleFoundState extends State<BeaconArticleFound> {
VisitAppContext? visitAppContext;
late VisitAppContext visitAppContext;
SectionDTO? sectionFound;
@override
@ -39,15 +41,15 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
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;
}
return FutureBuilder(
future: getSectionImage(appContext, appContext.clientAPI, sectionFound),
builder: (context, AsyncSnapshot<ResourceModel?> snapshot) {
future: getSectionImage(appContext, visitAppContext.clientAPI, sectionFound),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
return SizedBox(
height: size.height *0.4,
width: size.width *0.9,
@ -56,7 +58,7 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
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(
height: size.height * 0.25,
width: size.width * 0.75,
@ -65,30 +67,31 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(20)),
child: visitAppContext!.configuration!.isOffline! ?
Image.memory(
base64Decode(snapshot.data!.data!),
fit: BoxFit.cover
) :
Image.network(
snapshot.data!.source!,
fit: BoxFit.cover,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(
color: kMainColor1,
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
child: visitAppContext.configuration!.isOffline! ?
snapshot.data != null ?
Image.file(
snapshot.data!,
fit: BoxFit.cover,
) : null :
Image.network(
snapshot.data!.source!,
fit: BoxFit.cover,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(
color: kMainColor1,
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
),
),
),
@ -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) {
return null;
}
@ -128,7 +131,16 @@ class _BeaconArticleFoundState extends State<BeaconArticleFound> {
if(isConfigOffline) {
List<Map<String, dynamic>> resource = await DatabaseHelper.instance.queryWithColumnId(DatabaseTableType.resources, sectionFound.imageId!);
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 {
print("EMPTY resourcesModel - getSectionImage");
return null;

View File

@ -163,7 +163,7 @@ class _BodyState extends State<Body> {
else
{
// 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);
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

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

View File

@ -272,7 +272,7 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
content: BeaconArticleFound(beaconSection: beaconSection),
actions: <Widget>[
TextButton(
child: Text(TranslationHelper.getFromLocale("close", visitAppContext), style: TextStyle(color: kMainColor)),
child: Text(TranslationHelper.getFromLocale("close", visitAppContext), style: const TextStyle(color: kSecondGrey)),
onPressed: () {
_isDialogShowing = false; // set it `false` since dialog is closed
Navigator.of(context).pop();
@ -280,7 +280,7 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
},
),
TextButton(
child: Text(TranslationHelper.getFromLocale("open", visitAppContext), style: TextStyle(color: kMainColor)),
child: Text(TranslationHelper.getFromLocale("open", visitAppContext), style: const TextStyle(color: kSecondGrey)),
onPressed: () {
_isDialogShowing = false; // set it `false` since dialog is closed
Navigator.of(context).pop();
@ -364,147 +364,146 @@ class _VisitPageState extends State<VisitPage> with WidgetsBindingObserver {
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(right: 90, bottom: 1),
child: SizedBox(
height: 75.0,
width: 65.0,
child: FittedBox(
child: FloatingActionButton(
heroTag: "beacon",
onPressed: () async {
bool isCancel = false;
child: InkWell(
onTap: () async {
bool isCancel = false;
if(!controller.authorizationStatusOk) {
//await handleOpenLocationSettings();
if(!controller.authorizationStatusOk) {
//await handleOpenLocationSettings();
await showDialog(
context: context,
barrierDismissible: false,
builder: (_) {
return AlertDialog(
backgroundColor: Colors.white,
content: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: SizedBox(
height: 215,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Icons.my_location, color: kMainColor),
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(TranslationHelper.getFromLocale("locationWarning", visitAppContext), style: const TextStyle(color: kMainColor), textAlign: TextAlign.center),
),
],
await showDialog(
context: context,
barrierDismissible: false,
builder: (_) {
return AlertDialog(
backgroundColor: Colors.white,
content: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: SizedBox(
height: 215,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Icons.my_location, color: kSecondGrey),
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(TranslationHelper.getFromLocale("locationWarning", visitAppContext), style: const TextStyle(color: kSecondGrey), textAlign: TextAlign.center),
),
),
],
),
actions: <Widget>[
TextButton(
child: Text(TranslationHelper.getFromLocale("close", visitAppContext), style: TextStyle(color: kMainColor)),
onPressed: () {
isCancel = true;
Navigator.of(context).pop();
},
),
TextButton(
child: Text(TranslationHelper.getFromLocale("ok", visitAppContext), style: TextStyle(color: kMainColor)),
onPressed: () async {
Navigator.of(context).pop();
},
)
],
actionsAlignment: MainAxisAlignment.spaceAround,
contentPadding: EdgeInsets.zero,
);
});
),
),
actions: <Widget>[
TextButton(
child: Text(TranslationHelper.getFromLocale("close", visitAppContext), style: TextStyle(color: kSecondGrey)),
onPressed: () {
isCancel = true;
Navigator.of(context).pop();
},
),
TextButton(
child: Text(TranslationHelper.getFromLocale("ok", visitAppContext), style: TextStyle(color: kSecondGrey)),
onPressed: () async {
Navigator.of(context).pop();
},
)
],
actionsAlignment: MainAxisAlignment.spaceAround,
contentPadding: EdgeInsets.zero,
);
});
if(!isCancel) {
if(Platform.isIOS) {
Map<Permission, PermissionStatus> statuses0 = await [
Permission.bluetooth,
].request();
if(!isCancel) {
if(Platform.isIOS) {
Map<Permission, PermissionStatus> statuses0 = await [
Permission.bluetooth,
].request();
Map<Permission, PermissionStatus> statuses1 = await [
Permission.bluetoothScan,
].request();
Map<Permission, PermissionStatus> statuses1 = await [
Permission.bluetoothScan,
].request();
Map<Permission, PermissionStatus> statuses2 = await [
Permission.bluetoothConnect,
].request();
Map<Permission, PermissionStatus> statuses2 = await [
Permission.bluetoothConnect,
].request();
Map<Permission, PermissionStatus> statuses3 = await [
Permission.locationWhenInUse,
].request();
Map<Permission, PermissionStatus> statuses3 = await [
Permission.locationWhenInUse,
].request();
Map<Permission, PermissionStatus> statuses4 = await [
Permission.location,
].request();
Map<Permission, PermissionStatus> statuses4 = await [
Permission.location,
].request();
Map<Permission, PermissionStatus> statuses5 = await [
Permission.locationAlways,
].request();
Map<Permission, PermissionStatus> statuses5 = await [
Permission.locationAlways,
].request();
print(statuses0[Permission.bluetooth]);
print(statuses1[Permission.bluetoothScan]);
print(statuses2[Permission.bluetoothConnect]);
print(statuses3[Permission.locationWhenInUse]);
print(statuses4[Permission.location]);
print(statuses5[Permission.locationAlways]);
print(statuses0[Permission.bluetooth]);
print(statuses1[Permission.bluetoothScan]);
print(statuses2[Permission.bluetoothConnect]);
print(statuses3[Permission.locationWhenInUse]);
print(statuses4[Permission.location]);
print(statuses5[Permission.locationAlways]);
} else {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.location,
].request();
} else {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.location,
].request();
print(statuses[Permission.bluetoothScan]);
print(statuses[Permission.bluetoothConnect]);
print(statuses[Permission.location]);
print(statuses[Permission.locationWhenInUse]);
print(statuses[Permission.bluetoothScan]);
print(statuses[Permission.bluetoothConnect]);
print(statuses[Permission.location]);
print(statuses[Permission.locationWhenInUse]);
var status = await Permission.bluetoothScan.status;
print(status);
}
await listeningState();
}
var status = await Permission.bluetoothScan.status;
print(status);
}
await listeningState();
}
}
if(!isCancel) {
if(!controller.bluetoothEnabled) {
await handleOpenBluetooth();
}
if(!controller.locationServiceEnabled) {
await handleOpenLocationSettings();
}
if(!visitAppContext.isScanningBeacons) {
print("Start Scan");
print(_streamRanging);
if (_streamRanging != null) {
_streamRanging?.resume();
} else {
await initScanBeacon(visitAppContext);
}
if(!isCancel) {
if(!controller.bluetoothEnabled) {
await handleOpenBluetooth();
}
if(!controller.locationServiceEnabled) {
await handleOpenLocationSettings();
}
if(!visitAppContext.isScanningBeacons) {
print("Start Scan");
print(_streamRanging);
if (_streamRanging != null) {
_streamRanging?.resume();
} else {
await initScanBeacon(visitAppContext);
}
controller.startScanning();
visitAppContext.isScanningBeacons = true;
visitAppContext.isScanBeaconAlreadyAllowed = true;
appContext.setContext(visitAppContext);
} else {
print("Pause Scan");
controller.pauseScanning(); // PAUSE OR DISPOSE ?
visitAppContext.isScanningBeacons = false;
appContext.setContext(visitAppContext);
}
}
},
tooltip: 'Beacon',
backgroundColor: visitAppContext.isScanningBeacons ? kMainColor1 : Colors.grey,
child: const Icon(Icons.my_location),
controller.startScanning();
visitAppContext.isScanningBeacons = true;
visitAppContext.isScanBeaconAlreadyAllowed = true;
appContext.setContext(visitAppContext);
} else {
print("Pause Scan");
controller.pauseScanning(); // PAUSE OR DISPOSE ?
visitAppContext.isScanningBeacons = false;
appContext.setContext(visitAppContext);
}
}
},
child: Container(
decoration: BoxDecoration(
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/app_context.dart';
import 'package:mymuseum_visitapp/client.dart';
import 'package:path_provider/path_provider.dart';
class ApiService {
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); }
//print("AFTER");
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;
}
@ -132,7 +133,7 @@ class ApiService {
} else {
bool isOnline = await hasNetwork();
if(isOnline) {
ResourceModel? resourceModel = await downloadAudio(client, audioId);
ResourceModel? resourceModel = await downloadAudio(client, audioId, resourceTest.first[0]);
if(resourceModel != null) {
await DatabaseHelper.instance.insert(DatabaseTableType.resources, resourceModel.toMap());
}
@ -148,33 +149,43 @@ class ApiService {
}
}
static Future<ResourceModel?> downloadAudio(Client client, String audioId) async {
var url = "https://api.myinfomate.be/api/Resource/"+audioId; // TO TEST TODO UPDATE ROUTE
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
HttpClient client2 = HttpClient();
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();
await for(dynamic d in response) { _downloadData.addAll(d); }
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;
}
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!)
{
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
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) {
return DatabaseHelper.instance.getResourceFromDB(resourceTest.first);
} else {
return null;
}
} else
{
// ONLINE
return ResourceModel(); // To mock
}*/
}
// ONLINE
return null;
}
static Future<ExportConfigurationDTO?> exportConfiguration(Client client, String configurationId, String? language) async {

View File

@ -1,17 +1,329 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:manager_api/api.dart';
import 'package:mymuseum_visitapp/Helpers/DatabaseHelper.dart';
import 'package:mymuseum_visitapp/Helpers/modelsHelper.dart';
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
import 'package:mymuseum_visitapp/Models/resourceModel.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/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 {
VisitAppContext visitAppContext = (appContext.getContext() as VisitAppContext);
@ -35,7 +347,7 @@ class DownloadConfiguration {
ExportConfigurationDTO? exportConfigurationDTO;
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) {
print("Erreur lors du téléchargement de la visite");
print(e);
@ -144,6 +456,56 @@ class DownloadConfiguration {
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 {
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 {
VisitAppContext _visitContext;
Client clientAPI = Client("https://api.myinfomate.be"); // Replace by https://api.mymuseum.be //http://192.168.31.140:8089
VisitAppContext? _visitContext;
AppContext(this._visitContext);

View File

@ -15,12 +15,16 @@ import 'app_context.dart';
import 'constants.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:path_provider/path_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
String initialRoute;
VisitAppContext? localContext = await DatabaseHelper.instance.getData(DatabaseTableType.main);
Directory? appDocumentsDirectory = Platform.isIOS ? await getApplicationDocumentsDirectory() : await getDownloadsDirectory();
String localPath = appDocumentsDirectory!.path;
if(localContext != null) {
print("we've got an local db !");
print(localContext);
@ -38,6 +42,9 @@ void main() async {
print("NO LOCAL DB !");
}
localContext.localPath = localPath;
print("Local path $localPath");
initialRoute = '/home';
final MyApp myApp = MyApp(
@ -104,6 +111,9 @@ class _MyAppState extends State<MyApp> {
//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)),
visualDensity: VisualDensity.adaptivePlatformDensity,
appBarTheme: const AppBarTheme(
iconTheme: IconThemeData(color: Colors.white), // Change the color here
),
),
routes: {
'/home': (context) => const HomePage(),

View File

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

View File

@ -378,6 +378,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View File

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