refactor: make activity card into a dialog when launching / editing, adjust sizing to fit two-per-row on small screens (#2123)
This commit is contained in:
parent
6502c3d26c
commit
c204f484c9
7 changed files with 692 additions and 652 deletions
|
|
@ -1,47 +1,39 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_content.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_edit_card.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
||||
|
||||
class ActivitySuggestionCard extends StatelessWidget {
|
||||
final ActivityPlanModel activity;
|
||||
final ActivitySuggestionsAreaState controller;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
final double width;
|
||||
final double height;
|
||||
final double padding;
|
||||
|
||||
const ActivitySuggestionCard({
|
||||
super.key,
|
||||
required this.activity,
|
||||
required this.controller,
|
||||
required this.onPressed,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.padding,
|
||||
});
|
||||
|
||||
bool get _isSelected => controller.selectedActivity == activity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: PressableButton(
|
||||
onPressed: onPressed,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
color: theme.colorScheme.primary,
|
||||
child: AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
height: controller.isEditing && _isSelected
|
||||
? 675
|
||||
: _isSelected
|
||||
? 400
|
||||
: height,
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
|
|
@ -62,10 +54,7 @@ class ActivitySuggestionCard extends StatelessWidget {
|
|||
decoration: BoxDecoration(
|
||||
image: activity.imageURL != null
|
||||
? DecorationImage(
|
||||
image: controller.avatar == null || !_isSelected
|
||||
? NetworkImage(activity.imageURL!)
|
||||
: MemoryImage(controller.avatar!)
|
||||
as ImageProvider<Object>,
|
||||
image: NetworkImage(activity.imageURL!),
|
||||
)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
|
|
@ -74,40 +63,74 @@ class ActivitySuggestionCard extends StatelessWidget {
|
|||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16.0,
|
||||
top: 12.0,
|
||||
left: 12.0,
|
||||
right: 12.0,
|
||||
bottom: 12.0,
|
||||
),
|
||||
child: controller.isEditing && _isSelected
|
||||
? ActivitySuggestionEditCard(
|
||||
activity: activity,
|
||||
controller: controller,
|
||||
)
|
||||
: ActivitySuggestionCardContent(
|
||||
activity: activity,
|
||||
isSelected: _isSelected,
|
||||
controller: controller,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: Text(
|
||||
activity.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 54.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: activity.vocab
|
||||
.map(
|
||||
(vocab) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary
|
||||
.withAlpha(50),
|
||||
borderRadius:
|
||||
BorderRadius.circular(24.0),
|
||||
),
|
||||
child: Text(
|
||||
vocab.lemma,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.group_outlined,
|
||||
child: Text(
|
||||
L10n.of(context).countParticipants(
|
||||
activity.req.numberOfParticipants,
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (controller.isEditing && _isSelected)
|
||||
Positioned(
|
||||
top: 75.0,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(90),
|
||||
onTap: controller.selectPhoto,
|
||||
child: const CircleAvatar(
|
||||
radius: 16.0,
|
||||
child: Icon(
|
||||
Icons.add_a_photo_outlined,
|
||||
size: 16.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
|
||||
|
||||
class ActivitySuggestionCardContent extends StatelessWidget {
|
||||
final ActivityPlanModel activity;
|
||||
final ActivitySuggestionsAreaState controller;
|
||||
final bool isSelected;
|
||||
|
||||
const ActivitySuggestionCardContent({
|
||||
super.key,
|
||||
required this.activity,
|
||||
required this.controller,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: Text(
|
||||
activity.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: Text(
|
||||
activity.learningObjective,
|
||||
style: theme.textTheme.bodySmall,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: Text(
|
||||
activity.instructions,
|
||||
style: theme.textTheme.bodySmall,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 54.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: activity.vocab
|
||||
.map(
|
||||
(vocab) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary.withAlpha(50),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
child: Text(
|
||||
vocab.lemma,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isSelected)
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 54.0),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: activity.vocab
|
||||
.map(
|
||||
(vocab) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary.withAlpha(50),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
child: Text(
|
||||
vocab.lemma,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.group_outlined,
|
||||
child: Text(
|
||||
L10n.of(context).countParticipants(
|
||||
activity.req.numberOfParticipants,
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
Row(
|
||||
spacing: 6.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => controller.onLaunch(activity),
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: Size.zero,
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context).inviteAndLaunch,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton.filled(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
),
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
constraints:
|
||||
const BoxConstraints(), // override default min size of 48px
|
||||
iconSize: 16.0,
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
onPressed: () => controller.setEditting(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
581
lib/pangea/activity_suggestions/activity_suggestion_dialog.dart
Normal file
581
lib/pangea/activity_suggestions/activity_suggestion_dialog.dart
Normal file
|
|
@ -0,0 +1,581 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/http.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:matrix/matrix_api_lite/generated/model.dart' as sdk;
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ActivitySuggestionDialog extends StatefulWidget {
|
||||
final ActivityPlanModel activity;
|
||||
const ActivitySuggestionDialog({
|
||||
required this.activity,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
ActivitySuggestionDialogState createState() =>
|
||||
ActivitySuggestionDialogState();
|
||||
}
|
||||
|
||||
class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
|
||||
bool _isEditing = false;
|
||||
|
||||
Uint8List? _avatar;
|
||||
String? _avatarURL;
|
||||
|
||||
final TextEditingController _titleController = TextEditingController();
|
||||
final TextEditingController _instructionsController = TextEditingController();
|
||||
final TextEditingController _vocabController = TextEditingController();
|
||||
final TextEditingController _participantsController = TextEditingController();
|
||||
final TextEditingController _learningObjectivesController =
|
||||
TextEditingController();
|
||||
|
||||
// storing this separately so that we can dismiss edits,
|
||||
// rather than directly modifying the activity with each change
|
||||
final List<Vocab> _vocab = [];
|
||||
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_titleController.text = widget.activity.title;
|
||||
_learningObjectivesController.text = widget.activity.learningObjective;
|
||||
_instructionsController.text = widget.activity.instructions;
|
||||
_participantsController.text =
|
||||
widget.activity.req.numberOfParticipants.toString();
|
||||
_vocab.addAll(widget.activity.vocab);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_titleController.dispose();
|
||||
_learningObjectivesController.dispose();
|
||||
_instructionsController.dispose();
|
||||
_vocabController.dispose();
|
||||
_participantsController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _setEditing(bool editting) {
|
||||
_isEditing = editting;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void _setAvatar() async {
|
||||
final photo = await selectFiles(
|
||||
context,
|
||||
type: FileSelectorType.images,
|
||||
allowMultiple: false,
|
||||
);
|
||||
final bytes = await photo.singleOrNull?.readAsBytes();
|
||||
if (mounted) setState(() => _avatar = bytes);
|
||||
}
|
||||
|
||||
Future<void> _setAvatarURL() async {
|
||||
if (widget.activity.imageURL == null && _avatar == null) return;
|
||||
try {
|
||||
if (_avatar == null) {
|
||||
final Response response =
|
||||
await http.get(Uri.parse(widget.activity.imageURL!));
|
||||
_avatar = response.bodyBytes;
|
||||
}
|
||||
final resp = await Matrix.of(context).client.uploadContent(_avatar!);
|
||||
if (mounted) setState(() => _avatarURL = resp.toString());
|
||||
widget.activity.imageURL = _avatarURL;
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
data: {
|
||||
"imageURL": widget.activity.imageURL,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _clearEdits() {
|
||||
_avatar = null;
|
||||
_avatarURL = null;
|
||||
_vocab.clear();
|
||||
_vocab.addAll(widget.activity.vocab);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _updateTextFields() async {
|
||||
widget.activity.title = _titleController.text;
|
||||
widget.activity.learningObjective = _learningObjectivesController.text;
|
||||
widget.activity.instructions = _instructionsController.text;
|
||||
widget.activity.req.numberOfParticipants =
|
||||
int.tryParse(_participantsController.text) ?? 3;
|
||||
widget.activity.vocab = _vocab;
|
||||
}
|
||||
|
||||
void _addVocab() {
|
||||
_vocab.insert(
|
||||
0,
|
||||
Vocab(
|
||||
lemma: _vocabController.text.trim(),
|
||||
pos: "",
|
||||
),
|
||||
);
|
||||
_vocabController.clear();
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void _removeVocab(int index) {
|
||||
_vocab.removeAt(index);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _launch() async {
|
||||
final client = Matrix.of(context).client;
|
||||
|
||||
final resp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
await _setAvatarURL();
|
||||
final roomId = await client.createGroupChat(
|
||||
preset: CreateRoomPreset.publicChat,
|
||||
visibility: sdk.Visibility.private,
|
||||
groupName: widget.activity.title,
|
||||
initialState: [
|
||||
if (_avatarURL != null)
|
||||
StateEvent(
|
||||
type: EventTypes.RoomAvatar,
|
||||
content: {'url': _avatarURL.toString()},
|
||||
),
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(client.userID!),
|
||||
),
|
||||
],
|
||||
enableEncryption: false,
|
||||
);
|
||||
|
||||
Room? room = Matrix.of(context).client.getRoomById(roomId);
|
||||
if (room == null) {
|
||||
await client.waitForRoomInSync(roomId);
|
||||
room = Matrix.of(context).client.getRoomById(roomId);
|
||||
if (room == null) return;
|
||||
}
|
||||
|
||||
final eventId = await room.pangeaSendTextEvent(
|
||||
widget.activity.markdown,
|
||||
messageTag: ModelKey.messageTagActivityPlan,
|
||||
);
|
||||
|
||||
if (eventId == null) {
|
||||
debugger(when: kDebugMode);
|
||||
return;
|
||||
}
|
||||
|
||||
await room.setPinnedEvents([eventId]);
|
||||
context.go("/rooms/$roomId/invite");
|
||||
},
|
||||
);
|
||||
|
||||
if (!resp.isError) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final body = Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
image: widget.activity.imageURL != null || _avatar != null
|
||||
? DecorationImage(
|
||||
image: _avatar != null
|
||||
? MemoryImage(_avatar!)
|
||||
: NetworkImage(widget.activity.imageURL!)
|
||||
as ImageProvider<Object>,
|
||||
)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: TextFormField(
|
||||
controller: _titleController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).activityTitle,
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: Text(
|
||||
widget.activity.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (_isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: TextFormField(
|
||||
style: theme.textTheme.bodySmall,
|
||||
controller: _learningObjectivesController,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
L10n.of(context).learningObjectiveLabel,
|
||||
),
|
||||
maxLines: 4,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: Text(
|
||||
widget.activity.learningObjective,
|
||||
style: theme.textTheme.bodySmall,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (_isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: TextFormField(
|
||||
style: theme.textTheme.bodySmall,
|
||||
controller: _instructionsController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).instructions,
|
||||
),
|
||||
maxLines: 8,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: Text(
|
||||
widget.activity.instructions,
|
||||
style: theme.textTheme.bodySmall,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (_isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.group_outlined,
|
||||
child: TextFormField(
|
||||
controller: _participantsController,
|
||||
style: theme.textTheme.bodySmall,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).classRoster,
|
||||
),
|
||||
maxLines: 1,
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final val = int.parse(value);
|
||||
if (val <= 0) {
|
||||
return L10n.of(context).pleaseEnterInt;
|
||||
}
|
||||
} catch (e) {
|
||||
return L10n.of(context).pleaseEnterANumber;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.group_outlined,
|
||||
child: Text(
|
||||
L10n.of(context).countParticipants(
|
||||
widget.activity.req.numberOfParticipants,
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
if (_isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
const BoxConstraints(maxHeight: 54.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: _vocab
|
||||
.mapIndexed(
|
||||
(i, vocab) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary
|
||||
.withAlpha(50),
|
||||
borderRadius:
|
||||
BorderRadius.circular(24.0),
|
||||
),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => _removeVocab(i),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
vocab.lemma,
|
||||
style: theme
|
||||
.textTheme.bodySmall,
|
||||
),
|
||||
const Icon(
|
||||
Icons.close,
|
||||
size: 12.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
const BoxConstraints(maxHeight: 54.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: _vocab
|
||||
.map(
|
||||
(vocab) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary
|
||||
.withAlpha(50),
|
||||
borderRadius:
|
||||
BorderRadius.circular(24.0),
|
||||
),
|
||||
child: Text(
|
||||
vocab.lemma,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isEditing)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _vocabController,
|
||||
style: theme.textTheme.bodySmall,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).addVocabulary,
|
||||
),
|
||||
maxLines: 1,
|
||||
onFieldSubmitted: (_) => _addVocab(),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: const EdgeInsets.all(0.0),
|
||||
constraints:
|
||||
const BoxConstraints(), // override default min size of 48px
|
||||
iconSize: 16.0,
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
onPressed: _addVocab,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
spacing: 6.0,
|
||||
children: [
|
||||
if (_isEditing)
|
||||
GestureDetector(
|
||||
child: const Icon(Icons.save_outlined, size: 16.0),
|
||||
onTap: () {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
_updateTextFields();
|
||||
_setEditing(false);
|
||||
},
|
||||
),
|
||||
if (_isEditing)
|
||||
GestureDetector(
|
||||
child: const Icon(Icons.close_outlined, size: 16.0),
|
||||
onTap: () {
|
||||
_clearEdits();
|
||||
_setEditing(false);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
_updateTextFields();
|
||||
_launch();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: Size.zero,
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context).inviteAndLaunch,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!_isEditing)
|
||||
IconButton.filled(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
),
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
constraints:
|
||||
const BoxConstraints(), // override default min size of 48px
|
||||
iconSize: 16.0,
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
onPressed: () => _setEditing(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 4.0,
|
||||
left: 4.0,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
tooltip: L10n.of(context).close,
|
||||
),
|
||||
),
|
||||
if (_isEditing)
|
||||
Positioned(
|
||||
top: 160.0,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(90),
|
||||
onTap: _setAvatar,
|
||||
child: const CircleAvatar(
|
||||
radius: 24.0,
|
||||
child: Icon(
|
||||
Icons.add_a_photo_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final content = AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: ConstrainedBox(
|
||||
constraints: FluffyThemes.isColumnMode(context)
|
||||
? const BoxConstraints(
|
||||
maxWidth: 400.0,
|
||||
)
|
||||
: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width,
|
||||
maxHeight: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
child: body,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return FluffyThemes.isColumnMode(context)
|
||||
? Dialog(child: content)
|
||||
: Dialog.fullscreen(child: content);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card_row.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
|
||||
|
||||
class ActivitySuggestionEditCard extends StatefulWidget {
|
||||
final ActivityPlanModel activity;
|
||||
final ActivitySuggestionsAreaState controller;
|
||||
|
||||
const ActivitySuggestionEditCard({
|
||||
super.key,
|
||||
required this.activity,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
ActivitySuggestionEditCardState createState() =>
|
||||
ActivitySuggestionEditCardState();
|
||||
}
|
||||
|
||||
class ActivitySuggestionEditCardState
|
||||
extends State<ActivitySuggestionEditCard> {
|
||||
final TextEditingController _titleController = TextEditingController();
|
||||
final TextEditingController _instructionsController = TextEditingController();
|
||||
final TextEditingController _vocabController = TextEditingController();
|
||||
final TextEditingController _participantsController = TextEditingController();
|
||||
final TextEditingController _learningObjectivesController =
|
||||
TextEditingController();
|
||||
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_titleController.text = widget.activity.title;
|
||||
_learningObjectivesController.text = widget.activity.learningObjective;
|
||||
_instructionsController.text = widget.activity.instructions;
|
||||
_participantsController.text =
|
||||
widget.activity.req.numberOfParticipants.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_titleController.dispose();
|
||||
_learningObjectivesController.dispose();
|
||||
_instructionsController.dispose();
|
||||
_vocabController.dispose();
|
||||
_participantsController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _updateActivity() {
|
||||
widget.controller.updateActivity((activity) {
|
||||
activity.title = _titleController.text;
|
||||
activity.learningObjective = _learningObjectivesController.text;
|
||||
activity.instructions = _instructionsController.text;
|
||||
activity.req.numberOfParticipants =
|
||||
int.tryParse(_participantsController.text) ?? 3;
|
||||
return activity;
|
||||
});
|
||||
}
|
||||
|
||||
void _addVocab() {
|
||||
widget.controller.updateActivity((activity) {
|
||||
activity.vocab.insert(
|
||||
0,
|
||||
Vocab(
|
||||
lemma: _vocabController.text.trim(),
|
||||
pos: "",
|
||||
),
|
||||
);
|
||||
return activity;
|
||||
});
|
||||
_vocabController.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: TextFormField(
|
||||
controller: _titleController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).activityTitle,
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: TextFormField(
|
||||
style: theme.textTheme.bodySmall,
|
||||
controller: _learningObjectivesController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).learningObjectiveLabel,
|
||||
),
|
||||
maxLines: 4,
|
||||
minLines: 1,
|
||||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: TextFormField(
|
||||
style: theme.textTheme.bodySmall,
|
||||
controller: _instructionsController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).instructions,
|
||||
),
|
||||
maxLines: 8,
|
||||
minLines: 1,
|
||||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.group_outlined,
|
||||
child: TextFormField(
|
||||
controller: _participantsController,
|
||||
style: theme.textTheme.bodySmall,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context).classRoster,
|
||||
),
|
||||
maxLines: 1,
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final val = int.parse(value);
|
||||
if (val <= 0) {
|
||||
return L10n.of(context).pleaseEnterInt;
|
||||
}
|
||||
} catch (e) {
|
||||
return L10n.of(context).pleaseEnterANumber;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 54.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: widget.activity.vocab
|
||||
.mapIndexed(
|
||||
(i, vocab) => Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary.withAlpha(50),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
widget.controller.updateActivity((activity) {
|
||||
activity.vocab.removeAt(i);
|
||||
return activity;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
vocab.lemma,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
const Icon(Icons.close, size: 12.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _vocabController,
|
||||
style: theme.textTheme.bodySmall,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).addVocabulary,
|
||||
),
|
||||
maxLines: 1,
|
||||
onFieldSubmitted: (_) => _addVocab(),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: const EdgeInsets.all(0.0),
|
||||
constraints:
|
||||
const BoxConstraints(), // override default min size of 48px
|
||||
iconSize: 16.0,
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
onPressed: _addVocab,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
spacing: 6.0,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: const Icon(Icons.save_outlined, size: 16.0),
|
||||
onTap: () {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
_updateActivity();
|
||||
widget.controller.setEditting(false);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: const Icon(Icons.close_outlined, size: 16.0),
|
||||
onTap: () => widget.controller.setEditting(false),
|
||||
),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
_updateActivity();
|
||||
widget.controller.onLaunch(widget.activity);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: Size.zero,
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context).inviteAndLaunch,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,9 @@
|
|||
// shows n rows of activity suggestions vertically, where n is the number of rows
|
||||
// as the user tries to scroll horizontally to the right, the client will fetch more activity suggestions
|
||||
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/http.dart';
|
||||
import 'package:matrix/matrix.dart' as sdk;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
|
|
@ -19,15 +11,10 @@ import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
|
|||
import 'package:fluffychat/pangea/activity_planner/media_enum.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_plan_search_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/create_chat_card.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ActivitySuggestionsArea extends StatefulWidget {
|
||||
|
|
@ -50,14 +37,12 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
ActivityPlanModel? selectedActivity;
|
||||
bool isEditing = false;
|
||||
Uint8List? avatar;
|
||||
final List<ActivityPlanModel> _activityItems = [];
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
final double cardHeight = 275.0;
|
||||
final double cardWidth = 250.0;
|
||||
final double cardHeight = 235.0;
|
||||
final double cardPadding = 8.0;
|
||||
double get cardWidth => FluffyThemes.isColumnMode(context) ? 225.0 : 160.0;
|
||||
|
||||
void _scrollToItem(int index) {
|
||||
final viewportDimension = _scrollController.position.viewportDimension;
|
||||
|
|
@ -98,131 +83,26 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
void setSelectedActivity(ActivityPlanModel? activity) {
|
||||
selectedActivity = activity;
|
||||
isEditing = false;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void setEditting(bool editting) {
|
||||
if (selectedActivity == null) return;
|
||||
isEditing = editting;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void selectPhoto() async {
|
||||
final photo = await selectFiles(
|
||||
context,
|
||||
type: FileSelectorType.images,
|
||||
allowMultiple: false,
|
||||
);
|
||||
final bytes = await photo.singleOrNull?.readAsBytes();
|
||||
|
||||
setState(() {
|
||||
avatar = bytes;
|
||||
});
|
||||
}
|
||||
|
||||
void updateActivity(
|
||||
ActivityPlanModel Function(ActivityPlanModel) update,
|
||||
) {
|
||||
if (selectedActivity == null) return;
|
||||
update(selectedActivity!);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<String?> _getAvatarURL(ActivityPlanModel activity) async {
|
||||
if (activity.imageURL == null && avatar == null) return null;
|
||||
try {
|
||||
if (avatar == null) {
|
||||
final Response response = await http.get(Uri.parse(activity.imageURL!));
|
||||
avatar = response.bodyBytes;
|
||||
}
|
||||
return (await Matrix.of(context).client.uploadContent(avatar!))
|
||||
.toString();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
data: {
|
||||
"imageURL": activity.imageURL,
|
||||
},
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> onLaunch(ActivityPlanModel activity) async {
|
||||
final client = Matrix.of(context).client;
|
||||
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final avatarURL = await _getAvatarURL(activity);
|
||||
final roomId = await client.createGroupChat(
|
||||
preset: CreateRoomPreset.publicChat,
|
||||
visibility: sdk.Visibility.private,
|
||||
groupName: activity.title,
|
||||
initialState: [
|
||||
if (avatarURL != null)
|
||||
StateEvent(
|
||||
type: EventTypes.RoomAvatar,
|
||||
content: {'url': avatarURL.toString()},
|
||||
),
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(client.userID!),
|
||||
),
|
||||
],
|
||||
enableEncryption: false,
|
||||
);
|
||||
|
||||
Room? room = Matrix.of(context).client.getRoomById(roomId);
|
||||
if (room == null) {
|
||||
await client.waitForRoomInSync(roomId);
|
||||
room = Matrix.of(context).client.getRoomById(roomId);
|
||||
if (room == null) return;
|
||||
}
|
||||
|
||||
final eventId = await room.pangeaSendTextEvent(
|
||||
activity.markdown,
|
||||
messageTag: ModelKey.messageTagActivityPlan,
|
||||
);
|
||||
|
||||
if (eventId == null) {
|
||||
debugger(when: kDebugMode);
|
||||
return;
|
||||
}
|
||||
|
||||
await room.setPinnedEvents([eventId]);
|
||||
context.go("/rooms/$roomId/invite");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> cards = _activityItems
|
||||
.mapIndexed((i, activity) {
|
||||
return ActivitySuggestionCard(
|
||||
activity: activity,
|
||||
controller: this,
|
||||
onPressed: () {
|
||||
if (isEditing && selectedActivity == activity) {
|
||||
setEditting(false);
|
||||
} else if (selectedActivity == activity) {
|
||||
setSelectedActivity(null);
|
||||
} else {
|
||||
setSelectedActivity(activity);
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToItem(i);
|
||||
});
|
||||
_scrollToItem(i);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ActivitySuggestionDialog(
|
||||
activity: activity,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
padding: cardPadding,
|
||||
);
|
||||
})
|
||||
.cast<Widget>()
|
||||
|
|
@ -233,25 +113,22 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
|
|||
CreateChatCard(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
padding: cardPadding,
|
||||
),
|
||||
);
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.topCenter,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: FluffyThemes.isColumnMode(context)
|
||||
? ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: cards.length,
|
||||
itemBuilder: (context, index) => cards[index],
|
||||
controller: _scrollController,
|
||||
)
|
||||
: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
child: Wrap(
|
||||
children: cards,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 16.0,
|
||||
horizontal: FluffyThemes.isColumnMode(context) ? 16.0 : 0.0,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
child: Wrap(
|
||||
children: cards,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
|||
class CreateChatCard extends StatelessWidget {
|
||||
final double width;
|
||||
final double height;
|
||||
final double padding;
|
||||
|
||||
const CreateChatCard({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.padding,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -22,7 +24,7 @@ class CreateChatCard extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: PressableButton(
|
||||
onPressed: () => context.go('/rooms/newgroup'),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
|
|
@ -46,11 +48,15 @@ class CreateChatCard extends StatelessWidget {
|
|||
width: 80,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24.0),
|
||||
Text(
|
||||
L10n.of(context).createOwnChat,
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: theme.colorScheme.secondary),
|
||||
const SizedBox(height: 16.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
L10n.of(context).createOwnChat,
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: theme.colorScheme.secondary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
|
||||
class FullWidthDialog extends StatelessWidget {
|
||||
final Widget dialogContent;
|
||||
final double maxWidth;
|
||||
|
|
@ -16,7 +17,7 @@ class FullWidthDialog extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final content = ConstrainedBox(
|
||||
constraints: kIsWeb
|
||||
constraints: FluffyThemes.isColumnMode(context)
|
||||
? BoxConstraints(
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
|
|
@ -31,6 +32,8 @@ class FullWidthDialog extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
|
||||
return kIsWeb ? Dialog(child: content) : Dialog.fullscreen(child: content);
|
||||
return FluffyThemes.isColumnMode(context)
|
||||
? Dialog(child: content)
|
||||
: Dialog.fullscreen(child: content);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue