Skip to content

Commit b54ee2e

Browse files
committed
Add resources to events
1 parent bf18016 commit b54ee2e

File tree

9 files changed

+217
-108
lines changed

9 files changed

+217
-108
lines changed

api/lib/models/event/database.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class EventDatabaseService extends EventService with TableService {
5959
@override
6060
Future<List<Event>> getEvents(
6161
{Uint8List? groupId,
62+
List<Uint8List>? resourceIds,
6263
int offset = 0,
6364
int limit = 50,
6465
String search = ''}) async {
@@ -70,7 +71,13 @@ class EventDatabaseService extends EventService with TableService {
7071
}
7172
if (groupId != null) {
7273
where = where == null ? 'groupId = ?' : '$where AND groupId = ?';
73-
whereArgs = whereArgs == null ? [groupId] : [...whereArgs, groupId];
74+
whereArgs = [...?whereArgs, groupId];
75+
}
76+
if (resourceIds != null) {
77+
final statement =
78+
"id IN (SELECT itemId FROM eventResources WHERE resourceId IN (${resourceIds.map((e) => '?').join(', ')}))";
79+
where = where == null ? statement : '$where AND $statement';
80+
whereArgs = [...?whereArgs, ...resourceIds];
7481
}
7582
final result = await db?.query(
7683
'events',

api/lib/models/event/item/database.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ class CalendarItemDatabaseService extends CalendarItemService
121121
}
122122
if (resourceIds != null) {
123123
final statement =
124-
"calendarItems.id IN (SELECT itemId FROM calendarItemResources WHERE resourceId IN (${List.filled(resourceIds.length, '?').join(', ')}))";
124+
"(calendarItems.id IN (SELECT itemId FROM calendarItemResources WHERE resourceId IN (${List.filled(resourceIds.length, '?').join(', ')})) OR events.id IN (SELECT eventId FROM eventResources WHERE resourceId IN (${List.filled(resourceIds.length, '?').join(', ')})))";
125125
where = where == null ? statement : '$where AND $statement';
126-
whereArgs = [...?whereArgs, ...resourceIds];
126+
whereArgs = [...?whereArgs, ...resourceIds, ...resourceIds];
127127
}
128128
const eventPrefix = "event_";
129129
final result = await db?.query(

api/lib/models/event/service.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ abstract class EventService extends ModelService {
1616
int offset = 0,
1717
int limit = 50,
1818
String search = '',
19+
List<Uint8List>? resourceIds,
1920
});
2021

2122
FutureOr<Event?> createEvent(Event event);

app/lib/pages/calendar/filter.dart

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,11 @@ class CalendarFilter with CalendarFilterMappable {
4040
: null;
4141

4242
CalendarFilter removeGroup() => copyWith(
43-
group: null,
44-
source:
45-
event != null || resource != null || group != null ? source : null);
43+
group: null, source: event != null || resource != null ? source : null);
4644
CalendarFilter removeEvent() => copyWith(
47-
event: null,
48-
source:
49-
group != null || resource != null || event != null ? source : null);
50-
CalendarFilter removeResources() => copyWith(
51-
resource: null,
52-
source:
53-
group != null || event != null || resource != null ? source : null);
45+
event: null, source: group != null || resource != null ? source : null);
46+
CalendarFilter removeResource() => copyWith(
47+
resource: null, source: group != null || event != null ? source : null);
5448
}
5549

