Ce commit boucle le travail en cours sur la branche (assistant lunettes Meta, passage du lecteur audio flottant a un onglet, ajustements scanner / liste de configurations / telechargement) et y ajoute le badge de version. Le badge, en bas de la feuille Parametres, affiche « flavor . version . commit ». Un APK pose sur une tablette du terrain n'etait rattachable a aucun commit precis : la version du pubspec ne bougeait pas d'un build a l'autre et rien n'indiquait le flavor reellement installe. kGitSha suit le meme schema que kApiBaseUrl, injecte par --dart-define, et kFlavor expose le flavor deja calcule. package_info_plus etait deja une dependance transitive ; il devient direct, puisqu'il est desormais importe. /!\ Un --dart-define modifie n'est PAS pris en compte sans `flutter clean` sur ce projet : verifie a la sentinelle, le SHA restait absent de libapp.so tant que le cache Dart n'etait pas vide. Un build de release destine au terrain doit donc toujours passer par un clean, sinon le badge affiche le SHA du build precedent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
397 lines
17 KiB
Dart
397 lines
17 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:auto_size_text/auto_size_text.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
|
import 'package:manager_api_new/api.dart';
|
|
import 'package:mymuseum_visitapp/Components/LanguageSelection.dart';
|
|
import 'package:mymuseum_visitapp/Components/loading_common.dart';
|
|
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
|
|
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
|
import 'package:mymuseum_visitapp/Screens/ConfigurationPage/configuration_page.dart';
|
|
import 'package:mymuseum_visitapp/Services/apiService.dart';
|
|
import 'package:mymuseum_visitapp/Services/downloadConfiguration.dart';
|
|
import 'package:mymuseum_visitapp/app_context.dart';
|
|
import 'package:mymuseum_visitapp/constants.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class ConfigurationsList extends StatefulWidget {
|
|
const ConfigurationsList(
|
|
{Key? key,
|
|
required this.configurations,
|
|
required this.alreadyDownloaded,
|
|
required this.requestRefresh})
|
|
: super(key: key);
|
|
|
|
final List<ConfigurationDTO> configurations;
|
|
final List<String?> alreadyDownloaded;
|
|
final Function requestRefresh;
|
|
|
|
@override
|
|
State<ConfigurationsList> createState() => _ConfigurationsListState();
|
|
}
|
|
|
|
class _ConfigurationsListState extends State<ConfigurationsList> {
|
|
List<ConfigurationDTO> configurations = [];
|
|
List<String?> alreadyDownloaded = [];
|
|
VisitAppContext? visitAppContext;
|
|
|
|
bool isDialogOpen = false;
|
|
|
|
@override
|
|
void initState() {
|
|
configurations = widget.configurations;
|
|
alreadyDownloaded = widget.alreadyDownloaded;
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
Size size = MediaQuery.of(context).size;
|
|
final appContext = Provider.of<AppContext>(context);
|
|
visitAppContext = appContext.getContext();
|
|
|
|
return ListView.builder(
|
|
shrinkWrap:
|
|
true, //I've set this as true here depending on what your listview content is
|
|
//physics: NeverScrollableScrollPhysics(),//This prevents scrolling, but may inhibit refresh indicator, remove as you need
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return InkWell(
|
|
onTap: () async {
|
|
VisitAppContext visitAppContext = appContext.getContext();
|
|
if (configurations[index].isOffline! &&
|
|
alreadyDownloaded.any((c) => c == configurations[index].id)) {
|
|
if (!configurations[index]
|
|
.languages!
|
|
.contains(visitAppContext.language)) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(TranslationHelper.getFromLocale(
|
|
"languageNotSupported", appContext.getContext())),
|
|
backgroundColor: kMainColor2),
|
|
);
|
|
} else {
|
|
// Update context
|
|
visitAppContext.configuration = configurations[index];
|
|
visitAppContext.sectionIds = configurations[index].sectionIds;
|
|
|
|
appContext.setContext(visitAppContext);
|
|
|
|
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
|
builder: (context) =>
|
|
ConfigurationPage(configuration: configurations[index], isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
|
|
));
|
|
|
|
}
|
|
} else {
|
|
if (configurations[index].isOffline!) {
|
|
// Not already downloaded
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(TranslationHelper.getFromLocale(
|
|
"visitDownloadWarning", appContext.getContext())),
|
|
backgroundColor: kMainColor2),
|
|
);
|
|
} else {
|
|
// Online mode
|
|
if(!configurations[index].languages!.contains(visitAppContext.language))
|
|
{
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(TranslationHelper.getFromLocale(
|
|
"languageNotSupported", appContext.getContext())),
|
|
backgroundColor: kMainColor2),
|
|
);
|
|
} else {
|
|
visitAppContext.configuration = configurations[index];
|
|
visitAppContext.sectionIds = configurations[index].sectionIds;
|
|
|
|
appContext.setContext(visitAppContext);
|
|
|
|
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
|
builder: (context) =>
|
|
ConfigurationPage(configuration: configurations[index], isAlreadyAllowed: visitAppContext.isScanBeaconAlreadyAllowed),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
child: Container(
|
|
height: size.height * 0.15,
|
|
decoration: boxDecoration(configurations[index], false),
|
|
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
|
child: Stack(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
if (configurations[index].imageId != null)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
height: 136,
|
|
// image is square but we add extra 20 + 20 padding thats why width is 200
|
|
width: size.width * 0.3,
|
|
child: FutureBuilder(
|
|
future: ApiService.getResource(
|
|
appContext, configurations[index], configurations[index].imageId!),
|
|
builder:
|
|
(context, AsyncSnapshot<dynamic> snapshot) {
|
|
if (snapshot.connectionState ==
|
|
ConnectionState.done) {
|
|
return ClipRRect(
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(20),
|
|
bottomLeft: Radius.circular(20)),
|
|
child: snapshot.data != null
|
|
? Image.file(
|
|
snapshot.data!,
|
|
fit: BoxFit.cover,
|
|
):
|
|
Image.network(
|
|
configurations[index]
|
|
.imageSource!,
|
|
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,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
} else if (snapshot.connectionState ==
|
|
ConnectionState.none) {
|
|
return Text(TranslationHelper.getFromLocale(
|
|
"noData", appContext.getContext()));
|
|
} else {
|
|
return Center(
|
|
child: SizedBox(
|
|
height: size.height * 0.15,
|
|
child: const LoadingCommon()));
|
|
}
|
|
}),
|
|
),
|
|
Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Container(
|
|
width: size.width * 0.45,
|
|
child: Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(left: 10),
|
|
child: HtmlWidget(
|
|
TranslationHelper.get(
|
|
configurations[index].title,
|
|
appContext.getContext()),
|
|
textStyle: const TextStyle(
|
|
fontSize: kMenuDescriptionDetailSize,
|
|
),
|
|
/*maxFontSize: 18,
|
|
maxLines: 2,*/
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (configurations[index].isOffline!)
|
|
Positioned(
|
|
bottom: 0,
|
|
right: 0,
|
|
child: Container(
|
|
width: 45,
|
|
height: 45,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.rectangle,
|
|
color: kMainColor1,
|
|
borderRadius: BorderRadius.circular(20.0),
|
|
),
|
|
margin: const EdgeInsets.all(8),
|
|
child: InkWell(
|
|
onTap: () async {
|
|
await downloadClicked(context, appContext, configurations[index]);
|
|
widget.requestRefresh();
|
|
},
|
|
child: configurations[index].isOffline! &&
|
|
!alreadyDownloaded.any(
|
|
(c) => c == configurations[index].id)
|
|
? const Icon(Icons.download,
|
|
color: Colors.white)
|
|
: const Icon(Icons.refresh,
|
|
color: Colors.white),
|
|
),
|
|
))
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
itemCount: configurations.length);
|
|
}
|
|
|
|
Future<void> downloadClicked(BuildContext buildContext, AppContext appContext, ConfigurationDTO configuration) async {
|
|
bool isCancel = false;
|
|
//if(!alreadyDownloaded.any((c) => c == configuration.id)) {
|
|
await showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (_) {
|
|
return AlertDialog(
|
|
backgroundColor: Colors.white,
|
|
content: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(
|
|
!alreadyDownloaded.any((c) => c == configuration.id)
|
|
? TranslationHelper.getFromLocale(
|
|
"downloadPrompt", appContext.getContext())
|
|
: TranslationHelper.getFromLocale(
|
|
"downloadPromptUpdate",
|
|
appContext.getContext()),
|
|
style: const TextStyle(color: kSecondGrey),
|
|
textAlign: TextAlign.center),
|
|
),
|
|
const SizedBox(
|
|
height: 15,
|
|
),
|
|
Text(
|
|
TranslationHelper.getFromLocale(
|
|
"downloadLanguage", appContext.getContext()),
|
|
style: const TextStyle(color: kSecondGrey),
|
|
textAlign: TextAlign.center),
|
|
const SizedBox(
|
|
height: 25,
|
|
),
|
|
LanguageSelection(configurationDTO: configuration)
|
|
],
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
TextButton(
|
|
child: Text(
|
|
TranslationHelper.getFromLocale(
|
|
"close", appContext.getContext()),
|
|
style: TextStyle(color: kSecondGrey)),
|
|
onPressed: () {
|
|
isCancel = true;
|
|
Navigator.of(context).pop();
|
|
},
|
|
),
|
|
TextButton(
|
|
child: Text(
|
|
TranslationHelper.getFromLocale(
|
|
"download", appContext.getContext()),
|
|
style: TextStyle(color: kSecondGrey)),
|
|
onPressed: () async {
|
|
Navigator.of(context).pop();
|
|
},
|
|
)
|
|
],
|
|
actionsAlignment: MainAxisAlignment.spaceAround,
|
|
contentPadding: EdgeInsets.zero,
|
|
);
|
|
});
|
|
//}
|
|
|
|
if (!isCancel) {
|
|
/*String loadingText = TranslationHelper.getFromLocale(
|
|
"downloadConfiguration", appContext.getContext());
|
|
showDialog(
|
|
barrierDismissible: false,
|
|
context: context,
|
|
builder: (_) {
|
|
return Dialog(
|
|
backgroundColor: Colors.white,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const CircularProgressIndicator(),
|
|
const SizedBox(
|
|
height: 15,
|
|
),
|
|
Text(loadingText)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
});*/
|
|
|
|
// DIALOG HERE
|
|
if(!isDialogOpen) {
|
|
isDialogOpen = true;
|
|
|
|
var result = await showDialog(
|
|
builder: (BuildContext dialogContext) => AlertDialog(
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(20.0))
|
|
),
|
|
content: SizedBox(
|
|
width: 350,
|
|
child: DownloadConfigurationWidget(configuration: configuration),
|
|
),
|
|
), context: context
|
|
);
|
|
isDialogOpen = false;
|
|
} else {
|
|
print("ALREADY OPEN LAAAA");
|
|
}
|
|
|
|
//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;
|
|
appContext.setContext(visitAppContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
boxDecoration(ConfigurationDTO configuration, bool isSelected) {
|
|
return BoxDecoration(
|
|
color: Colors.white,
|
|
shape: BoxShape.rectangle,
|
|
borderRadius: BorderRadius.circular(20.0),
|
|
border: Border.all(
|
|
color: kMainColor0.withOpacity(0.35),
|
|
width: 0.2,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: kBackgroundGrey.withOpacity(0.35),
|
|
//spreadRadius: 0.15,
|
|
blurRadius: 27,
|
|
offset: const Offset(0, 15), // changes position of shadow
|
|
),
|
|
],
|
|
);
|
|
}
|