Agenda + misc for sub menu (pdf, agenda)
This commit is contained in:
parent
f07570d8ee
commit
c50083b19f
128
lib/Models/agenda.dart
Normal file
128
lib/Models/agenda.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class Agenda {
|
||||||
|
List<EventAgenda> events;
|
||||||
|
|
||||||
|
Agenda({required this.events});
|
||||||
|
|
||||||
|
factory Agenda.fromJson(String jsonString) {
|
||||||
|
final List<dynamic> jsonList = json.decode(jsonString);
|
||||||
|
List<EventAgenda> events = [];
|
||||||
|
|
||||||
|
for (var eventData in jsonList) {
|
||||||
|
try {
|
||||||
|
events.add(EventAgenda.fromJson(eventData));
|
||||||
|
} catch(e) {
|
||||||
|
print("Erreur lors du parsing du json : ${e.toString()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Agenda(events: events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventAgenda {
|
||||||
|
String? name;
|
||||||
|
String? description;
|
||||||
|
String? type;
|
||||||
|
DateTime? dateAdded;
|
||||||
|
DateTime? dateFrom;
|
||||||
|
DateTime? dateTo;
|
||||||
|
String? dateHour;
|
||||||
|
EventAddress? address;
|
||||||
|
String? website;
|
||||||
|
String? phone;
|
||||||
|
String? idVideoYoutube;
|
||||||
|
String? email;
|
||||||
|
String? image;
|
||||||
|
|
||||||
|
EventAgenda({
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
required this.type,
|
||||||
|
required this.dateAdded,
|
||||||
|
required this.dateFrom,
|
||||||
|
required this.dateTo,
|
||||||
|
required this.dateHour,
|
||||||
|
required this.address,
|
||||||
|
required this.website,
|
||||||
|
required this.phone,
|
||||||
|
required this.idVideoYoutube,
|
||||||
|
required this.email,
|
||||||
|
required this.image,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory EventAgenda.fromJson(Map<String, dynamic> json) {
|
||||||
|
return EventAgenda(
|
||||||
|
name: json['name'],
|
||||||
|
description: json['description'],
|
||||||
|
type: json['type'] is !bool ? json['type'] : null,
|
||||||
|
dateAdded: json['date_added'] != null && json['date_added'].isNotEmpty ? DateTime.parse(json['date_added']) : null,
|
||||||
|
dateFrom: json['date_from'] != null && json['date_from'].isNotEmpty ? DateTime.parse(json['date_from']) : null,
|
||||||
|
dateTo: json['date_to'] != null && json['date_to'].isNotEmpty ? DateTime.parse(json['date_to']) : null,
|
||||||
|
dateHour: json['date_hour'],
|
||||||
|
address: json['address'] is !bool ? EventAddress.fromJson(json['address']) : null,
|
||||||
|
website: json['website'],
|
||||||
|
phone: json['phone'],
|
||||||
|
idVideoYoutube: json['id_video_youtube'],
|
||||||
|
email: json['email'],
|
||||||
|
image: json['image'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventAddress {
|
||||||
|
String? address;
|
||||||
|
dynamic lat;
|
||||||
|
dynamic lng;
|
||||||
|
int? zoom;
|
||||||
|
String? placeId;
|
||||||
|
String? name;
|
||||||
|
String? streetNumber;
|
||||||
|
String? streetName;
|
||||||
|
String? streetNameShort;
|
||||||
|
String? city;
|
||||||
|
String? state;
|
||||||
|
String? stateShort;
|
||||||
|
String? postCode;
|
||||||
|
String? country;
|
||||||
|
String? countryShort;
|
||||||
|
|
||||||
|
EventAddress({
|
||||||
|
required this.address,
|
||||||
|
required this.lat,
|
||||||
|
required this.lng,
|
||||||
|
required this.zoom,
|
||||||
|
required this.placeId,
|
||||||
|
required this.name,
|
||||||
|
required this.streetNumber,
|
||||||
|
required this.streetName,
|
||||||
|
required this.streetNameShort,
|
||||||
|
required this.city,
|
||||||
|
required this.state,
|
||||||
|
required this.stateShort,
|
||||||
|
required this.postCode,
|
||||||
|
required this.country,
|
||||||
|
required this.countryShort,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory EventAddress.fromJson(Map<String, dynamic> json) {
|
||||||
|
return EventAddress(
|
||||||
|
address: json['address'],
|
||||||
|
lat: json['lat'],
|
||||||
|
lng: json['lng'],
|
||||||
|
zoom: json['zoom'],
|
||||||
|
placeId: json['place_id'],
|
||||||
|
name: json['name'],
|
||||||
|
streetNumber: json['street_number'],
|
||||||
|
streetName: json['street_name'],
|
||||||
|
streetNameShort: json['street_name_short'],
|
||||||
|
city: json['city'],
|
||||||
|
state: json['state'],
|
||||||
|
stateShort: json['state_short'],
|
||||||
|
postCode: json['post_code'],
|
||||||
|
country: json['country'],
|
||||||
|
countryShort: json['country_short'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
198
lib/Screens/Sections/Agenda/agenda_page.dart
Normal file
198
lib/Screens/Sections/Agenda/agenda_page.dart
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
//import 'dart:html';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:manager_api_new/api.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Components/loading_common.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Models/agenda.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Screens/Sections/Agenda/event_list_item.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Screens/Sections/Agenda/event_popup.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Screens/Sections/Agenda/month_filter.dart';
|
||||||
|
import 'package:mymuseum_visitapp/app_context.dart';
|
||||||
|
import 'package:mymuseum_visitapp/constants.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class AgendaPage extends StatefulWidget {
|
||||||
|
final AgendaDTO section;
|
||||||
|
AgendaPage({required this.section});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AgendaPage createState() => _AgendaPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AgendaPage extends State<AgendaPage> {
|
||||||
|
AgendaDTO agendaDTO = AgendaDTO();
|
||||||
|
late Agenda agenda;
|
||||||
|
late ValueNotifier<List<EventAgenda>> filteredAgenda = ValueNotifier<List<EventAgenda>>([]);
|
||||||
|
late Uint8List mapIcon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
/*print(widget.section!.data);
|
||||||
|
agendaDTO = AgendaDTO.fromJson(jsonDecode(widget.section!.data!))!;
|
||||||
|
print(agendaDTO);*/
|
||||||
|
agendaDTO = widget.section;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Agenda?> getAndParseJsonInfo(VisitAppContext visitAppContext) async {
|
||||||
|
try {
|
||||||
|
// Récupération du contenu JSON depuis l'URL
|
||||||
|
var httpClient = HttpClient();
|
||||||
|
|
||||||
|
// We need to get detail to get url from resourceId
|
||||||
|
var resourceIdForSelectedLanguage = agendaDTO.resourceIds!.where((ri) => ri.language == visitAppContext.language).first.value;
|
||||||
|
ResourceDTO? resourceDTO = await visitAppContext.clientAPI.resourceApi!.resourceGetDetail(resourceIdForSelectedLanguage!);
|
||||||
|
|
||||||
|
var request = await httpClient.getUrl(Uri.parse(resourceDTO!.url!));
|
||||||
|
var response = await request.close();
|
||||||
|
var jsonString = await response.transform(utf8.decoder).join();
|
||||||
|
|
||||||
|
agenda = Agenda.fromJson(jsonString);
|
||||||
|
agenda.events = agenda.events.where((a) => a.dateFrom != null && a.dateFrom!.isAfter(DateTime.now())).toList();
|
||||||
|
agenda.events.sort((a, b) => a.dateFrom!.compareTo(b.dateFrom!));
|
||||||
|
filteredAgenda.value = agenda.events;
|
||||||
|
|
||||||
|
mapIcon = await getByteIcon();
|
||||||
|
|
||||||
|
return agenda;
|
||||||
|
} catch(e) {
|
||||||
|
print("Erreur lors du parsing du json : ${e.toString()}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getByteIcon() async {
|
||||||
|
final ByteData bytes = await rootBundle.load('assets/icons/marker.png');
|
||||||
|
var icon = await getBytesFromAsset(bytes, 25);
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Uint8List> getBytesFromAsset(ByteData data, int width) async {
|
||||||
|
//ByteData data = await rootBundle.load(path);
|
||||||
|
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(),
|
||||||
|
targetWidth: width);
|
||||||
|
ui.FrameInfo fi = await codec.getNextFrame();
|
||||||
|
return (await fi.image.toByteData(format: ui.ImageByteFormat.png))
|
||||||
|
!.buffer
|
||||||
|
.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Size size = MediaQuery.of(context).size;
|
||||||
|
final VisitAppContext visitAppContext = Provider.of<AppContext>(context).getContext();
|
||||||
|
|
||||||
|
return FutureBuilder(future: getAndParseJsonInfo(visitAppContext),
|
||||||
|
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Center(
|
||||||
|
child: Text("Le fichier choisi n'est pas valide")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
color: kBackgroundLight,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0, bottom: 2.0, top: 2.0),
|
||||||
|
child: ValueListenableBuilder<List<EventAgenda>>(
|
||||||
|
valueListenable: filteredAgenda,
|
||||||
|
builder: (context, value, _) {
|
||||||
|
return GridView.builder(
|
||||||
|
scrollDirection: Axis.vertical, // Changer pour horizontal si nécessaire
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2, // Nombre de colonnes dans la grid
|
||||||
|
crossAxisSpacing: 2.0, // Espace entre les colonnes
|
||||||
|
mainAxisSpacing: 2.0, // Espace entre les lignes
|
||||||
|
childAspectRatio: 0.65, // Aspect ratio des enfants de la grid
|
||||||
|
),
|
||||||
|
itemCount: value.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
EventAgenda eventAgenda = value[index];
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
print("${eventAgenda.name}");
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return EventPopup(eventAgenda: eventAgenda, mapProvider: agendaDTO.agendaMapProvider ?? MapProvider.Google, mapIcon: mapIcon);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: EventListItem(
|
||||||
|
eventAgenda: eventAgenda,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: MonthFilter(
|
||||||
|
events: snapshot.data.events,
|
||||||
|
onMonthSelected: (filteredList) {
|
||||||
|
print('events sélectionné: $filteredList');
|
||||||
|
var result = filteredList != null ? filteredList : <EventAgenda>[];
|
||||||
|
result.sort((a, b) => a.dateFrom!.compareTo(b.dateFrom!));
|
||||||
|
filteredAgenda.value = result;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 35,
|
||||||
|
left: 10,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: kMainColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.arrow_back, size: 23, color: Colors.white)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (snapshot.connectionState == ConnectionState.none) {
|
||||||
|
return Text("No data");
|
||||||
|
} else {
|
||||||
|
return Center(
|
||||||
|
child: Container(
|
||||||
|
height: size.height * 0.2,
|
||||||
|
child: LoadingCommon()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} //_webView
|
||||||
163
lib/Screens/Sections/Agenda/event_list_item.dart
Normal file
163
lib/Screens/Sections/Agenda/event_list_item.dart
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Components/loading_common.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Models/agenda.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||||||
|
import 'package:mymuseum_visitapp/app_context.dart';
|
||||||
|
import 'package:mymuseum_visitapp/constants.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class EventListItem extends StatelessWidget {
|
||||||
|
final EventAgenda eventAgenda;
|
||||||
|
|
||||||
|
EventListItem({super.key, required this.eventAgenda});
|
||||||
|
|
||||||
|
final DateFormat formatter = DateFormat('dd/MM/yyyy');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final appContext = Provider.of<AppContext>(context);
|
||||||
|
VisitAppContext visitAppContext = appContext.getContext();
|
||||||
|
var primaryColor = visitAppContext.configuration != null ? visitAppContext.configuration!.primaryColor != null ? new Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16)) : kSecondColor : kSecondColor;
|
||||||
|
|
||||||
|
Size size = MediaQuery
|
||||||
|
.of(context)
|
||||||
|
.size;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(10.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0),
|
||||||
|
//color: Colors.red,
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
offset: Offset(0.0, 2.0),
|
||||||
|
blurRadius: 6.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
//width: size.width * 0.5, //210.0,
|
||||||
|
//constraints: const BoxConstraints(maxWidth: 210, maxHeight: 100),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: size.height * 0.18, // must be same ref0
|
||||||
|
constraints: const BoxConstraints(maxHeight: 250),
|
||||||
|
width: size.width*1,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
eventAgenda.image != null ? ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), topRight: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
|
||||||
|
child: Container(
|
||||||
|
//color: Colors.green,
|
||||||
|
//constraints: const BoxConstraints(maxHeight: 175),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: eventAgenda.image!,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height * 0.2, // must be same ref0
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
progressIndicatorBuilder: (context, url, downloadProgress) {
|
||||||
|
return Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
child: LoadingCommon(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorWidget: (context, url, error) => Icon(Icons.error),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
): SizedBox(),
|
||||||
|
Positioned(
|
||||||
|
right: 0.0,
|
||||||
|
bottom: 0.0,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: primaryColor,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 10.0,
|
||||||
|
right: 10.0,
|
||||||
|
top: 2.0,
|
||||||
|
bottom: 2.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const Icon(
|
||||||
|
Icons.calendar_today_rounded,
|
||||||
|
size: 10.0,
|
||||||
|
color: kBackgroundColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5.0),
|
||||||
|
Text(
|
||||||
|
eventAgenda.dateFrom!.isAtSameMomentAs(eventAgenda.dateTo!) ? "${formatter.format(eventAgenda.dateFrom!)}": "${formatter.format(eventAgenda.dateFrom!)} - ${formatter.format(eventAgenda.dateTo!)}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: kBackgroundColor,
|
||||||
|
fontSize: 12
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
/*height: size.height * 0.13,
|
||||||
|
constraints: BoxConstraints(maxHeight: 120),*/
|
||||||
|
child: Container(
|
||||||
|
width: size.width,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), bottomRight: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
|
||||||
|
border: Border(top: BorderSide(width: 0.1, color: kMainGrey))
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
HtmlWidget(
|
||||||
|
'<div style="text-align: center;">${eventAgenda.name!.length > 75 ? eventAgenda.name!.substring(0, 75) + " ..." : eventAgenda.name}</div>',
|
||||||
|
customStylesBuilder: (element) {
|
||||||
|
return {'text-align': 'center', 'font-family': "Roboto"};
|
||||||
|
},
|
||||||
|
textStyle: TextStyle(fontSize: 14.0),
|
||||||
|
),
|
||||||
|
/*AutoSizeText(
|
||||||
|
eventAgenda.type!,
|
||||||
|
maxFontSize: 12.0,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.0,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),*/
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
475
lib/Screens/Sections/Agenda/event_popup.dart
Normal file
475
lib/Screens/Sections/Agenda/event_popup.dart
Normal file
@ -0,0 +1,475 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' as mapBox;
|
||||||
|
import 'package:manager_api_new/api.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Components/video_viewer_youtube.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Models/agenda.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||||||
|
import 'package:mymuseum_visitapp/app_context.dart';
|
||||||
|
import 'package:mymuseum_visitapp/constants.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class EventPopup extends StatefulWidget {
|
||||||
|
final EventAgenda eventAgenda;
|
||||||
|
final MapProvider mapProvider;
|
||||||
|
final Uint8List mapIcon;
|
||||||
|
|
||||||
|
EventPopup({Key? key, required this.eventAgenda, required this.mapProvider, required this.mapIcon}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EventPopup> createState() => _EventPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EventPopupState extends State<EventPopup> {
|
||||||
|
final DateFormat formatter = DateFormat('dd/MM/yyyy hh:mm');
|
||||||
|
Completer<GoogleMapController> _controller = Completer();
|
||||||
|
Set<Marker> markers = {};
|
||||||
|
bool init = false;
|
||||||
|
|
||||||
|
mapBox.MapboxMap? mapboxMap;
|
||||||
|
mapBox.PointAnnotationManager? pointAnnotationManager;
|
||||||
|
|
||||||
|
Set<Marker> getMarkers() {
|
||||||
|
markers = {};
|
||||||
|
|
||||||
|
if (widget.eventAgenda.address!.lat != null && widget.eventAgenda.address!.lng != null) {
|
||||||
|
markers.add(Marker(
|
||||||
|
draggable: false,
|
||||||
|
markerId: MarkerId(widget.eventAgenda.address!.lat.toString() +
|
||||||
|
widget.eventAgenda.address!.lng.toString()),
|
||||||
|
position: LatLng(
|
||||||
|
double.parse(widget.eventAgenda.address!.lat!.toString()),
|
||||||
|
double.parse(widget.eventAgenda.address!.lng!.toString()),
|
||||||
|
),
|
||||||
|
icon: BitmapDescriptor.defaultMarker,
|
||||||
|
infoWindow: InfoWindow.noText));
|
||||||
|
}
|
||||||
|
return markers;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMapCreated(mapBox.MapboxMap mapboxMap, Uint8List icon) {
|
||||||
|
this.mapboxMap = mapboxMap;
|
||||||
|
|
||||||
|
mapboxMap.annotations.createPointAnnotationManager().then((pointAnnotationManager) async {
|
||||||
|
this.pointAnnotationManager = pointAnnotationManager;
|
||||||
|
pointAnnotationManager.createMulti(createPoints(LatLng(double.parse(widget.eventAgenda.address!.lat!.toString()), double.parse(widget.eventAgenda.address!.lng!.toString())), icon));
|
||||||
|
init = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPoints(LatLng position, Uint8List icon) {
|
||||||
|
var options = <mapBox.PointAnnotationOptions>[];
|
||||||
|
options.add(mapBox.PointAnnotationOptions(
|
||||||
|
geometry: mapBox.Point(
|
||||||
|
coordinates: mapBox.Position(
|
||||||
|
position.longitude,
|
||||||
|
position.latitude,
|
||||||
|
)), // .toJson()
|
||||||
|
iconSize: 1.3,
|
||||||
|
iconOffset: [0.0, 0.0],
|
||||||
|
symbolSortKey: 10,
|
||||||
|
iconColor: 0,
|
||||||
|
iconImage: null,
|
||||||
|
image: icon, //widget.selectedMarkerIcon,
|
||||||
|
));
|
||||||
|
print(options.length);
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> openEmail(String email) async {
|
||||||
|
final Uri emailLaunchUri = Uri(
|
||||||
|
scheme: 'mailto',
|
||||||
|
path: email,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await launchUrl(emailLaunchUri, mode: LaunchMode.externalApplication);
|
||||||
|
} catch (e) {
|
||||||
|
print('Erreur lors de l\'ouverture de l\'email: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> openPhone(String phone) async {
|
||||||
|
final Uri phoneLaunchUri = Uri(
|
||||||
|
scheme: 'tel',
|
||||||
|
path: phone,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await launchUrl(phoneLaunchUri, mode: LaunchMode.externalApplication);
|
||||||
|
} catch (e) {
|
||||||
|
print('Erreur lors de l\'ouverture de l\'email: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final appContext = Provider.of<AppContext>(context);
|
||||||
|
VisitAppContext visitAppContext = appContext.getContext();
|
||||||
|
|
||||||
|
var dateToShow = widget.eventAgenda.dateFrom!.isAtSameMomentAs(widget.eventAgenda.dateTo!) ? "${formatter.format(widget.eventAgenda.dateFrom!)}": "${formatter.format(widget.eventAgenda.dateFrom!)} - ${formatter.format(widget.eventAgenda.dateTo!)}";
|
||||||
|
Size size = MediaQuery.of(context).size;
|
||||||
|
|
||||||
|
if(!init) {
|
||||||
|
print("getmarkers in build");
|
||||||
|
getMarkers();
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Dialog(
|
||||||
|
insetPadding: const EdgeInsets.all(4.0),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0))
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
|
||||||
|
color: kBackgroundColor,
|
||||||
|
),
|
||||||
|
height: size.height * 0.92,
|
||||||
|
width: size.width * 0.95,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0), topRight: Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: kMainGrey,
|
||||||
|
spreadRadius: 0.5,
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: Offset(0, 1), // changes position of shadow
|
||||||
|
),
|
||||||
|
],
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
begin: Alignment.centerRight,
|
||||||
|
end: Alignment.centerLeft,
|
||||||
|
colors: [
|
||||||
|
kMainColor0,
|
||||||
|
kMainColor1,
|
||||||
|
kMainColor2,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
border: const Border(right: BorderSide(width: 0.05, color: kMainGrey)),
|
||||||
|
color: Colors.grey,
|
||||||
|
image: widget.eventAgenda.image != null ? DecorationImage(
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
opacity: 0.4,
|
||||||
|
image: NetworkImage(
|
||||||
|
widget.eventAgenda.image!,
|
||||||
|
),
|
||||||
|
): null
|
||||||
|
),
|
||||||
|
width: size.width,
|
||||||
|
height: 125,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10, right: 10, top: 4),
|
||||||
|
child: Center(
|
||||||
|
child: HtmlWidget(
|
||||||
|
'<div style="text-align: center;">${widget.eventAgenda.name!}</div>',
|
||||||
|
textStyle: const TextStyle(fontSize: 20.0, color: Colors.white),
|
||||||
|
customStylesBuilder: (element)
|
||||||
|
{
|
||||||
|
return {'text-align': 'center', 'font-family': "Roboto", '-webkit-line-clamp': "2"};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 125),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kSecondColor.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Wrap(
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
spacing: 6.0,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.calendar_today_rounded, color: kSecondColor, size: 18),
|
||||||
|
Text(
|
||||||
|
dateToShow,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: kSecondColor,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(minHeight: 250, maxHeight: size.height * 0.38),
|
||||||
|
width: size.width,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kBackgroundLight,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0))
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 20, right: 10, bottom: 10, top: 15),
|
||||||
|
child: Scrollbar(
|
||||||
|
thumbVisibility: true,
|
||||||
|
thickness: 2.0,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
HtmlWidget(
|
||||||
|
widget.eventAgenda.description!,
|
||||||
|
customStylesBuilder: (element) {
|
||||||
|
return {'text-align': 'left', 'font-family': "Roboto"};
|
||||||
|
},
|
||||||
|
textStyle: const TextStyle(fontSize: 15.0),
|
||||||
|
),
|
||||||
|
widget.eventAgenda.idVideoYoutube != null && widget.eventAgenda.idVideoYoutube!.isNotEmpty ?
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 250,
|
||||||
|
width: 350,
|
||||||
|
child: VideoViewerYoutube(videoUrl: "https://www.youtube.com/watch?v=${widget.eventAgenda.idVideoYoutube}", isAuto: false, webView: true)
|
||||||
|
),
|
||||||
|
) :
|
||||||
|
SizedBox(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
widget.eventAgenda.address!.lat != null && widget.eventAgenda.address!.lng != null ?
|
||||||
|
SizedBox(
|
||||||
|
width: size.width,
|
||||||
|
height: size.height * 0.2,
|
||||||
|
child: widget.mapProvider == MapProvider.Google ?
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
|
||||||
|
child: GoogleMap(
|
||||||
|
mapToolbarEnabled: false,
|
||||||
|
initialCameraPosition: CameraPosition(
|
||||||
|
target: LatLng(double.parse(widget.eventAgenda.address!.lat!.toString()), double.parse(widget.eventAgenda.address!.lng!.toString())),
|
||||||
|
zoom: 14,
|
||||||
|
),
|
||||||
|
onMapCreated: (GoogleMapController controller) {
|
||||||
|
_controller.complete(controller);
|
||||||
|
},
|
||||||
|
markers: markers,
|
||||||
|
),
|
||||||
|
) :
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(visitAppContext.configuration!.roundedValue?.toDouble() ?? 20.0)),
|
||||||
|
child: mapBox.MapWidget(
|
||||||
|
key: ValueKey("mapBoxWidget"),
|
||||||
|
styleUri: mapBox.MapboxStyles.STANDARD,
|
||||||
|
onMapCreated: (maBoxMap) {
|
||||||
|
_onMapCreated(maBoxMap, widget.mapIcon);
|
||||||
|
},
|
||||||
|
cameraOptions: mapBox.CameraOptions(
|
||||||
|
center: mapBox.Point(coordinates: mapBox.Position(double.parse(widget.eventAgenda.address!.lng!.toString()), double.parse(widget.eventAgenda.address!.lat!.toString()))), // .toJson()
|
||||||
|
zoom: 14
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
): SizedBox(),
|
||||||
|
widget.eventAgenda.address?.address != null &&
|
||||||
|
widget.eventAgenda.address!.address!.isNotEmpty
|
||||||
|
? GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
final query = Uri.encodeComponent(widget.eventAgenda.address!.address!);
|
||||||
|
final googleMapsUrl = Uri.parse("https://www.google.com/maps/search/?api=1&query=$query");
|
||||||
|
|
||||||
|
if (await canLaunchUrl(googleMapsUrl)) {
|
||||||
|
await launchUrl(googleMapsUrl, mode: LaunchMode.externalApplication);
|
||||||
|
} else {
|
||||||
|
print("Impossible d'ouvrir Google Maps");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: size.width,
|
||||||
|
height: 60,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.location_on, size: 13, color: kMainColor),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: SizedBox(
|
||||||
|
width: size.width * 0.7,
|
||||||
|
child: AutoSizeText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
widget.eventAgenda.address!.address!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: kMainColor,
|
||||||
|
//decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
widget.eventAgenda.phone != null && widget.eventAgenda.phone!.isNotEmpty
|
||||||
|
? SizedBox(
|
||||||
|
width: size.width,
|
||||||
|
height: 35,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => openPhone(widget.eventAgenda.phone!),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.phone, size: 13, color: kMainColor),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: Text(widget.eventAgenda.phone!, style: TextStyle(fontSize: 12, color: kMainColor)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SizedBox(),
|
||||||
|
widget.eventAgenda.email != null && widget.eventAgenda.email!.isNotEmpty
|
||||||
|
? SizedBox(
|
||||||
|
width: size.width,
|
||||||
|
height: 35,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => openEmail(widget.eventAgenda.email!),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.email, size: 13, color: kMainColor),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: AutoSizeText(
|
||||||
|
widget.eventAgenda.email!,
|
||||||
|
style: TextStyle(fontSize: 12, color: kMainColor),
|
||||||
|
maxLines: 3
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SizedBox(),
|
||||||
|
widget.eventAgenda.website != null && widget.eventAgenda.website!.isNotEmpty
|
||||||
|
? GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
final url = Uri.parse(widget.eventAgenda.website!);
|
||||||
|
if (await canLaunchUrl(url)) {
|
||||||
|
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||||
|
} else {
|
||||||
|
// Optionnel : afficher une erreur
|
||||||
|
print('Impossible d\'ouvrir le lien');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: size.width * 0.8,
|
||||||
|
height: 35,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.public, size: 13, color: kMainColor),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Flexible(
|
||||||
|
child: AutoSizeText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
widget.eventAgenda.website!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: kMainColor,
|
||||||
|
//decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 4.5,
|
||||||
|
top: 4.5,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kMainColor.withValues(alpha: 0.55),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 25,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
207
lib/Screens/Sections/Agenda/month_filter.dart
Normal file
207
lib/Screens/Sections/Agenda/month_filter.dart
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Models/agenda.dart';
|
||||||
|
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
||||||
|
import 'package:mymuseum_visitapp/app_context.dart';
|
||||||
|
import 'package:mymuseum_visitapp/constants.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class MonthFilter extends StatefulWidget {
|
||||||
|
final List<EventAgenda> events;
|
||||||
|
final Function(List<EventAgenda>?) onMonthSelected;
|
||||||
|
|
||||||
|
MonthFilter({required this.events, required this.onMonthSelected});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_MonthFilterState createState() => _MonthFilterState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MonthFilterState extends State<MonthFilter> with SingleTickerProviderStateMixin {
|
||||||
|
String? _selectedMonth;
|
||||||
|
bool _isExpanded = false;
|
||||||
|
bool _showContent = false;
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _widthAnimation;
|
||||||
|
|
||||||
|
Map<String, List<String>> monthNames = {
|
||||||
|
'fr': ['', 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
|
||||||
|
'en': ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||||
|
'de': ['', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
|
||||||
|
'nl': ['', 'Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
|
||||||
|
'it': ['', 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
|
||||||
|
'es': ['', 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
|
||||||
|
'pl': ['', 'Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
|
||||||
|
'cn': ['', '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||||
|
'uk': ['', 'Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень'],
|
||||||
|
'ar': ['', 'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'],
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
|
||||||
|
_widthAnimation = Tween<double>(begin: 40, end: 265).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleExpand() {
|
||||||
|
setState(() {
|
||||||
|
if (_isExpanded) {
|
||||||
|
_showContent = false;
|
||||||
|
_isExpanded = false;
|
||||||
|
} else {
|
||||||
|
_isExpanded = true;
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
if (_isExpanded) {
|
||||||
|
setState(() => _showContent = true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_isExpanded ? _controller.forward() : _controller.reverse();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final appContext = Provider.of<AppContext>(context);
|
||||||
|
VisitAppContext visitAppContext = appContext.getContext();
|
||||||
|
var primaryColor = visitAppContext.configuration?.primaryColor != null
|
||||||
|
? Color(int.parse(visitAppContext.configuration!.primaryColor!.split('(0x')[1].split(')')[0], radix: 16))
|
||||||
|
: kSecondColor;
|
||||||
|
|
||||||
|
double rounded = visitAppContext.configuration?.roundedValue?.toDouble() ?? 20.0;
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> sortedMonths = _getSortedMonths();
|
||||||
|
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _widthAnimation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Container(
|
||||||
|
width: _widthAnimation.value,
|
||||||
|
height: _isExpanded ? 350 : 75,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _isExpanded ? primaryColor.withValues(alpha: 0.9) : primaryColor.withValues(alpha: 0.5),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(rounded),
|
||||||
|
bottomRight: Radius.circular(rounded),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _showContent
|
||||||
|
? Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close, color: Colors.white),
|
||||||
|
onPressed: toggleExpand,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: sortedMonths.length + 1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == 0) return _buildAllItem(appContext, primaryColor);
|
||||||
|
final monthYear = sortedMonths[index - 1]['monthYear'];
|
||||||
|
final filteredEvents = _filterEvents(monthYear);
|
||||||
|
final nbrEvents = filteredEvents.length;
|
||||||
|
if (nbrEvents == 0) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
String monthName = _getTranslatedMonthName(visitAppContext, monthYear);
|
||||||
|
String year = RegExp(r'\d{4}').stringMatch(monthYear)!;
|
||||||
|
|
||||||
|
bool isSelected = _selectedMonth == monthYear;
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
'$monthName $year ($nbrEvents)',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15.0,
|
||||||
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||||
|
color: isSelected ? Colors.white : Colors.black,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center
|
||||||
|
),
|
||||||
|
tileColor: isSelected ? primaryColor.withValues(alpha: 0.6) : null,
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _selectedMonth = monthYear);
|
||||||
|
widget.onMonthSelected(filteredEvents);
|
||||||
|
toggleExpand(); // Auto close after tap
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: _isExpanded ? null : IconButton(
|
||||||
|
icon: const Icon(Icons.menu, color: Colors.white),
|
||||||
|
onPressed: toggleExpand,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAllItem(AppContext appContext, Color primaryColor) {
|
||||||
|
final totalEvents = widget.events.length;
|
||||||
|
final isSelected = _selectedMonth == null;
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
'${TranslationHelper.getFromLocale("agenda.all", appContext.getContext())} ($totalEvents)',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15.0,
|
||||||
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||||
|
color: isSelected ? Colors.white : Colors.black,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center
|
||||||
|
),
|
||||||
|
tileColor: isSelected ? primaryColor.withValues(alpha: 0.6) : null,
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _selectedMonth = null);
|
||||||
|
widget.onMonthSelected(widget.events);
|
||||||
|
toggleExpand(); // Auto close after tap
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getTranslatedMonthName(VisitAppContext context, String monthYear) {
|
||||||
|
int monthIndex = int.parse(monthYear.split('-')[0]);
|
||||||
|
return monthNames[context.language!.toLowerCase()]![monthIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> _getSortedMonths() {
|
||||||
|
Map<String, int> monthsMap = {};
|
||||||
|
|
||||||
|
for (var event in widget.events) {
|
||||||
|
final key = '${event.dateFrom!.month}-${event.dateFrom!.year}';
|
||||||
|
monthsMap[key] = (monthsMap[key] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> sorted = monthsMap.entries
|
||||||
|
.map((e) => {'monthYear': e.key, 'totalEvents': e.value})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
sorted.sort((a, b) {
|
||||||
|
final aParts = a['monthYear'].split('-').map(int.parse).toList();
|
||||||
|
final bParts = b['monthYear'].split('-').map(int.parse).toList();
|
||||||
|
return aParts[1] != bParts[1] ? aParts[1].compareTo(bParts[1]) : aParts[0].compareTo(bParts[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EventAgenda> _filterEvents(String? monthYear) {
|
||||||
|
if (monthYear == null) return widget.events;
|
||||||
|
|
||||||
|
final parts = monthYear.split('-');
|
||||||
|
final selectedMonth = int.parse(parts[0]);
|
||||||
|
final selectedYear = int.parse(parts[1]);
|
||||||
|
|
||||||
|
return widget.events.where((event) {
|
||||||
|
final date = event.dateFrom!;
|
||||||
|
return date.month == selectedMonth && date.year == selectedYear;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -80,7 +80,6 @@ class _GeoPointFilterState extends State<GeoPointFilter> with SingleTickerProvid
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
_isExpanded ? _controller.forward() : _controller.reverse();
|
_isExpanded ? _controller.forward() : _controller.reverse();
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,8 @@ class PdfFilter extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMixin {
|
class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMixin {
|
||||||
bool isExpanded = false;
|
bool _isExpanded = false;
|
||||||
|
bool _showContent = false;
|
||||||
int _selectedOrderPdf = 0;
|
int _selectedOrderPdf = 0;
|
||||||
late AnimationController _controller;
|
late AnimationController _controller;
|
||||||
late Animation<double> _widthAnimation;
|
late Animation<double> _widthAnimation;
|
||||||
@ -25,14 +26,24 @@ class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMix
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300));
|
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
|
||||||
_widthAnimation = Tween<double>(begin: 40, end: 250).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
_widthAnimation = Tween<double>(begin: 40, end: 250).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleExpand() {
|
void toggleExpand() {
|
||||||
setState(() {
|
setState(() {
|
||||||
isExpanded = !isExpanded;
|
if (_isExpanded) {
|
||||||
isExpanded ? _controller.forward() : _controller.reverse();
|
_showContent = false;
|
||||||
|
_isExpanded = false;
|
||||||
|
} else {
|
||||||
|
_isExpanded = true;
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
if (_isExpanded) {
|
||||||
|
setState(() => _showContent = true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_isExpanded ? _controller.forward() : _controller.reverse();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,15 +70,15 @@ class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMix
|
|||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return Container(
|
return Container(
|
||||||
width: _widthAnimation.value,
|
width: _widthAnimation.value,
|
||||||
height: isExpanded ? 300 : 75,
|
height: _isExpanded ? 300 : 75,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isExpanded ? primaryColor.withValues(alpha: 0.9) : primaryColor.withValues(alpha: 0.5) ,
|
color: _isExpanded ? primaryColor.withValues(alpha: 0.9) : primaryColor.withValues(alpha: 0.5) ,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(rounded),
|
topRight: Radius.circular(rounded),
|
||||||
bottomRight: Radius.circular(rounded),
|
bottomRight: Radius.circular(rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: isExpanded
|
child: _showContent
|
||||||
? Column(
|
? Column(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -113,7 +124,7 @@ class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMix
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: IconButton(
|
: _isExpanded ? null : IconButton(
|
||||||
icon: const Icon(Icons.menu, color: Colors.white),
|
icon: const Icon(Icons.menu, color: Colors.white),
|
||||||
onPressed: toggleExpand,
|
onPressed: toggleExpand,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import 'package:mymuseum_visitapp/Helpers/translationHelper.dart';
|
|||||||
import 'package:mymuseum_visitapp/Models/articleRead.dart';
|
import 'package:mymuseum_visitapp/Models/articleRead.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/Screens/Sections/Agenda/agenda_page.dart';
|
||||||
import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.dart';
|
import 'package:mymuseum_visitapp/Screens/Sections/Article/article_page.dart';
|
||||||
import 'package:mymuseum_visitapp/Screens/Sections/Map/map_context.dart';
|
import 'package:mymuseum_visitapp/Screens/Sections/Map/map_context.dart';
|
||||||
import 'package:mymuseum_visitapp/Screens/Sections/Map/map_page.dart';
|
import 'package:mymuseum_visitapp/Screens/Sections/Map/map_page.dart';
|
||||||
@ -77,7 +78,7 @@ class _SectionPageState extends State<SectionPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
appBar: test!.type == SectionType.Menu || test.type == SectionType.Agenda ? CustomAppBar(
|
appBar: test!.type == SectionType.Menu ? CustomAppBar(
|
||||||
title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "",
|
title: sectionDTO != null ? TranslationHelper.get(sectionDTO!.title, visitAppContext) : "",
|
||||||
isHomeButton: false,
|
isHomeButton: false,
|
||||||
) : null,
|
) : null,
|
||||||
@ -92,6 +93,9 @@ class _SectionPageState extends State<SectionPage> {
|
|||||||
var sectionResult = snapshot.data;
|
var sectionResult = snapshot.data;
|
||||||
if(sectionDTO != null && sectionResult != null) {
|
if(sectionDTO != null && sectionResult != null) {
|
||||||
switch(sectionDTO!.type) {
|
switch(sectionDTO!.type) {
|
||||||
|
case SectionType.Agenda:
|
||||||
|
AgendaDTO agendaDTO = AgendaDTO.fromJson(sectionResult)!;
|
||||||
|
return AgendaPage(section: agendaDTO);
|
||||||
case SectionType.Article:
|
case SectionType.Article:
|
||||||
ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!;
|
ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!;
|
||||||
return ArticlePage(visitAppContextIn: widget.visitAppContextIn, articleDTO: articleDTO, resourcesModel: resourcesModel);
|
return ArticlePage(visitAppContextIn: widget.visitAppContextIn, articleDTO: articleDTO, resourcesModel: resourcesModel);
|
||||||
|
|||||||
@ -1394,7 +1394,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||||
|
|||||||
@ -70,6 +70,7 @@ dependencies:
|
|||||||
qr_flutter: ^4.1.0 # multi
|
qr_flutter: ^4.1.0 # multi
|
||||||
flutter_map: ^7.0.2 #all
|
flutter_map: ^7.0.2 #all
|
||||||
image: ^4.1.7
|
image: ^4.1.7
|
||||||
|
url_launcher: ^6.3.1
|
||||||
|
|
||||||
manager_api_new:
|
manager_api_new:
|
||||||
path: manager_api_new
|
path: manager_api_new
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user