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();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,8 @@ class PdfFilter extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMixin {
|
||||
bool isExpanded = false;
|
||||
bool _isExpanded = false;
|
||||
bool _showContent = false;
|
||||
int _selectedOrderPdf = 0;
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _widthAnimation;
|
||||
@ -25,14 +26,24 @@ class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMix
|
||||
@override
|
||||
void 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));
|
||||
}
|
||||
|
||||
void toggleExpand() {
|
||||
setState(() {
|
||||
isExpanded = !isExpanded;
|
||||
isExpanded ? _controller.forward() : _controller.reverse();
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
@ -59,15 +70,15 @@ class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMix
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
width: _widthAnimation.value,
|
||||
height: isExpanded ? 300 : 75,
|
||||
height: _isExpanded ? 300 : 75,
|
||||
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(
|
||||
topRight: Radius.circular(rounded),
|
||||
bottomRight: Radius.circular(rounded),
|
||||
),
|
||||
),
|
||||
child: isExpanded
|
||||
child: _showContent
|
||||
? Column(
|
||||
children: [
|
||||
IconButton(
|
||||
@ -113,7 +124,7 @@ class _PdfFilterState extends State<PdfFilter> with SingleTickerProviderStateMix
|
||||
),
|
||||
],
|
||||
)
|
||||
: IconButton(
|
||||
: _isExpanded ? null : IconButton(
|
||||
icon: const Icon(Icons.menu, color: Colors.white),
|
||||
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/resourceModel.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/Map/map_context.dart';
|
||||
import 'package:mymuseum_visitapp/Screens/Sections/Map/map_page.dart';
|
||||
@ -77,7 +78,7 @@ class _SectionPageState extends State<SectionPage> {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
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) : "",
|
||||
isHomeButton: false,
|
||||
) : null,
|
||||
@ -92,6 +93,9 @@ class _SectionPageState extends State<SectionPage> {
|
||||
var sectionResult = snapshot.data;
|
||||
if(sectionDTO != null && sectionResult != null) {
|
||||
switch(sectionDTO!.type) {
|
||||
case SectionType.Agenda:
|
||||
AgendaDTO agendaDTO = AgendaDTO.fromJson(sectionResult)!;
|
||||
return AgendaPage(section: agendaDTO);
|
||||
case SectionType.Article:
|
||||
ArticleDTO articleDTO = ArticleDTO.fromJson(sectionResult)!;
|
||||
return ArticlePage(visitAppContextIn: widget.visitAppContextIn, articleDTO: articleDTO, resourcesModel: resourcesModel);
|
||||
|
||||
@ -1394,7 +1394,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
url_launcher:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||
|
||||
@ -70,6 +70,7 @@ dependencies:
|
||||
qr_flutter: ^4.1.0 # multi
|
||||
flutter_map: ^7.0.2 #all
|
||||
image: ^4.1.7
|
||||
url_launcher: ^6.3.1
|
||||
|
||||
manager_api_new:
|
||||
path: manager_api_new
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user