feat: start a chat using an activity template (#2107)
Co-authored-by: wcjord <32568597+wcjord@users.noreply.github.com>
This commit is contained in:
parent
e150a3b0a9
commit
07cbf2426a
15 changed files with 1209 additions and 58 deletions
|
|
@ -4715,7 +4715,7 @@
|
|||
"myBookmarkedActivities": "My Bookmarked Activities",
|
||||
"noBookmarkedActivities": "No bookmarked activities",
|
||||
"activityTitle": "Activity Title",
|
||||
"addVocabulary": "Add Vocabulary",
|
||||
"addVocabulary": "Add vocabulary",
|
||||
"instructions": "Instructions",
|
||||
"bookmark": "Bookmark this activity",
|
||||
"numberOfLearners": "Number of learners",
|
||||
|
|
@ -4807,5 +4807,9 @@
|
|||
"morphAnalyticsListBody": "These are all the grammar concepts in the language you're learning! You'll unlock them as you encounter them while chatting. Click for details.",
|
||||
"knockSpaceSuccess": "You have requested to join this space! An admin will respond to your request when they receive it 😀",
|
||||
"joinByCode": "Join by code",
|
||||
"createASpace": "Create a space"
|
||||
"createASpace": "Create a space",
|
||||
"inviteAndLaunch": "Invite and launch",
|
||||
"createOwnChat": "Create your own chat",
|
||||
"pleaseEnterInt": "Please enter a number",
|
||||
"home": "Home"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ import 'package:fluffychat/pages/settings_password/settings_password.dart';
|
|||
import 'package:fluffychat/pages/settings_security/settings_security.dart';
|
||||
import 'package:fluffychat/pages/settings_style/settings_style.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/suggestions_page.dart';
|
||||
import 'package:fluffychat/pangea/guard/p_vguard.dart';
|
||||
import 'package:fluffychat/pangea/layouts/bottom_nav_layout.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/login_or_signup_view.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/signup.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/user_settings.dart';
|
||||
|
|
@ -176,7 +179,13 @@ abstract class AppRoutes {
|
|||
),
|
||||
sideView: child,
|
||||
)
|
||||
: child,
|
||||
// #Pangea
|
||||
// : child,
|
||||
: state.fullPath?.split("/").reversed.elementAt(1) == 'rooms' &&
|
||||
state.pathParameters['roomid'] != null
|
||||
? child
|
||||
: BottomNavLayout(mainView: child),
|
||||
// Pangea#
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
|
|
@ -186,7 +195,10 @@ abstract class AppRoutes {
|
|||
context,
|
||||
state,
|
||||
FluffyThemes.isColumnMode(context)
|
||||
? const EmptyPage()
|
||||
// #Pangea
|
||||
// ? const EmptyPage()
|
||||
? const ActivitySuggestionsArea()
|
||||
// Pangea#
|
||||
: ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
),
|
||||
|
|
@ -233,6 +245,15 @@ abstract class AppRoutes {
|
|||
const JoinWithAlias(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/homepage',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SuggestionsPage(),
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
GoRoute(
|
||||
path: 'archive',
|
||||
|
|
|
|||
|
|
@ -53,16 +53,18 @@ class SettingsView extends StatelessWidget {
|
|||
],
|
||||
Expanded(
|
||||
child: Scaffold(
|
||||
appBar: FluffyThemes.isColumnMode(context)
|
||||
? null
|
||||
: AppBar(
|
||||
title: Text(L10n.of(context).settings),
|
||||
leading: Center(
|
||||
child: BackButton(
|
||||
onPressed: () => context.go('/rooms'),
|
||||
),
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
// appBar: FluffyThemes.isColumnMode(context)
|
||||
// ? null
|
||||
// : AppBar(
|
||||
// title: Text(L10n.of(context).settings),
|
||||
// leading: Center(
|
||||
// child: BackButton(
|
||||
// onPressed: () => context.go('/rooms'),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Pangea#
|
||||
body: ListTileTheme(
|
||||
iconColor: theme.colorScheme.onSurface,
|
||||
child: ListView(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
|
||||
|
||||
class ActivityPlanModel {
|
||||
final ActivityPlanRequest req;
|
||||
final String title;
|
||||
final String learningObjective;
|
||||
final String instructions;
|
||||
final List<Vocab> vocab;
|
||||
String title;
|
||||
String learningObjective;
|
||||
String instructions;
|
||||
List<Vocab> vocab;
|
||||
String? imageURL;
|
||||
String? bookmarkId;
|
||||
|
||||
ActivityPlanModel({
|
||||
|
|
@ -14,6 +17,7 @@ class ActivityPlanModel {
|
|||
required this.learningObjective,
|
||||
required this.instructions,
|
||||
required this.vocab,
|
||||
this.imageURL,
|
||||
this.bookmarkId,
|
||||
});
|
||||
|
||||
|
|
@ -27,6 +31,7 @@ class ActivityPlanModel {
|
|||
json['vocab'].map((vocab) => Vocab.fromJson(vocab)),
|
||||
),
|
||||
bookmarkId: json['bookmark_id'],
|
||||
imageURL: json['image_url'],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +43,7 @@ class ActivityPlanModel {
|
|||
'instructions': instructions,
|
||||
'vocab': vocab.map((vocab) => vocab.toJson()).toList(),
|
||||
'bookmark_id': bookmarkId,
|
||||
'image_url': imageURL,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +69,28 @@ class ActivityPlanModel {
|
|||
bool get isBookmarked {
|
||||
return bookmarkId != null;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is ActivityPlanModel &&
|
||||
other.req == req &&
|
||||
other.title == title &&
|
||||
other.learningObjective == learningObjective &&
|
||||
other.instructions == instructions &&
|
||||
listEquals(other.vocab, vocab) &&
|
||||
other.bookmarkId == bookmarkId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
req.hashCode ^
|
||||
title.hashCode ^
|
||||
learningObjective.hashCode ^
|
||||
instructions.hashCode ^
|
||||
Object.hashAll(vocab) ^
|
||||
bookmarkId.hashCode;
|
||||
}
|
||||
|
||||
class Vocab {
|
||||
|
|
@ -87,4 +115,14 @@ class Vocab {
|
|||
'pos': pos,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is Vocab && other.lemma == lemma && other.pos == pos;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => lemma.hashCode ^ pos.hashCode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class ActivityPlanRequest {
|
|||
final String languageOfInstructions;
|
||||
final String targetLanguage;
|
||||
final int count;
|
||||
final int numberOfParticipants;
|
||||
int numberOfParticipants;
|
||||
|
||||
ActivityPlanRequest({
|
||||
required this.topic,
|
||||
|
|
@ -57,4 +57,32 @@ class ActivityPlanRequest {
|
|||
|
||||
String get storageKey =>
|
||||
'$topic-$mode-$objective-${media.string}-$cefrLevel-$languageOfInstructions-$targetLanguage-$numberOfParticipants';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is ActivityPlanRequest &&
|
||||
other.topic == topic &&
|
||||
other.mode == mode &&
|
||||
other.objective == objective &&
|
||||
other.media == media &&
|
||||
other.cefrLevel == cefrLevel &&
|
||||
other.languageOfInstructions == languageOfInstructions &&
|
||||
other.targetLanguage == targetLanguage &&
|
||||
other.count == count &&
|
||||
other.numberOfParticipants == numberOfParticipants;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
topic.hashCode ^
|
||||
mode.hashCode ^
|
||||
objective.hashCode ^
|
||||
media.hashCode ^
|
||||
cefrLevel.hashCode ^
|
||||
languageOfInstructions.hashCode ^
|
||||
targetLanguage.hashCode ^
|
||||
count.hashCode ^
|
||||
numberOfParticipants.hashCode;
|
||||
}
|
||||
|
|
|
|||
117
lib/pangea/activity_suggestions/activity_suggestion_card.dart
Normal file
117
lib/pangea/activity_suggestions/activity_suggestion_card.dart
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
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_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/common/widgets/pressable_button.dart';
|
||||
|
||||
class ActivitySuggestionCard extends StatelessWidget {
|
||||
final ActivityPlanModel activity;
|
||||
final ActivitySuggestionsAreaState controller;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const ActivitySuggestionCard({
|
||||
super.key,
|
||||
required this.activity,
|
||||
required this.controller,
|
||||
required this.onPressed,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
bool get _isSelected => controller.selectedActivity == activity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
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,
|
||||
width: width,
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
height: 100,
|
||||
width: width,
|
||||
decoration: BoxDecoration(
|
||||
image: activity.imageURL != null
|
||||
? DecorationImage(
|
||||
image: controller.avatar == null || !_isSelected
|
||||
? NetworkImage(activity.imageURL!)
|
||||
: MemoryImage(controller.avatar!)
|
||||
as ImageProvider<Object>,
|
||||
)
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16.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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ActivitySuggestionCardRow extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Widget child;
|
||||
|
||||
const ActivitySuggestionCardRow({
|
||||
required this.icon,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 12.0,
|
||||
),
|
||||
Expanded(child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
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,45 +1,257 @@
|
|||
// // 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
|
||||
// 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 'package:flutter/material.dart';
|
||||
import 'dart:developer';
|
||||
|
||||
// import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
|
||||
// import 'package:fluffychat/pangea/activity_suggestions/activity_plan_search_repo.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// class ActivitySuggestionsArea extends StatefulWidget {
|
||||
// const ActivitySuggestionsArea({super.key});
|
||||
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';
|
||||
|
||||
// @override
|
||||
// ActivitySuggestionsAreaState createState() => ActivitySuggestionsAreaState();
|
||||
// }
|
||||
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/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 ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// }
|
||||
class ActivitySuggestionsArea extends StatefulWidget {
|
||||
const ActivitySuggestionsArea({super.key});
|
||||
|
||||
// Future<void> fetchMoreSuggestions() async {
|
||||
// ActivitySearchRepo.get(
|
||||
// ActivityPlanRequest(),
|
||||
// );
|
||||
// }
|
||||
@override
|
||||
ActivitySuggestionsAreaState createState() => ActivitySuggestionsAreaState();
|
||||
}
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Container(
|
||||
// child: ListView.builder(
|
||||
// scrollDirection: Axis.vertical,
|
||||
// itemCount: 5,
|
||||
// itemBuilder: (context, index) {
|
||||
// return Container(
|
||||
// height: 100,
|
||||
// width: 100,
|
||||
// color: Colors.blue,
|
||||
// margin: const EdgeInsets.all(10),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setActivityItems();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
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;
|
||||
|
||||
void _scrollToItem(int index) {
|
||||
final viewportDimension = _scrollController.position.viewportDimension;
|
||||
final double scrollOffset = FluffyThemes.isColumnMode(context)
|
||||
? index * cardWidth - (viewportDimension / 2) + (cardWidth / 2)
|
||||
: (index + 1) * (cardHeight + 8.0);
|
||||
|
||||
final maxScrollExtent = _scrollController.position.maxScrollExtent;
|
||||
final safeOffset = scrollOffset.clamp(0.0, maxScrollExtent);
|
||||
|
||||
if (safeOffset == _scrollController.offset) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scrollController.animateTo(
|
||||
safeOffset,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _setActivityItems() async {
|
||||
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);
|
||||
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);
|
||||
});
|
||||
},
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
);
|
||||
})
|
||||
.cast<Widget>()
|
||||
.toList();
|
||||
|
||||
cards.insert(
|
||||
0,
|
||||
CreateChatCard(
|
||||
width: cardWidth,
|
||||
height: cardHeight,
|
||||
),
|
||||
);
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
class ActivitySuggestionsConstants {
|
||||
static const String plusIconPath = "add_icon.svg";
|
||||
}
|
||||
61
lib/pangea/activity_suggestions/create_chat_card.dart
Normal file
61
lib/pangea/activity_suggestions/create_chat_card.dart
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
||||
|
||||
class CreateChatCard extends StatelessWidget {
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const CreateChatCard({
|
||||
required this.width,
|
||||
required this.height,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PressableButton(
|
||||
onPressed: () => context.go('/rooms/newgroup'),
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
color: theme.colorScheme.primary,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
),
|
||||
height: height,
|
||||
width: width,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child: CustomizedSvg(
|
||||
svgUrl:
|
||||
"${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.plusIconPath}",
|
||||
colorReplacements: const {},
|
||||
height: 80,
|
||||
width: 80,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24.0),
|
||||
Text(
|
||||
L10n.of(context).createOwnChat,
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: theme.colorScheme.secondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/pangea/activity_suggestions/suggestions_page.dart
Normal file
31
lib/pangea/activity_suggestions/suggestions_page.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart';
|
||||
|
||||
class SuggestionsPage extends StatelessWidget {
|
||||
const SuggestionsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
child: LearningProgressIndicators(),
|
||||
),
|
||||
Expanded(
|
||||
child: ActivitySuggestionsArea(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -111,7 +111,11 @@ class CustomizedSvg extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final cached = _getSvgFromCache();
|
||||
if (cached != null) {
|
||||
return SvgPicture.string(cached);
|
||||
return SvgPicture.string(
|
||||
cached,
|
||||
width: width,
|
||||
height: height,
|
||||
);
|
||||
}
|
||||
|
||||
return FutureBuilder<String?>(
|
||||
|
|
|
|||
151
lib/pangea/layouts/bottom_nav_layout.dart
Normal file
151
lib/pangea/layouts/bottom_nav_layout.dart
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class BottomNavLayout extends StatelessWidget {
|
||||
final Widget mainView;
|
||||
|
||||
const BottomNavLayout({
|
||||
super.key,
|
||||
required this.mainView,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: mainView,
|
||||
bottomNavigationBar: const BottomNavBar(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BottomNavBar extends StatefulWidget {
|
||||
const BottomNavBar({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
BottomNavBarState createState() => BottomNavBarState();
|
||||
}
|
||||
|
||||
class BottomNavBarState extends State<BottomNavBar> {
|
||||
int get selectedIndex {
|
||||
final route = GoRouterState.of(context).fullPath.toString();
|
||||
if (route.contains("settings")) {
|
||||
return 2;
|
||||
}
|
||||
if (route.contains('homepage')) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant BottomNavBar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
debugPrint("didUpdateWidget");
|
||||
}
|
||||
|
||||
void onItemTapped(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
context.go('/rooms/homepage');
|
||||
break;
|
||||
case 1:
|
||||
context.go('/rooms');
|
||||
break;
|
||||
case 2:
|
||||
context.go('/rooms/settings');
|
||||
break;
|
||||
}
|
||||
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
BottomNavItem(
|
||||
controller: this,
|
||||
icon: Icons.home,
|
||||
label: L10n.of(context).home,
|
||||
index: 0,
|
||||
),
|
||||
BottomNavItem(
|
||||
controller: this,
|
||||
icon: Icons.chat_bubble_outline,
|
||||
label: L10n.of(context).chats,
|
||||
index: 1,
|
||||
),
|
||||
BottomNavItem(
|
||||
controller: this,
|
||||
icon: Icons.settings,
|
||||
label: L10n.of(context).settings,
|
||||
index: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BottomNavItem extends StatelessWidget {
|
||||
final BottomNavBarState controller;
|
||||
final int index;
|
||||
final IconData icon;
|
||||
final String label;
|
||||
|
||||
const BottomNavItem({
|
||||
required this.controller,
|
||||
required this.index,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSelected = controller.selectedIndex == index;
|
||||
final theme = Theme.of(context);
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => controller.onItemTapped(index),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 6.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.secondaryContainer,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: isSelected
|
||||
? theme.colorScheme.onPrimary
|
||||
: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? theme.colorScheme.onPrimary
|
||||
: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue