Get event from backend instead of local parsing (to be tested) + claude.md
This commit is contained in:
parent
da6ff5177e
commit
8529bb2096
54
CLAUDE.md
Normal file
54
CLAUDE.md
Normal file
@ -0,0 +1,54 @@
|
||||
# tablet-app
|
||||
|
||||
App Flutter dédiée aux tablettes fixes (kiosks). Les visiteurs viennent à la tablette — elle ne bouge pas.
|
||||
|
||||
## Fonctionnement
|
||||
1. Sélection de l'instance via **pincode**
|
||||
2. Choix d'une **configuration** à afficher (configurée dans `manager-app`)
|
||||
3. Affichage du contenu en boucle pour les visiteurs
|
||||
|
||||
## Stack
|
||||
- Flutter mobile (cible : tablette Android/iOS)
|
||||
- State management : **Provider** + `ChangeNotifier`
|
||||
- MQTT (`mqtt_client`) pour la communication temps réel avec le backend
|
||||
- SQLite (`sqflite`) pour le cache local
|
||||
- Firebase Storage + Core
|
||||
|
||||
## Client API
|
||||
Dépendance locale sur le client généré dans `manager-app` :
|
||||
```yaml
|
||||
manager_api_new:
|
||||
path: ../manager-app/manager_api_new
|
||||
```
|
||||
**Ne pas copier/dupliquer le client** — toujours pointer vers `manager-app/manager_api_new`.
|
||||
|
||||
## Structure
|
||||
```
|
||||
lib/
|
||||
├── api/ # Intégration OpenAPI
|
||||
├── Components/ # Boutons, carrousels, loaders, players audio/vidéo
|
||||
├── Helpers/ # DatabaseHelper, DeviceInfoHelper, MQTTHelper, translationHelper
|
||||
├── Models/ # TabletAppContext, agenda, section, map-marker, WeatherData
|
||||
├── Screens/ # Agenda, Article, Configuration, MainView, Menu, PDF,
|
||||
│ # Map (Google Maps + MapBox + Flutter Map), Puzzle, Quiz,
|
||||
│ # Slider, Video, Weather, Web
|
||||
└── Services/ # assistantService, downloadService, statisticsService
|
||||
```
|
||||
|
||||
## Fonctionnalités non supportées
|
||||
La tablette étant fixe, certaines features orientées mobilité ne sont pas implémentées :
|
||||
- **Escape game / parcours géolocalisé** — requiert un device mobile qui se déplace
|
||||
- Pas de scanner QR, pas de beacons
|
||||
- Pas de notifications push
|
||||
|
||||
## Particularités vs mymuseum-visitapp
|
||||
- MQTT activé (synchronisation device ↔ backend)
|
||||
- Langues gérées via `translationHelper` + données backend (même pattern que visitapp)
|
||||
- Support cartes : Google Maps, MapBox, Flutter Map
|
||||
|
||||
## Commandes utiles
|
||||
```bash
|
||||
flutter run # Lancer sur device/émulateur connecté
|
||||
flutter build apk # Build Android
|
||||
flutter build ios # Build iOS
|
||||
```
|
||||
@ -24,6 +24,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1034665398515:android:23d9de7735f898e5d6a786",
|
||||
"android_client_info": {
|
||||
"package_name": "be.unov.mymuseum.mdlf"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyBVCpwP5Uxh_nDUV2b6s4TybUqPJ-lvXm0"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1034665398515:android:b7475582b41ed32dd6a786",
|
||||
|
||||
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:manager_api_new/api.dart';
|
||||
|
||||
class Agenda {
|
||||
List<EventAgenda> events;
|
||||
@ -33,6 +34,8 @@ class EventAgenda {
|
||||
String? website;
|
||||
String? phone;
|
||||
String? idVideoYoutube;
|
||||
String? videoLink;
|
||||
String? videoResourceUrl;
|
||||
String? email;
|
||||
String? image;
|
||||
|
||||
@ -48,10 +51,63 @@ class EventAgenda {
|
||||
required this.website,
|
||||
required this.phone,
|
||||
required this.idVideoYoutube,
|
||||
this.videoLink,
|
||||
this.videoResourceUrl,
|
||||
required this.email,
|
||||
required this.image,
|
||||
});
|
||||
|
||||
factory EventAgenda.fromDto(EventAgendaDTO dto, String language) {
|
||||
String? pickTranslation(List<TranslationDTO>? list) {
|
||||
if (list == null || list.isEmpty) return null;
|
||||
return (list.firstWhere(
|
||||
(t) => t.language == language,
|
||||
orElse: () => list.first,
|
||||
)).value;
|
||||
}
|
||||
|
||||
EventAddress? address;
|
||||
final a = dto.address;
|
||||
if (a != null) {
|
||||
final coords = a.geometry?.coordinates as List?;
|
||||
address = EventAddress(
|
||||
address: a.address,
|
||||
lat: coords != null && coords.length >= 2 ? coords[1] : null,
|
||||
lng: coords != null && coords.length >= 2 ? coords[0] : null,
|
||||
zoom: a.zoom,
|
||||
placeId: null,
|
||||
name: null,
|
||||
streetNumber: a.streetNumber,
|
||||
streetName: a.streetName,
|
||||
streetNameShort: null,
|
||||
city: a.city,
|
||||
state: a.state,
|
||||
stateShort: null,
|
||||
postCode: a.postCode,
|
||||
country: a.country,
|
||||
countryShort: null,
|
||||
);
|
||||
}
|
||||
|
||||
return EventAgenda(
|
||||
name: pickTranslation(dto.label),
|
||||
description: pickTranslation(dto.description),
|
||||
type: dto.type,
|
||||
dateAdded: dto.dateAdded,
|
||||
dateFrom: dto.dateFrom,
|
||||
dateTo: dto.dateTo,
|
||||
dateHour: null,
|
||||
address: address,
|
||||
website: dto.website,
|
||||
phone: dto.phone,
|
||||
idVideoYoutube: dto.idVideoYoutube,
|
||||
videoLink: dto.videoLink,
|
||||
videoResourceUrl: dto.videoResource?.url,
|
||||
email: dto.email,
|
||||
image: dto.resource?.url,
|
||||
);
|
||||
}
|
||||
|
||||
factory EventAgenda.fromJson(Map<String, dynamic> json) {
|
||||
return EventAgenda(
|
||||
name: json['name'],
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
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';
|
||||
@ -49,21 +45,23 @@ class _AgendaView extends State<AgendaView> {
|
||||
|
||||
Future<Agenda?> getAndParseJsonInfo(TabletAppContext tabletAppContext) async {
|
||||
try {
|
||||
// Récupération du contenu JSON depuis l'URL
|
||||
var httpClient = HttpClient();
|
||||
if (agendaDTO.id == null) return null;
|
||||
final dtos = await tabletAppContext.clientAPI!.sectionAgendaApi!
|
||||
.sectionAgendaGetUpcomingEvents(agendaDTO.id!);
|
||||
|
||||
// We need to get detail to get url from resourceId
|
||||
var resourceIdForSelectedLanguage = agendaDTO.resourceIds!.where((ri) => ri.language == tabletAppContext.language).first.value;
|
||||
ResourceDTO? resourceDTO = await tabletAppContext.clientAPI!.resourceApi!.resourceGetDetail(resourceIdForSelectedLanguage!);
|
||||
if (dtos == null) return null;
|
||||
|
||||
var request = await httpClient.getUrl(Uri.parse(resourceDTO!.url!));
|
||||
var response = await request.close();
|
||||
var jsonString = await response.transform(utf8.decoder).join();
|
||||
final events = dtos
|
||||
.map((dto) => EventAgenda.fromDto(dto, tabletAppContext.language))
|
||||
.toList();
|
||||
events.sort((a, b) {
|
||||
if (a.dateFrom == null) return 1;
|
||||
if (b.dateFrom == null) return -1;
|
||||
return a.dateFrom!.compareTo(b.dateFrom!);
|
||||
});
|
||||
|
||||
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;
|
||||
agenda = Agenda(events: events);
|
||||
filteredAgenda.value = events;
|
||||
|
||||
mapIcon = await getByteIcon();
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import 'package:manager_api_new/api.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:tablet_app/Components/loading_common.dart';
|
||||
import 'package:tablet_app/Components/video_viewer.dart';
|
||||
import 'package:tablet_app/Components/video_viewer_youtube.dart';
|
||||
import 'package:tablet_app/Models/agenda.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
@ -249,7 +250,7 @@ class _EventPopupState extends State<EventPopup> {
|
||||
},
|
||||
textStyle: TextStyle(fontSize: kDescriptionSize),
|
||||
),
|
||||
widget.eventAgenda.idVideoYoutube != null && widget.eventAgenda.idVideoYoutube!.isNotEmpty ?
|
||||
if (widget.eventAgenda.idVideoYoutube != null && widget.eventAgenda.idVideoYoutube!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
@ -257,8 +258,25 @@ class _EventPopupState extends State<EventPopup> {
|
||||
width: 350,
|
||||
child: VideoViewerYoutube(videoUrl: "https://www.youtube.com/watch?v=${widget.eventAgenda.idVideoYoutube}", isAuto: false, webView: true)
|
||||
),
|
||||
) :
|
||||
SizedBox(),
|
||||
),
|
||||
if (widget.eventAgenda.videoLink != null && widget.eventAgenda.videoLink!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
height: 250,
|
||||
width: 350,
|
||||
child: VideoViewer(videoUrl: widget.eventAgenda.videoLink!, file: null),
|
||||
),
|
||||
),
|
||||
if (widget.eventAgenda.videoResourceUrl != null && widget.eventAgenda.videoResourceUrl!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
height: 250,
|
||||
width: 350,
|
||||
child: VideoViewer(videoUrl: widget.eventAgenda.videoResourceUrl!, file: null),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -32,6 +32,9 @@ class Client {
|
||||
StatsApi? _statsApi;
|
||||
StatsApi? get statsApi => _statsApi;
|
||||
|
||||
SectionAgendaApi? _sectionAgendaApi;
|
||||
SectionAgendaApi? get sectionAgendaApi => _sectionAgendaApi;
|
||||
|
||||
Client(String path, {String? apiKey}) {
|
||||
_apiClient = ApiClient(basePath: path);
|
||||
if (apiKey != null) _apiClient!.addDefaultHeader('X-Api-Key', apiKey);
|
||||
@ -44,5 +47,6 @@ class Client {
|
||||
_deviceApi = DeviceApi(_apiClient);
|
||||
_applicationInstanceApi = ApplicationInstanceApi(_apiClient);
|
||||
_statsApi = StatsApi(_apiClient);
|
||||
_sectionAgendaApi = SectionAgendaApi(_apiClient);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user