5650
class CalendarFilterView extends StatefulWidget {
@@ -194,7 +188,7 @@ class _CalendarFilterViewState extends State<CalendarFilterView> {
194188
? null
195189
: () {
196190
setState(() {
197-
_filter = _filter.removeResources();
191+
_filter = _filter.removeResource();
198192
});
199193
widget.onChanged(_filter);
200194
},

app/lib/pages/calendar/item.dart

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -86,24 +86,17 @@ class _CalendarItemDialogState extends State<CalendarItemDialog> {
8686
final resourceConnector = cubit.getService(_source).calendarItemResource;
8787
final tabs = !_create && noteConnector != null && resourceConnector != null;
8888
final type = _item.type;
89-
String title;
90-
switch (type) {
91-
case CalendarItemType.appointment:
92-
title = _create
93-
? AppLocalizations.of(context).createAppointment
94-
: AppLocalizations.of(context).editAppointment;
95-
break;
96-
case CalendarItemType.moment:
97-
title = _create
98-
? AppLocalizations.of(context).createMoment
99-
: AppLocalizations.of(context).editMoment;
100-
break;
101-
case CalendarItemType.pending:
102-
title = _create
103-
? AppLocalizations.of(context).createPending
104-
: AppLocalizations.of(context).editPending;
105-
break;
106-
}
89+
final title = switch (type) {
90+
CalendarItemType.appointment => _create
91+
? AppLocalizations.of(context).createAppointment
92+
: AppLocalizations.of(context).editAppointment,
93+
CalendarItemType.moment => _create
94+
? AppLocalizations.of(context).createMoment
95+
: AppLocalizations.of(context).editMoment,
96+
CalendarItemType.pending => _create
97+
? AppLocalizations.of(context).createPending
98+
: AppLocalizations.of(context).editPending,
99+
};
107100

108101
return ResponsiveAlertDialog(
109102
title: Text(title),

app/lib/pages/events/event.dart

Lines changed: 128 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import 'package:flow/cubits/flow.dart';
2+
import 'package:flow/pages/events/note.dart';
3+
import 'package:flow/pages/events/resources.dart';
24
import 'package:flow/pages/groups/select.dart';
35
import 'package:flutter/material.dart';
46
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -31,85 +33,142 @@ class EventDialog extends StatelessWidget {
3133
var currentEvent = event ?? const Event();
3234
var currentSource = source ?? '';
3335
var currentService = cubit.sourcesService.getSource(currentSource).event;
36+
final noteConnector = cubit.getService(currentSource).eventNote;
37+
final resourceConnector = cubit.getService(currentSource).eventResource;
3438
final nameController = TextEditingController(text: currentEvent.name);
3539
final locationController =
3640
TextEditingController(text: currentEvent.location);
41+
final tabs = !create && noteConnector != null && resourceConnector != null;
3742
return ResponsiveAlertDialog(
3843
title: Text(create
3944
? AppLocalizations.of(context).createEvent
4045
: AppLocalizations.of(context).editEvent),
4146
constraints: const BoxConstraints(maxWidth: 600, maxHeight: 800),
42-
content: ListView(
43-
shrinkWrap: true,
44-
children: [
45-
if (source == null) ...[
46-
SourceDropdown<EventService>(
47-
value: currentSource,
48-
buildService: (source) => source.event,
49-
onChanged: (connected) {
50-
currentSource = connected?.source ?? '';
51-
currentService = connected?.model;
52-
currentEvent = currentEvent.copyWith(
53-
groupId: null,
54-
);
55-
},
56-
),
57-
const SizedBox(height: 16),
58-
],
59-
const SizedBox(height: 16),
60-
TextFormField(
61-
controller: nameController,
62-
decoration: InputDecoration(
63-
labelText: AppLocalizations.of(context).name,
64-
icon: const PhosphorIcon(PhosphorIconsLight.fileText),
65-
filled: true,
66-
),
67-
onChanged: (value) =>
68-
currentEvent = currentEvent.copyWith(name: value),
69-
),
70-
const SizedBox(height: 16),
71-
MarkdownField(
72-
decoration: InputDecoration(
73-
labelText: AppLocalizations.of(context).description,
74-
border: const OutlineInputBorder(),
75-
icon: const PhosphorIcon(PhosphorIconsLight.fileText),
76-
),
77-
value: currentEvent.description,
78-
onChanged: (value) =>
79-
currentEvent = currentEvent.copyWith(description: value),
80-
),
81-
const SizedBox(height: 16),
82-
GroupSelectTile(
83-
source: currentSource,
84-
value: currentEvent.groupId,
85-
onChanged: (value) {
86-
currentEvent = currentEvent.copyWith(groupId: value?.model);
87-
},
88-
),
89-
const SizedBox(height: 8),
90-
StatefulBuilder(
91-
builder: (context, setState) => CheckboxListTile(
92-
secondary: const Icon(PhosphorIconsLight.circleHalfTilt),
93-
title: Text(AppLocalizations.of(context).blocked),
94-
value: currentEvent.blocked,
95-
onChanged: (value) => setState(
96-
() => currentEvent = currentEvent.copyWith(
97-
blocked: value ?? currentEvent.blocked),
47+
content: DefaultTabController(
48+
length: tabs ? 3 : 1,
49+
child: Column(
50+
children: [
51+
if (tabs)
52+
TabBar(
53+
tabs: [
54+
(
55+
PhosphorIconsLight.faders,
56+
AppLocalizations.of(context).general
57+
),
58+
(
59+
PhosphorIconsLight.checkCircle,
60+
AppLocalizations.of(context).notes
61+
),
62+
(
63+
PhosphorIconsLight.cube,
64+
AppLocalizations.of(context).resources
65+
),
66+
]
67+
.map((e) => HorizontalTab(
68+
icon: PhosphorIcon(e.$1),
69+
label: Text(e.$2),
70+
))
71+
.toList()),
72+
Flexible(
73+
child: TabBarView(
74+
children: [
75+
Material(
76+
color: Colors.transparent,
77+
child: ListView(
78+
shrinkWrap: true,
79+
children: [
80+
if (source == null) ...[
81+
SourceDropdown<EventService>(
82+
value: currentSource,
83+
buildService: (source) => source.event,
84+
onChanged: (connected) {
85+
currentSource = connected?.source ?? '';
86+
currentService = connected?.model;
87+
currentEvent = currentEvent.copyWith(
88+
groupId: null,
89+
);
90+
},
91+
),
92+
const SizedBox(height: 16),
93+
],
94+
const SizedBox(height: 16),
95+
TextFormField(
96+
controller: nameController,
97+
decoration: InputDecoration(
98+
labelText: AppLocalizations.of(context).name,
99+
icon:
100+
const PhosphorIcon(PhosphorIconsLight.fileText),
101+
filled: true,
102+
),
103+
onChanged: (value) =>
104+
currentEvent = currentEvent.copyWith(name: value),
105+
),
106+
const SizedBox(height: 16),
107+
MarkdownField(
108+
decoration: InputDecoration(
109+
labelText: AppLocalizations.of(context).description,
110+
border: const OutlineInputBorder(),
111+
icon:
112+
const PhosphorIcon(PhosphorIconsLight.fileText),
113+
),
114+
value: currentEvent.description,
115+
onChanged: (value) => currentEvent =
116+
currentEvent.copyWith(description: value),
117+
),
118+
const SizedBox(height: 16),
119+
GroupSelectTile(
120+
source: currentSource,
121+
value: currentEvent.groupId,
122+
onChanged: (value) {
123+
currentEvent =
124+
currentEvent.copyWith(groupId: value?.model);
125+
},
126+
),
127+
const SizedBox(height: 8),
128+
StatefulBuilder(
129+
builder: (context, setState) => CheckboxListTile(
130+
secondary: const Icon(
131+
PhosphorIconsLight.circleHalfTilt),
132+
title: Text(
133+
AppLocalizations.of(context).blocked),
134+
value: currentEvent.blocked,
135+
onChanged: (value) => setState(
136+
() => currentEvent = currentEvent.copyWith(
137+
blocked: value ?? currentEvent.blocked),
138+
),
139+
)),
140+
const SizedBox(height: 8),
141+
TextField(
142+
decoration: InputDecoration(
143+
labelText: AppLocalizations.of(context).location,
144+
icon: const PhosphorIcon(PhosphorIconsLight.mapPin),
145+
),
146+
minLines: 1,
147+
maxLines: 2,
148+
controller: locationController,
149+
onChanged: (value) => currentEvent =
150+
currentEvent.copyWith(location: value),
151+
),
152+
],
98153
),
99-
)),
100-
const SizedBox(height: 8),
101-
TextField(
102-
decoration: InputDecoration(
103-
labelText: AppLocalizations.of(context).location,
104-
icon: const PhosphorIcon(PhosphorIconsLight.mapPin),
154+
),
155+
if (tabs) ...[
156+
NotesView(
157+
model: currentEvent,
158+
connector: noteConnector,
159+
source: currentSource,
160+
),
161+
ResourcesView(
162+
model: currentEvent,
163+
connector: resourceConnector,
164+
source: currentSource,
165+
),
166+
],
167+
],
168+
),
105169
),
106-
minLines: 1,
107-
maxLines: 2,
108-
controller: locationController,
109-
onChanged: (value) =>
110-
currentEvent = currentEvent.copyWith(location: value),
111-
),
112-
],
170+
],
171+
),
113172
),
114173
actions: [
115174
TextButton(

app/lib/pages/events/filter.dart

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import 'package:dart_mappable/dart_mappable.dart';
2+
import 'package:flow/pages/resources/select.dart';
3+
import 'package:flow_api/models/resource/model.dart';
24
import 'package:flutter/material.dart';
35
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
46
import 'dart:typed_data';
@@ -14,13 +16,18 @@ part 'filter.mapper.dart';
1416
class EventFilter with EventFilterMappable {
1517
final String? source;
1618
final Uint8List? group;
19+
final Uint8List? resource;
1720

1821
const EventFilter({
1922
this.source,
2023
this.group,
24+
this.resource,
2125
});
2226

23-
EventFilter removeGroup() => copyWith(group: null, source: source);
27+
EventFilter removeGroup() =>
28+
copyWith(group: null, source: resource == null ? null : source);
29+
EventFilter removeResource() =>
30+
copyWith(resource: null, source: group == null ? null : source);
2431
}
2532

2633
class EventFilterView extends StatefulWidget {
@@ -85,6 +92,39 @@ class _EventFilterViewState extends State<EventFilterView> {
8592
}
8693
},
8794
),
95+
InputChip(
96+
label: Text(AppLocalizations.of(context).resource),
97+
avatar: const PhosphorIcon(PhosphorIconsLight.cube),
98+
selected: _filter.resource != null,
99+
showCheckmark: false,
100+
onDeleted: _filter.resource == null
101+
? null
102+
: () {
103+
setState(() {
104+
_filter = _filter.removeResource();
105+
});
106+
widget.onChanged(_filter);
107+
},
108+
onSelected: (value) async {
109+
final sourceResource = await showDialog<SourcedModel<Resource>>(
110+
context: context,
111+
builder: (context) => ResourceSelectDialog(
112+
selected: _filter.source != null && _filter.resource != null
113+
? SourcedModel(_filter.source!, _filter.resource!)
114+
: null,
115+
source: _filter.source,
116+
),
117+
);
118+
if (sourceResource != null) {
119+
setState(() {
120+
_filter = _filter.copyWith(
121+
resource: sourceResource.model.id,
122+
source: sourceResource.source);
123+
});
124+
widget.onChanged(_filter);
125+
}
126+
},
127+
),
88128
]
89129
.map((e) => Padding(padding: const EdgeInsets.all(8.0), child: e))
90130
.toList(),

0 commit comments

Comments
 (0)