@@ -3,17 +3,26 @@ import 'dart:developer';
3
3
import 'package:flutter/foundation.dart' ;
4
4
import 'package:flutter/material.dart' ;
5
5
6
+ import 'package:cached_network_image/cached_network_image.dart' ;
7
+ import 'package:collection/collection.dart' ;
6
8
import 'package:flutter_gen/gen_l10n/l10n.dart' ;
9
+ import 'package:http/http.dart' as http;
7
10
import 'package:matrix/matrix.dart' ;
8
11
12
+ import 'package:fluffychat/config/app_config.dart' ;
13
+ import 'package:fluffychat/config/themes.dart' ;
9
14
import 'package:fluffychat/pangea/activity_planner/activity_plan_generation_repo.dart' ;
10
15
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart' ;
11
16
import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart' ;
12
17
import 'package:fluffychat/pangea/activity_planner/activity_plan_response.dart' ;
18
+ import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart' ;
13
19
import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.dart' ;
20
+ import 'package:fluffychat/pangea/activity_planner/list_request_schema.dart' ;
21
+ import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart' ;
14
22
import 'package:fluffychat/pangea/common/constants/model_keys.dart' ;
15
23
import 'package:fluffychat/pangea/common/utils/error_handler.dart' ;
16
24
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart' ;
25
+ import 'package:fluffychat/utils/file_selector.dart' ;
17
26
import 'package:fluffychat/widgets/future_loading_dialog.dart' ;
18
27
import 'activity_plan_card.dart' ;
19
28
@@ -23,10 +32,13 @@ class ActivityListView extends StatefulWidget {
23
32
/// if null, show saved activities
24
33
final ActivityPlanRequest ? activityPlanRequest;
25
34
35
+ final ActivityPlannerPageState controller;
36
+
26
37
const ActivityListView ({
27
38
super .key,
28
39
required this .room,
29
40
required this .activityPlanRequest,
41
+ required this .controller,
30
42
});
31
43
32
44
@override
@@ -41,10 +53,15 @@ class ActivityListViewState extends State<ActivityListView> {
41
53
bool _isLoading = true ;
42
54
Object ? _error;
43
55
56
+ Uint8List ? _avatar;
57
+ String ? _avatarURL;
58
+ String ? _filename;
59
+
44
60
@override
45
61
void initState () {
46
62
super .initState ();
47
63
_loadActivities ();
64
+ _setModeImageURL ();
48
65
}
49
66
50
67
Future <void > _loadActivities () async {
@@ -108,12 +125,79 @@ class ActivityListViewState extends State<ActivityListView> {
108
125
return ;
109
126
}
110
127
111
- await widget.room? .setPinnedEvents ([eventId]);
128
+ Uint8List ? bytes = _avatar;
129
+ if (_avatarURL != null && bytes == null ) {
130
+ final resp = await http
131
+ .get (Uri .parse (_avatarURL! ))
132
+ .timeout (const Duration (seconds: 5 ));
133
+ bytes = resp.bodyBytes;
134
+ }
135
+
136
+ if (bytes != null && _filename != null ) {
137
+ final file = MatrixFile (
138
+ bytes: bytes,
139
+ name: _filename! ,
140
+ );
141
+
142
+ await widget.room? .sendFileEvent (
143
+ file,
144
+ shrinkImageMaxDimension: 1600 ,
145
+ extraContent: {
146
+ ModelKey .messageTags: ModelKey .messageTagActivityPlan,
147
+ },
148
+ );
149
+ }
150
+
151
+ if (widget.room != null && widget.room! .canSendDefaultStates) {
152
+ await widget.room? .setPinnedEvents ([eventId]);
153
+ }
112
154
113
155
Navigator .of (context).pop ();
114
156
},
115
157
);
116
158
159
+ Future <ActivitySettingResponseSchema ?> get _selectedMode async {
160
+ final modes = await widget.controller.modeItems;
161
+ return modes.firstWhereOrNull (
162
+ (element) =>
163
+ element.name.toLowerCase () ==
164
+ widget.activityPlanRequest? .mode.toLowerCase (),
165
+ );
166
+ }
167
+
168
+ Future <void > _setModeImageURL () async {
169
+ final mode = await _selectedMode;
170
+ if (mode == null ) return ;
171
+
172
+ final modeName =
173
+ mode.defaultName.toLowerCase ().replaceAll (RegExp (r'\s+' ), '' );
174
+ final filename =
175
+ "${ActivitySuggestionsConstants .modeImageFileStart }$modeName .jpg" ;
176
+
177
+ if (! mounted) return ;
178
+ setState (() {
179
+ _avatarURL = "${AppConfig .assetsBaseURL }/$filename " ;
180
+ _filename = filename;
181
+ });
182
+ }
183
+
184
+ void selectPhoto () async {
185
+ final resp = await selectFiles (
186
+ context,
187
+ type: FileSelectorType .images,
188
+ allowMultiple: false ,
189
+ );
190
+
191
+ final photo = resp.singleOrNull;
192
+ if (photo == null ) return ;
193
+ final bytes = await photo.readAsBytes ();
194
+
195
+ setState (() {
196
+ _avatar = bytes;
197
+ _filename = photo.name;
198
+ });
199
+ }
200
+
117
201
@override
118
202
Widget build (BuildContext context) {
119
203
final l10n = L10n .of (context);
@@ -152,8 +236,66 @@ class ActivityListViewState extends State<ActivityListView> {
152
236
padding: const EdgeInsets .all (16 ),
153
237
itemCount: widget.activityPlanRequest == null
154
238
? _bookmarkedActivities.length
155
- : _activities! .length,
239
+ : _activities! .length + 1 ,
156
240
itemBuilder: (context, index) {
241
+ if (index == 0 ) {
242
+ return Center (
243
+ child: Stack (
244
+ alignment: Alignment .bottomCenter,
245
+ children: [
246
+ Column (
247
+ children: [
248
+ AnimatedSize (
249
+ duration: FluffyThemes .animationDuration,
250
+ child: Container (
251
+ decoration: BoxDecoration (
252
+ borderRadius: BorderRadius .circular (12.0 ),
253
+ ),
254
+ width: 400.0 ,
255
+ clipBehavior: Clip .hardEdge,
256
+ child: _avatarURL != null || _avatar != null
257
+ ? ClipRRect (
258
+ child: _avatar == null
259
+ ? CachedNetworkImage (
260
+ fit: BoxFit .cover,
261
+ imageUrl: _avatarURL! ,
262
+ placeholder: (context, url) {
263
+ return const Center (
264
+ child:
265
+ CircularProgressIndicator (),
266
+ );
267
+ },
268
+ errorWidget: (context, url, error) =>
269
+ const SizedBox (),
270
+ )
271
+ : Image .memory (
272
+ _avatar! ,
273
+ fit: BoxFit .cover,
274
+ ),
275
+ )
276
+ : const Padding (
277
+ padding: EdgeInsets .all (16.0 ),
278
+ ),
279
+ ),
280
+ ),
281
+ const SizedBox (height: 16.0 ),
282
+ ],
283
+ ),
284
+ InkWell (
285
+ borderRadius: BorderRadius .circular (90 ),
286
+ onTap: _isLoading ? null : selectPhoto,
287
+ child: const CircleAvatar (
288
+ radius: 32.0 ,
289
+ child: Icon (Icons .add_a_photo_outlined),
290
+ ),
291
+ ),
292
+ ],
293
+ ),
294
+ );
295
+ }
296
+
297
+ index-- ;
298
+
157
299
return ActivityPlanCard (
158
300
activity: widget.activityPlanRequest == null
159
301
? _bookmarkedActivities[index]
0 commit comments