import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'package:drift/drift.dart' hide Column; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:mime/mime.dart'; import 'package:provider/provider.dart'; import 'package:sendtrain/daos/media_items_dao.dart'; import 'package:sendtrain/daos/object_media_items_dao.dart'; import 'package:sendtrain/daos/sessions_dao.dart'; import 'package:sendtrain/database/database.dart'; import 'package:sendtrain/services/search/google_places_service.dart'; import 'package:sendtrain/widgets/builders/dialogs.dart'; import 'package:sendtrain/widgets/generic/elements/form_search_input.dart'; import 'package:sendtrain/widgets/generic/elements/form_text_input.dart'; import 'package:sendtrain/widgets/sessions/session_view.dart'; class SessionEditor extends StatefulWidget { const SessionEditor({super.key, this.data, this.session, this.callback}); final Session? session; final Map? data; final Function? callback; @override State createState() => _SessionEditorState(); } // used to pass the result of the found image back to current context... class SessionPayload { String? photoUri; String? address; List? files; } class _SessionEditorState extends State { final GlobalKey _formKey = GlobalKey(); late AppDatabase db; String editorType = 'Create'; final Map sessionCreateController = { 'name': TextEditingController(), 'content': TextEditingController(), 'status': TextEditingController(), 'date': TextEditingController(), 'address': TextEditingController(), 'media': TextEditingController(), }; final SessionPayload sessionPayload = SessionPayload(); @override void initState() { super.initState(); db = Provider.of(context, listen: false); // if we're editing a session, we'll want to populate it with the appropriate values if (widget.session != null) { editorType = 'Edit'; final Session session = widget.session!; sessionCreateController['name']?.text = session.title; sessionCreateController['content']?.text = session.content; sessionCreateController['status']?.text = session.status.name; sessionCreateController['date']?.text = DateFormat('yyyy-MM-dd').format(session.date!); if (session.address != null) { sessionCreateController['address']?.text = session.address!; } } } Future createSession(context) async { Map payload = { Symbol('title'): Value(sessionCreateController['name']!.text), Symbol('content'): Value(sessionCreateController['content']!.text), // we want to maintain existing status during update Symbol('status'): widget.session != null ? Value(widget.session!.status) : Value(SessionStatus.pending), Symbol('date'): Value( DateTime.parse(sessionCreateController['date']!.text)), }; // if a session exists we'll want to update it // so the payload needs the session id if (widget.session != null) { payload[Symbol('id')] = Value(widget.session!.id); } // optional params if (sessionCreateController['address']!.text.isNotEmpty) { payload[Symbol('address')] = Value(sessionCreateController['address']!.text); } return await SessionsDao(db) .createOrUpdate(Function.apply(SessionsCompanion.new, [], payload)); } Future deleteSessionMedia(int sessionId, MediaType mediaType) async { List deletedMedia = (await MediaItemsDao(db).fromSession(sessionId)) .where((mediaItem) => mediaItem.type == mediaType) .toList(); for (int i = 0; i < deletedMedia.length; i++) { await MediaItemsDao(db).remove(deletedMedia[i]); } } Future createSessionMedia( title, sessionId, description, reference, mediaType, ) async { // if (sessionPayload.photoUri != null) { MediaItemsCompanion mediaItem = MediaItemsCompanion( title: Value(title), description: Value(description), reference: Value(reference.toString()), type: Value(mediaType)); return await MediaItemsDao(db).createOrUpdate(mediaItem).then((id) async { ObjectMediaItemsCompanion omi = ObjectMediaItemsCompanion( objectId: Value(sessionId), objectType: Value(ObjectType.sessions), mediaId: Value(id), ); await ObjectMediaItemsDao(db).createOrUpdate(omi); }); // } } @override Widget build(BuildContext context) { sessionCreateController['date']!.text = DateFormat('yyyy-MM-dd').format(DateTime.now()); return Padding( padding: EdgeInsets.fromLTRB(15, 0, 15, 15), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: EdgeInsets.only(top: 10, bottom: 10), child: Text('$editorType Session', textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge)), FormTextInput( controller: sessionCreateController['name']!, title: 'Title'), FormTextInput( controller: sessionCreateController['content']!, title: 'Description', icon: Icon(Icons.description_rounded), maxLines: 10), FormTextInput( controller: sessionCreateController['date']!, title: 'Date', icon: Icon(Icons.date_range_rounded), onTap: () { showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime.now().add(Duration(days: 365))) .then((date) { if (date != null) { sessionCreateController['date']?.text = DateFormat('yyyy-MM-dd').format(date); } }); }), FormSearchInput( title: 'Location (optional)', controller: sessionCreateController['address']!, service: GooglePlacesService(), resultHandler: (content, service) async { if (content.imageReferences != null) { // get a random photo item from the returned result Map photo = content.imageReferences![ Random().nextInt(content.imageReferences!.length)]; await service .fetchPhoto(photo['name']) .then((photoMap) { sessionPayload.photoUri = photoMap['photoUri']; }); } sessionPayload.address = content.address; sessionCreateController['address']!.text = content.description; service.finish(); }), Padding( padding: EdgeInsets.only(top: 10, bottom: 10), child: TextFormField( readOnly: true, decoration: InputDecoration( prefixIcon: Icon(Icons.image_rounded), filled: true, border: OutlineInputBorder( borderSide: BorderSide.none, borderRadius: BorderRadius.circular(12), ), labelText: 'Media (optional)', ), controller: sessionCreateController['media'], onTap: () async { FilePickerResult? result = await FilePicker.platform .pickFiles( allowMultiple: true, type: FileType.image); if (result != null) { List files = result.files; sessionCreateController['media']!.text = files.map((file) => file.name).toString(); sessionPayload.files = files; } })), Row(mainAxisAlignment: MainAxisAlignment.end, children: [ Padding( padding: EdgeInsets.only(top: 10), child: FilledButton( onPressed: () async => { if (_formKey.currentState!.validate()) { await createSession(_formKey.currentContext) .then((sessionId) async { int currentSessionId = sessionId; // dirft weirdly doesn't return the proper id if // an upsert ends up bein an update, so we'll // set the id to the provided session for an update if (widget.session != null) { currentSessionId = widget.session!.id; } // if we've found a photo add it to media! if (sessionPayload.photoUri != null) { await deleteSessionMedia( currentSessionId, MediaType.location); await createSessionMedia( 'Location Image', currentSessionId, sessionPayload.address, sessionPayload.photoUri, MediaType.location); } // if we've selected files to save, save them if (sessionPayload.files != null) { for (int i = 0; i < sessionPayload.files!.length; i++) { PlatformFile file = sessionPayload.files![i]; String? type = lookupMimeType(file.path!)! .split('/') .first; Uint8List fileBytes = await file.xFile.readAsBytes(); MediaType mediaType = MediaType.localImage; if (type == "video") { mediaType = MediaType.localVideo; } await createSessionMedia( 'Local Media', currentSessionId, file.name, base64Encode(fileBytes), mediaType); } } // if session is null it's new so we show the dialog // otherwise the dialog is already open, so no need if (widget.session == null) { SessionsDao(db) .find(currentSessionId) .then((session) => showGenericDialog( SessionView( session: session), _formKey.currentContext!)); } Navigator.pop( _formKey.currentContext!, 'Submit'); if (widget.callback != null) { await widget.callback!(); } }) } }, child: Text('Submit'))) ]), ], ))); } }