functional activity addition, bug to only remove activity action and date selection need to be fixed

This commit is contained in:
Joshua Burman 2025-01-05 15:07:36 -05:00
parent fec4eaaf92
commit acab37eb60
10 changed files with 177 additions and 28 deletions

View File

@ -20,6 +20,13 @@ class ActivitiesDao extends DatabaseAccessor<AppDatabase>
Future remove(Activity activity) => delete(activities).delete(activity); Future remove(Activity activity) => delete(activities).delete(activity);
Future<List<Activity>> contains(value) async {
return (select(activities)
..where((t) =>
t.title.contains(value) | t.description.contains(value) | t.category.contains(value)))
.get();
}
Future<List<Activity>> activitiesFromSession(int id) async { Future<List<Activity>> activitiesFromSession(int id) async {
final result = select(db.sessionActivities).join( final result = select(db.sessionActivities).join(
[ [
@ -46,9 +53,8 @@ class ActivitiesDao extends DatabaseAccessor<AppDatabase>
], ],
)..where(db.sessionActivities.sessionId.equals(id)); )..where(db.sessionActivities.sessionId.equals(id));
return query.watch().map((rows){ return query.watch().map((rows) {
final activities = final activities = (rows).map((e) => e.readTable(db.activities)).toList();
(rows).map((e) => e.readTable(db.activities)).toList();
return activities; return activities;
}); });

View File

@ -7,6 +7,8 @@ part 'session_activities_dao.g.dart';
class SessionActivitiesDao extends DatabaseAccessor<AppDatabase> with _$SessionActivitiesDaoMixin { class SessionActivitiesDao extends DatabaseAccessor<AppDatabase> with _$SessionActivitiesDaoMixin {
SessionActivitiesDao(super.db); SessionActivitiesDao(super.db);
Future createOrUpdate(SessionActivitiesCompanion sessionActivity) => into(sessionActivities).insertOnConflictUpdate(sessionActivity);
Future<List<SessionActivity>> all() async { Future<List<SessionActivity>> all() async {
return await select(sessionActivities).get(); return await select(sessionActivities).get();
} }

View File

@ -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<AppDatabase>(context, listen: false));
void finish() {}
Future<List<Suggestion>?> fetchSuggestions(String input) async {
List<Activity> activities = await dao.contains(input);
if (activities.isNotEmpty) {
return activities
.map<Suggestion>((activity) => Suggestion<Activity>(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();
}
},
);
}
}

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:sendtrain/models/google_place_model.dart'; import 'package:sendtrain/models/google_place_model.dart';
import 'package:sendtrain/widgets/generic/elements/form_search_input.dart'; import 'package:sendtrain/widgets/generic/elements/form_search_input.dart';
@ -15,7 +16,7 @@ class GooglePlacesService {
client.close(); client.close();
} }
Future<List<Suggestion>?> fetchSuggestions(String input, String lang) async { Future<List<Suggestion>?> fetchSuggestions(String input) async {
var headers = { var headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Goog-Api-Key': apiKey, 'X-Goog-Api-Key': apiKey,
@ -75,4 +76,15 @@ class GooglePlacesService {
throw Exception(response.reasonPhrase); throw Exception(response.reasonPhrase);
} }
} }
Widget resultWidget(GooglePlaceModel place, Function? callback) {
return ListTile(
title: Text(place.description),
onTap: () async {
if (callback != null) {
callback();
}
},
);
}
} }

View File

@ -14,8 +14,9 @@ import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.da
class ActivityCard extends StatefulWidget { class ActivityCard extends StatefulWidget {
final Activity activity; final Activity activity;
final Function? callback;
const ActivityCard({super.key, required this.activity}); const ActivityCard({super.key, required this.activity, this.callback});
@override @override
State<ActivityCard> createState() => ActivityCardState(); State<ActivityCard> createState() => ActivityCardState();
@ -45,7 +46,6 @@ class ActivityCardState extends State<ActivityCard> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
ListTile( ListTile(
// visualDensity: VisualDensity(horizontal: VisualDensity.maximumDensity),
leading: CardImage( leading: CardImage(
image: image:
findMediaByType(mediaItems, MediaType.image)), findMediaByType(mediaItems, MediaType.image)),

View File

@ -5,9 +5,15 @@ import 'package:sendtrain/services/functional/debouncer.dart';
import 'package:sendtrain/widgets/generic/elements/form_text_input.dart'; import 'package:sendtrain/widgets/generic/elements/form_text_input.dart';
class Suggestion<T> { class Suggestion<T> {
T type; T content;
Suggestion(this.type); Suggestion(this.content);
Widget resultWidget() {
return ListTile(
title: Text('test'),
);
}
} }
// controller: manages the selected content // controller: manages the selected content
@ -19,13 +25,13 @@ class FormSearchInput extends StatefulWidget {
{super.key, {super.key,
required this.controller, required this.controller,
required this.service, required this.service,
this.title, required this.resultHandler,
this.callback}); this.title});
final String? title; final String? title;
final TextEditingController controller; final TextEditingController controller;
final dynamic service; final dynamic service;
final Function? callback; final Function resultHandler;
@override @override
State<FormSearchInput> createState() => _FormSearchInputState(); State<FormSearchInput> createState() => _FormSearchInputState();
@ -35,7 +41,7 @@ class _FormSearchInputState extends State<FormSearchInput> {
String? _currentQuery; String? _currentQuery;
late final service = widget.service; late final service = widget.service;
late final callback = widget.callback; late final resultHandler = widget.resultHandler;
// The most recent suggestions received from the API. // The most recent suggestions received from the API.
late Iterable<Widget> _lastOptions = <Widget>[]; late Iterable<Widget> _lastOptions = <Widget>[];
late final Debouncer debouncer; late final Debouncer debouncer;
@ -54,7 +60,7 @@ class _FormSearchInputState extends State<FormSearchInput> {
// final Iterable<String> options = await _FakeAPI.search(_currentQuery!); // final Iterable<String> options = await _FakeAPI.search(_currentQuery!);
if (query.isNotEmpty) { if (query.isNotEmpty) {
final List<Suggestion>? suggestions = final List<Suggestion>? suggestions =
await service.fetchSuggestions(_currentQuery!, 'en'); await service.fetchSuggestions(_currentQuery!);
// If another search happened after this one, throw away these options. // If another search happened after this one, throw away these options.
if (_currentQuery != query) { if (_currentQuery != query) {
@ -96,17 +102,11 @@ class _FormSearchInputState extends State<FormSearchInput> {
} }
_lastOptions = List<ListTile>.generate(options.length, (int index) { _lastOptions = List<ListTile>.generate(options.length, (int index) {
final Suggestion item = options[index]; final Suggestion item = options[index];
final dynamic content = item.type; final dynamic content = item.content;
return ListTile( return service.resultWidget(content, () {
title: Text(content.description), resultHandler(content, service);
onTap: () async { controller.closeView(null);
if (callback != null) { });
callback!(content, service);
}
controller.closeView(null);
},
);
}); });
return _lastOptions; return _lastOptions;

View File

@ -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<FormState> _formKey = GlobalKey<FormState>();
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: <Widget>[
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<AppDatabase>(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!();
}
}))
])
])));
}
}

