diff --git a/lib/daos/activities_dao.dart b/lib/daos/activities_dao.dart index 95ac6ba..9751d6f 100644 --- a/lib/daos/activities_dao.dart +++ b/lib/daos/activities_dao.dart @@ -20,6 +20,13 @@ class ActivitiesDao extends DatabaseAccessor Future remove(Activity activity) => delete(activities).delete(activity); + Future> contains(value) async { + return (select(activities) + ..where((t) => + t.title.contains(value) | t.description.contains(value) | t.category.contains(value))) + .get(); + } + Future> activitiesFromSession(int id) async { final result = select(db.sessionActivities).join( [ @@ -46,9 +53,8 @@ class ActivitiesDao extends DatabaseAccessor ], )..where(db.sessionActivities.sessionId.equals(id)); - return query.watch().map((rows){ - final activities = - (rows).map((e) => e.readTable(db.activities)).toList(); + return query.watch().map((rows) { + final activities = (rows).map((e) => e.readTable(db.activities)).toList(); return activities; }); diff --git a/lib/daos/session_activities_dao.dart b/lib/daos/session_activities_dao.dart index 9c645ae..8751cc3 100644 --- a/lib/daos/session_activities_dao.dart +++ b/lib/daos/session_activities_dao.dart @@ -7,6 +7,8 @@ part 'session_activities_dao.g.dart'; class SessionActivitiesDao extends DatabaseAccessor with _$SessionActivitiesDaoMixin { SessionActivitiesDao(super.db); + Future createOrUpdate(SessionActivitiesCompanion sessionActivity) => into(sessionActivities).insertOnConflictUpdate(sessionActivity); + Future> all() async { return await select(sessionActivities).get(); } diff --git a/lib/services/search/activity_finder_service.dart b/lib/services/search/activity_finder_service.dart new file mode 100644 index 0000000..d2d9833 --- /dev/null +++ b/lib/services/search/activity_finder_service.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:sendtrain/daos/activities_dao.dart'; +import 'package:sendtrain/database/database.dart'; +// import 'package:sendtrain/widgets/activities/activity_card.dart'; +import 'package:sendtrain/widgets/generic/elements/form_search_input.dart'; + +class ActivityFinderService { + final BuildContext context; + final ActivitiesDao dao; + + ActivityFinderService(this.context) + : dao = ActivitiesDao(Provider.of(context, listen: false)); + + void finish() {} + + Future?> fetchSuggestions(String input) async { + List activities = await dao.contains(input); + + if (activities.isNotEmpty) { + return activities + .map((activity) => Suggestion(activity)) + .toList(); + } else { + return null; + } + } + + Widget resultWidget(Activity activity, Function? callback) { + // return ActivityCard(activity: activity, callback: callback); + return ListTile( + title: Text(activity.title), + subtitle: Text(activity.description), + onTap: () { + if (callback != null) { + callback(); + } + }, + ); + } +} diff --git a/lib/services/apis/google_places_service.dart b/lib/services/search/google_places_service.dart similarity index 86% rename from lib/services/apis/google_places_service.dart rename to lib/services/search/google_places_service.dart index 21fb308..5cbd766 100644 --- a/lib/services/apis/google_places_service.dart +++ b/lib/services/search/google_places_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:sendtrain/models/google_place_model.dart'; import 'package:sendtrain/widgets/generic/elements/form_search_input.dart'; @@ -15,7 +16,7 @@ class GooglePlacesService { client.close(); } - Future?> fetchSuggestions(String input, String lang) async { + Future?> fetchSuggestions(String input) async { var headers = { 'Content-Type': 'application/json', 'X-Goog-Api-Key': apiKey, @@ -75,4 +76,15 @@ class GooglePlacesService { throw Exception(response.reasonPhrase); } } + + Widget resultWidget(GooglePlaceModel place, Function? callback) { + return ListTile( + title: Text(place.description), + onTap: () async { + if (callback != null) { + callback(); + } + }, + ); + } } diff --git a/lib/widgets/activities/activity_card.dart b/lib/widgets/activities/activity_card.dart index 60ea7ff..ef90c69 100644 --- a/lib/widgets/activities/activity_card.dart +++ b/lib/widgets/activities/activity_card.dart @@ -14,8 +14,9 @@ import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.da class ActivityCard extends StatefulWidget { final Activity activity; + final Function? callback; - const ActivityCard({super.key, required this.activity}); + const ActivityCard({super.key, required this.activity, this.callback}); @override State createState() => ActivityCardState(); @@ -45,7 +46,6 @@ class ActivityCardState extends State { mainAxisSize: MainAxisSize.min, children: [ ListTile( - // visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity), leading: CardImage( image: findMediaByType(mediaItems, MediaType.image)), diff --git a/lib/widgets/generic/elements/form_search_input.dart b/lib/widgets/generic/elements/form_search_input.dart index 00cb229..ed78401 100644 --- a/lib/widgets/generic/elements/form_search_input.dart +++ b/lib/widgets/generic/elements/form_search_input.dart @@ -5,9 +5,15 @@ import 'package:sendtrain/services/functional/debouncer.dart'; import 'package:sendtrain/widgets/generic/elements/form_text_input.dart'; class Suggestion { - T type; + T content; - Suggestion(this.type); + Suggestion(this.content); + + Widget resultWidget() { + return ListTile( + title: Text('test'), + ); + } } // controller: manages the selected content @@ -19,13 +25,13 @@ class FormSearchInput extends StatefulWidget { {super.key, required this.controller, required this.service, - this.title, - this.callback}); + required this.resultHandler, + this.title}); final String? title; final TextEditingController controller; final dynamic service; - final Function? callback; + final Function resultHandler; @override State createState() => _FormSearchInputState(); @@ -35,7 +41,7 @@ class _FormSearchInputState extends State { String? _currentQuery; late final service = widget.service; - late final callback = widget.callback; + late final resultHandler = widget.resultHandler; // The most recent suggestions received from the API. late Iterable _lastOptions = []; late final Debouncer debouncer; @@ -54,7 +60,7 @@ class _FormSearchInputState extends State { // final Iterable options = await _FakeAPI.search(_currentQuery!); if (query.isNotEmpty) { final List? suggestions = - await service.fetchSuggestions(_currentQuery!, 'en'); + await service.fetchSuggestions(_currentQuery!); // If another search happened after this one, throw away these options. if (_currentQuery != query) { @@ -96,17 +102,11 @@ class _FormSearchInputState extends State { } _lastOptions = List.generate(options.length, (int index) { final Suggestion item = options[index]; - final dynamic content = item.type; - return ListTile( - title: Text(content.description), - onTap: () async { - if (callback != null) { - callback!(content, service); - } - - controller.closeView(null); - }, - ); + final dynamic content = item.content; + return service.resultWidget(content, () { + resultHandler(content, service); + controller.closeView(null); + }); }); return _lastOptions; diff --git a/lib/widgets/sessions/session_activities_editor.dart b/lib/widgets/sessions/session_activities_editor.dart new file mode 100644 index 0000000..11d5df8 --- /dev/null +++ b/lib/widgets/sessions/session_activities_editor.dart @@ -0,0 +1,69 @@ +import 'package:drift/drift.dart' hide Column; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:sendtrain/daos/session_activities_dao.dart'; +import 'package:sendtrain/database/database.dart'; +import 'package:sendtrain/services/search/activity_finder_service.dart'; +import 'package:sendtrain/widgets/generic/elements/form_search_input.dart'; + +class SessionActivitiesEditor extends StatelessWidget { + SessionActivitiesEditor({super.key, required this.session, this.callback}); + + final GlobalKey _formKey = GlobalKey(); + final TextEditingController tec = TextEditingController(); + final Session session; + final Function? callback; + late final Activity selectedActivity; + + @override + Widget build( + BuildContext context, + ) { + 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('Add Activity', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge)), + FormSearchInput( + title: 'Find an Activity', + controller: tec, + service: ActivityFinderService(context), + resultHandler: (Activity content, + ActivityFinderService service) async { + tec.text = content.title; + selectedActivity = content; + }), + Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + Padding( + padding: EdgeInsets.only(top: 10), + child: FilledButton( + child: Text('Submit'), + onPressed: () async { + final SessionActivitiesDao dao = + SessionActivitiesDao( + Provider.of(context, listen: false)); + + await dao.createOrUpdate(SessionActivitiesCompanion( + sessionId: Value(session.id), + activityId: Value(selectedActivity.id), + position: Value(0), + )); + + Navigator.pop(_formKey.currentContext!, 'Submit'); + + if (callback != null) { + await callback!(); + } + })) + ]) + ]))); + } +} diff --git a/lib/widgets/sessions/session_editor.dart b/lib/widgets/sessions/session_editor.dart index 784aa31..c5c29e0 100644 --- a/lib/widgets/sessions/session_editor.dart +++ b/lib/widgets/sessions/session_editor.dart @@ -12,7 +12,7 @@ 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/apis/google_places_service.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'; @@ -185,7 +185,7 @@ class _SessionEditorState extends State { title: 'Location (optional)', controller: sessionCreateController['address']!, service: GooglePlacesService(), - callback: (content, service) async { + resultHandler: (content, service) async { if (content.imageReferences != null) { // get a random photo item from the returned result Map photo = content.imageReferences![ diff --git a/lib/widgets/sessions/session_view.dart b/lib/widgets/sessions/session_view.dart index 0537128..41c884b 100644 --- a/lib/widgets/sessions/session_view.dart +++ b/lib/widgets/sessions/session_view.dart @@ -10,6 +10,7 @@ import 'package:sendtrain/extensions/string_extensions.dart'; import 'package:sendtrain/helpers/widget_helpers.dart'; import 'package:sendtrain/widgets/achievements/achievement_editor.dart'; import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart'; +import 'package:sendtrain/widgets/sessions/session_activities_editor.dart'; import 'package:sendtrain/widgets/sessions/session_editor.dart'; import 'package:sendtrain/widgets/sessions/session_view_achievements.dart'; import 'package:sendtrain/widgets/sessions/session_view_activities.dart'; @@ -91,6 +92,16 @@ class _SessionViewState extends State { session: session, callback: resetState)); }, ), + FloatingActionButton.extended( + icon: const Icon(Icons.edit_outlined), + label: Text('Add Activity'), + onPressed: () { + showEditorSheet( + context, + SessionActivitiesEditor( + session: session, callback: resetState)); + }, + ), FloatingActionButton.extended( icon: const Icon(Icons.edit_outlined), label: Text('Edit'), diff --git a/lib/widgets/sessions/session_view_activities.dart b/lib/widgets/sessions/session_view_activities.dart index 2101c17..2f8683c 100644 --- a/lib/widgets/sessions/session_view_activities.dart +++ b/lib/widgets/sessions/session_view_activities.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:sendtrain/daos/activities_dao.dart'; import 'package:sendtrain/database/database.dart'; +import 'package:sendtrain/helpers/widget_helpers.dart'; import 'package:sendtrain/widgets/activities/activity_card.dart'; import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart'; +import 'package:sendtrain/widgets/sessions/session_activities_editor.dart'; class SessionViewActivities extends StatefulWidget { const SessionViewActivities({super.key, required this.session}); @@ -43,7 +45,13 @@ class _SessionViewActivitiesState extends State { customBorder: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), - onTap: () {}, + onTap: () { + showEditorSheet( + context, + SessionActivitiesEditor( + session: widget.session, + callback: () {})); + }, child: ListTile( contentPadding: EdgeInsets.only( top: 5, left: 15, right: 5, bottom: 5), @@ -51,7 +59,7 @@ class _SessionViewActivitiesState extends State { leading: Icon(Icons.add_box_rounded), title: Text('Add an Activity!'), subtitle: Text( - 'Here you can associate one or more activities that you can follow during your session.'), + 'Here you can associate one or more activities that you can follow along with during your session.'), ))) ])); }