feat: add activity suggestions to new chat page (#2235)

pull/1817/head
ggurdin 7 months ago committed by GitHub
parent 0faeb6f6ae
commit ee11c5596b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -4833,5 +4833,8 @@
"youUnlocked": "You've unlocked",
"resetInstructionTooltipsTitle": "Reset instruction tooltips",
"resetInstructionTooltipsDesc": "Click to show instruction tooltips like for a brand new user.",
"selectForGrammar": "Select a grammar icon for activities and details."
"selectForGrammar": "Select a grammar icon for activities and details.",
"newChatActivityTitle": "Add a fun activity",
"newChatActivityDesc": "Make every group chat an adventure with Activity Planner! Set captivating topics and objectives for the group, and bring conversations to life with stunning images. Spark imaginative discussions and keep the fun flowing effortlessly!",
"exploreMore": "Explore more"
}

@ -10,6 +10,7 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/new_group/new_group_view.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
@ -36,6 +37,7 @@ class NewGroupController extends State<NewGroup> {
TextEditingController nameController = TextEditingController();
// #Pangea
ActivityPlanModel? _selectedActivity;
bool requiredCodeToJoin = false;
// bool publicGroup = false;
// Pangea#
@ -61,6 +63,9 @@ class NewGroupController extends State<NewGroup> {
// void setPublicGroup(bool b) =>
// setState(() => publicGroup = groupCanBeFound = b);
void setRequireCode(bool b) => setState(() => requiredCodeToJoin = b);
void setSelectedActivity(ActivityPlanModel? activity) =>
setState(() => _selectedActivity = activity);
// Pangea#
void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b);
@ -113,6 +118,15 @@ class NewGroupController extends State<NewGroup> {
);
if (!mounted) return;
// #Pangea
if (_selectedActivity != null) {
Room? room = Matrix.of(context).client.getRoomById(roomId);
if (room == null) {
await Matrix.of(context).client.waitForRoomInSync(roomId);
room = Matrix.of(context).client.getRoomById(roomId);
}
if (room == null) return;
await room.sendActivityPlan(_selectedActivity!);
}
// if a timeout happened, don't redirect to the chat
if (error != null) return;
// Pangea#

@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/new_group/new_group.dart';
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_carousel.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
@ -36,6 +37,9 @@ class NewGroupView extends StatelessWidget {
),
),
body: MaxWidthBody(
// #Pangea
showBorder: false,
// Pangea#
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
@ -171,6 +175,13 @@ class NewGroupView extends StatelessWidget {
// onChanged: null,
// ),
// ),
if (controller.createGroupType == CreateGroupType.group)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: ActivitySuggestionCarousel(
onActivitySelected: controller.setSelectedActivity,
),
),
// Pangea#
AnimatedSize(
duration: FluffyThemes.animationDuration,

@ -79,7 +79,7 @@ class ActivitySuggestionCard extends StatelessWidget {
style: const TextStyle(
fontWeight: FontWeight.bold,
),
maxLines: 1,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),

@ -0,0 +1,285 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:shimmer/shimmer.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
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/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
class ActivitySuggestionCarousel extends StatefulWidget {
final Function(ActivityPlanModel?) onActivitySelected;
const ActivitySuggestionCarousel({
required this.onActivitySelected,
super.key,
});
@override
ActivitySuggestionCarouselState createState() =>
ActivitySuggestionCarouselState();
}
class ActivitySuggestionCarouselState
extends State<ActivitySuggestionCarousel> {
bool _isOpen = true;
bool _loading = true;
String? _error;
double get _cardWidth => _isColumnMode ? 250.0 : 200.0;
final double _cardHeight = 275.0;
ActivityPlanModel? _currentActivity;
final List<ActivityPlanModel> _activityItems = [];
@override
void initState() {
super.initState();
_setActivityItems();
}
Future<void> _setActivityItems() async {
try {
final ActivityPlanRequest request = ActivityPlanRequest(
topic: "",
mode: "",
objective: "",
media: MediaEnum.nan,
cefrLevel: LanguageLevelTypeEnum.a1,
languageOfInstructions: LanguageKeys.defaultLanguage,
targetLanguage:
MatrixState.pangeaController.languageController.userL2?.langCode ??
LanguageKeys.defaultLanguage,
numberOfParticipants: 3,
count: 5,
);
final resp = await ActivitySearchRepo.get(request);
_activityItems.addAll(resp.activityPlans);
} catch (e) {
_error = e.toString();
} finally {
_loading = false;
_currentActivity =
_activityItems.isNotEmpty ? _activityItems.first : null;
if (mounted) setState(() {});
}
}
bool get _isColumnMode => FluffyThemes.isColumnMode(context);
int? get _currentIndex {
if (_currentActivity == null) return null;
final index = _activityItems.indexOf(_currentActivity!);
return index == -1 ? null : index;
}
bool get _canMoveLeft => _currentIndex != null && _currentIndex! > 0;
bool get _canMoveRight =>
_currentIndex != null && _currentIndex! < _activityItems.length - 1;
void _moveLeft() {
if (!_canMoveLeft) return;
_setActivityByIndex(_currentIndex! - 1);
}
void _moveRight() {
if (!_canMoveRight) return;
_setActivityByIndex(_currentIndex! + 1);
}
void _setActivityByIndex(int index) {
if (index < 0 || index >= _activityItems.length) return;
widget.onActivitySelected(_activityItems[index]);
setState(() {
_currentActivity = _activityItems[index];
});
}
void _close() {
widget.onActivitySelected(null);
setState(() => _isOpen = false);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return AnimatedSize(
duration: FluffyThemes.animationDuration,
child: !_isOpen
? const SizedBox.shrink()
: Container(
decoration: BoxDecoration(
border: Border.all(color: theme.dividerColor),
borderRadius: BorderRadius.circular(24.0),
),
padding: const EdgeInsets.all(16.0),
child: Column(
spacing: 16.0,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
L10n.of(context).newChatActivityTitle,
style: theme.textTheme.titleLarge,
),
IconButton(
icon: const Icon(Icons.close),
onPressed: _close,
),
],
),
Text(L10n.of(context).newChatActivityDesc),
Row(
spacing: _isColumnMode ? 16.0 : 4.0,
mainAxisAlignment: MainAxisAlignment.center,
children: [
MouseRegion(
cursor: _canMoveLeft
? SystemMouseCursors.click
: SystemMouseCursors.basic,
child: GestureDetector(
onTap: _canMoveLeft ? _moveLeft : null,
child: Icon(
Icons.chevron_left_outlined,
size: 32.0,
color: _canMoveLeft ? null : theme.disabledColor,
),
),
),
Container(
constraints:
BoxConstraints(maxHeight: _cardHeight + 12.0),
child: _error != null ||
(_currentActivity == null && !_loading)
? const SizedBox.shrink()
: _loading
? Shimmer.fromColors(
baseColor:
theme.colorScheme.primary.withAlpha(50),
highlightColor: theme.colorScheme.primary
.withAlpha(150),
child: Container(
height: _cardHeight,
width: _cardWidth,
decoration: BoxDecoration(
color:
theme.colorScheme.surfaceContainer,
borderRadius:
BorderRadius.circular(24.0),
),
),
)
: ActivitySuggestionCard(
activity: _currentActivity!,
onPressed: () {
showDialog(
context: context,
builder: (context) {
return ActivitySuggestionDialog(
activity: _currentActivity!,
);
},
);
},
width: _cardWidth,
height: _cardHeight,
padding: 0.0,
),
),
MouseRegion(
cursor: _canMoveRight
? SystemMouseCursors.click
: SystemMouseCursors.basic,
child: GestureDetector(
onTap: _canMoveRight ? _moveRight : null,
child: Icon(
Icons.chevron_right_outlined,
size: 32.0,
color: _canMoveRight ? null : theme.disabledColor,
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 16.0,
children: _activityItems.mapIndexed((i, activity) {
final selected = activity == _currentActivity;
return InkWell(
borderRadius: BorderRadius.circular(12.0),
onTap: () => _setActivityByIndex(i),
child: ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: selected ? 0.0 : 0.5,
sigmaY: selected ? 0.0 : 0.5,
),
child: Opacity(
opacity: selected ? 1.0 : 0.5,
child: ClipOval(
child: SizedBox.fromSize(
size: const Size.fromRadius(12.0),
child: activity.imageURL != null
? CachedNetworkImage(
imageUrl: activity.imageURL!,
errorWidget: (context, url, error) {
return CircleAvatar(
backgroundColor:
theme.colorScheme.secondary,
radius: 12.0,
);
},
progressIndicatorBuilder:
(context, url, progress) {
return CircularProgressIndicator(
value: progress.progress,
);
},
)
: CircleAvatar(
backgroundColor:
theme.colorScheme.secondary,
radius: 12.0,
),
),
),
),
),
);
}).toList(),
),
ElevatedButton(
onPressed: () => _isColumnMode
? context.go("/rooms")
: context.go("/rooms/homepage"),
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primaryContainer,
),
child: _loading
? const LinearProgressIndicator()
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(L10n.of(context).exploreMore),
],
),
),
],
),
),
);
}
}

@ -38,7 +38,7 @@ class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
final List<ActivityPlanModel> _activityItems = [];
final ScrollController _scrollController = ScrollController();
final double cardHeight = 235.0;
final double cardHeight = 250.0;
double get cardPadding => _isColumnMode ? 8.0 : 0.0;
double get cardWidth => _isColumnMode ? 225.0 : 150.0;

@ -8,12 +8,18 @@ class MaxWidthBody extends StatelessWidget {
final double maxWidth;
final bool withScrolling;
final EdgeInsets? innerPadding;
// #Pangea
final bool showBorder;
// Pangea#
const MaxWidthBody({
required this.child,
this.maxWidth = 600,
this.withScrolling = true,
this.innerPadding,
// #Pangea
this.showBorder = true,
// Pangea#
super.key,
});
@override
@ -38,7 +44,12 @@ class MaxWidthBody extends StatelessWidget {
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
side: BorderSide(
color: theme.dividerColor,
// #Pangea
// color: theme.dividerColor,
color: showBorder
? theme.dividerColor
: Colors.transparent,
// Pangea#
),
),
clipBehavior: Clip.hardEdge,

Loading…
Cancel
Save