View File

@ -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/object_media_items_dao.dart';
import 'package:sendtrain/daos/sessions_dao.dart'; import 'package:sendtrain/daos/sessions_dao.dart';
import 'package:sendtrain/database/database.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/builders/dialogs.dart';
import 'package:sendtrain/widgets/generic/elements/form_search_input.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/generic/elements/form_text_input.dart';
@ -185,7 +185,7 @@ class _SessionEditorState extends State<SessionEditor> {
title: 'Location (optional)', title: 'Location (optional)',
controller: sessionCreateController['address']!, controller: sessionCreateController['address']!,
service: GooglePlacesService(), service: GooglePlacesService(),
callback: (content, service) async { resultHandler: (content, service) async {
if (content.imageReferences != null) { if (content.imageReferences != null) {
// get a random photo item from the returned result // get a random photo item from the returned result
Map<String, dynamic> photo = content.imageReferences![ Map<String, dynamic> photo = content.imageReferences![

View File

@ -10,6 +10,7 @@ import 'package:sendtrain/extensions/string_extensions.dart';
import 'package:sendtrain/helpers/widget_helpers.dart'; import 'package:sendtrain/helpers/widget_helpers.dart';
import 'package:sendtrain/widgets/achievements/achievement_editor.dart'; import 'package:sendtrain/widgets/achievements/achievement_editor.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.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_editor.dart';
import 'package:sendtrain/widgets/sessions/session_view_achievements.dart'; import 'package:sendtrain/widgets/sessions/session_view_achievements.dart';
import 'package:sendtrain/widgets/sessions/session_view_activities.dart'; import 'package:sendtrain/widgets/sessions/session_view_activities.dart';
@ -91,6 +92,16 @@ class _SessionViewState extends State<SessionView> {
session: session, callback: resetState)); 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( FloatingActionButton.extended(
icon: const Icon(Icons.edit_outlined), icon: const Icon(Icons.edit_outlined),
label: Text('Edit'), label: Text('Edit'),

View File

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sendtrain/daos/activities_dao.dart'; import 'package:sendtrain/daos/activities_dao.dart';
import 'package:sendtrain/database/database.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/activities/activity_card.dart';
import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart'; import 'package:sendtrain/widgets/generic/elements/generic_progress_indicator.dart';
import 'package:sendtrain/widgets/sessions/session_activities_editor.dart';
class SessionViewActivities extends StatefulWidget { class SessionViewActivities extends StatefulWidget {
const SessionViewActivities({super.key, required this.session}); const SessionViewActivities({super.key, required this.session});
@ -43,7 +45,13 @@ class _SessionViewActivitiesState extends State<SessionViewActivities> {
customBorder: RoundedRectangleBorder( customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
onTap: () {}, onTap: () {
showEditorSheet(
context,
SessionActivitiesEditor(
session: widget.session,
callback: () {}));
},
child: ListTile( child: ListTile(
contentPadding: EdgeInsets.only( contentPadding: EdgeInsets.only(
top: 5, left: 15, right: 5, bottom: 5), top: 5, left: 15, right: 5, bottom: 5),
@ -51,7 +59,7 @@ class _SessionViewActivitiesState extends State<SessionViewActivities> {
leading: Icon(Icons.add_box_rounded), leading: Icon(Icons.add_box_rounded),
title: Text('Add an Activity!'), title: Text('Add an Activity!'),
subtitle: Text( 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.'),
))) )))
])); ]));
} }