feat: activity dropdown menu
parent
5ce2a787b4
commit
b45541d826
@ -1,216 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_constants.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
class ActivityPinnedMessage extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
const ActivityPinnedMessage(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
State<ActivityPinnedMessage> createState() => ActivityPinnedMessageState();
|
||||
}
|
||||
|
||||
class ActivityPinnedMessageState extends State<ActivityPinnedMessage> {
|
||||
bool _showDropdown = false;
|
||||
|
||||
Room get room => widget.controller.room;
|
||||
|
||||
void _scrollToActivity() {
|
||||
final eventId = widget.controller.timeline?.events
|
||||
.firstWhereOrNull(
|
||||
(e) => e.type == PangeaEventTypes.activityPlan,
|
||||
)
|
||||
?.eventId;
|
||||
if (eventId == null) return;
|
||||
widget.controller.scrollToEventId(eventId);
|
||||
}
|
||||
|
||||
void _setShowDropdown(bool value) {
|
||||
if (value != _showDropdown) {
|
||||
setState(() {
|
||||
_showDropdown = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _finishActivity({bool forAll = false}) async {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
forAll
|
||||
? await room.finishActivityForAll()
|
||||
: await room.finishActivity();
|
||||
if (mounted) {
|
||||
_setShowDropdown(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// if the room has no activity, or if it doesn't have the permission
|
||||
// levels for sending the required events, don't show the pinned message
|
||||
if (!room.isActiveInActivity) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: _showDropdown ? 0 : null,
|
||||
child: Column(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
decoration: BoxDecoration(
|
||||
color: _showDropdown
|
||||
? theme.colorScheme.surfaceContainerHighest
|
||||
: theme.colorScheme.surface,
|
||||
),
|
||||
child: ChatAppBarListTile(
|
||||
title: "🎯 ${room.activityPlan!.learningObjective}",
|
||||
leading: const SizedBox(width: 18.0),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: ElevatedButton(
|
||||
onPressed:
|
||||
_showDropdown ? null : () => _setShowDropdown(true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: Size.zero,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 4.0,
|
||||
),
|
||||
backgroundColor: AppConfig.yellowDark,
|
||||
foregroundColor: theme.colorScheme.surface,
|
||||
disabledBackgroundColor:
|
||||
AppConfig.yellowDark.withAlpha(100),
|
||||
disabledForegroundColor:
|
||||
theme.colorScheme.surface.withAlpha(100),
|
||||
),
|
||||
child: Text(
|
||||
L10n.of(context).endActivityTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: _scrollToActivity,
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
child: ClipRect(
|
||||
child: _showDropdown
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).endActivityDesc,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 16.0 : 12.0,
|
||||
),
|
||||
),
|
||||
CachedNetworkImage(
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${ActivitySessionConstants.endActivityAssetPath}",
|
||||
width: isColumnMode ? 240.0 : 120.0,
|
||||
),
|
||||
Row(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
foregroundColor:
|
||||
theme.colorScheme.onSecondary,
|
||||
backgroundColor:
|
||||
theme.colorScheme.secondary,
|
||||
),
|
||||
onPressed: _finishActivity,
|
||||
child: Text(
|
||||
L10n.of(context).endActivityTitle,
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 16.0 : 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (room.isRoomAdmin)
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
foregroundColor:
|
||||
theme.colorScheme.onErrorContainer,
|
||||
backgroundColor:
|
||||
theme.colorScheme.errorContainer,
|
||||
),
|
||||
onPressed: () =>
|
||||
_finishActivity(forAll: true),
|
||||
child: Text(
|
||||
L10n.of(context).endForAll,
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 16.0 : 12.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
if (_showDropdown)
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => _setShowDropdown(false),
|
||||
child: Container(color: Colors.black.withAlpha(100)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_summary/activity_summary_analytics_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ActivityStatsButton extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
|
||||
const ActivityStatsButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ActivityStatsButton> createState() => _ActivityStatsButtonState();
|
||||
}
|
||||
|
||||
class _ActivityStatsButtonState extends State<ActivityStatsButton> {
|
||||
StreamSubscription? _analyticsSubscription;
|
||||
ActivitySummaryAnalyticsModel analytics = ActivitySummaryAnalyticsModel();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => _updateAnalytics(),
|
||||
);
|
||||
|
||||
_analyticsSubscription = widget
|
||||
.controller.pangeaController.getAnalytics.analyticsStream.stream
|
||||
.listen((_) {
|
||||
_updateAnalytics();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_analyticsSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
int get xpCount => analytics.totalXPForUser(
|
||||
Matrix.of(context).client.userID ?? '',
|
||||
);
|
||||
|
||||
int get vocabCount => analytics.uniqueConstructCountForUser(
|
||||
widget.controller.room.client.userID!,
|
||||
ConstructTypeEnum.vocab,
|
||||
);
|
||||
|
||||
int get grammarCount => analytics.uniqueConstructCountForUser(
|
||||
widget.controller.room.client.userID!,
|
||||
ConstructTypeEnum.morph,
|
||||
);
|
||||
|
||||
Future<void> _updateAnalytics() async {
|
||||
final analytics = await widget.controller.room.getActivityAnalytics();
|
||||
if (mounted) {
|
||||
setState(() => this.analytics = analytics);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 350,
|
||||
height: 55,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: () => widget.controller.setShowDropdown(
|
||||
!widget.controller.showActivityDropdown,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppConfig.goldLight.withAlpha(100),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_StatsBadge(icon: Icons.radar, value: "$xpCount XP"),
|
||||
_StatsBadge(icon: Symbols.dictionary, value: "$vocabCount"),
|
||||
_StatsBadge(
|
||||
icon: Symbols.toys_and_games,
|
||||
value: "$grammarCount",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StatsBadge extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String value;
|
||||
const _StatsBadge({
|
||||
required this.icon,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final baseStyle = theme.textTheme.bodyMedium;
|
||||
final double fontSize = (screenWidth < 400) ? 10 : 14;
|
||||
final double iconSize = (screenWidth < 400) ? 14 : 18;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: iconSize,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: baseStyle?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,341 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_details_row.dart';
|
||||
import 'package:fluffychat/pangea/activity_summary/activity_summary_analytics_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
class ActivityStatsMenu extends StatefulWidget {
|
||||
final ChatController controller;
|
||||
const ActivityStatsMenu(
|
||||
this.controller, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ActivityStatsMenu> createState() => ActivityStatsMenuState();
|
||||
}
|
||||
|
||||
class ActivityStatsMenuState extends State<ActivityStatsMenu> {
|
||||
ActivitySummaryAnalyticsModel? analytics;
|
||||
Room get room => widget.controller.room;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_updateUsedVocab();
|
||||
});
|
||||
}
|
||||
|
||||
Set<String>? get _usedVocab => analytics?.constructs[room.client.userID!]
|
||||
?.constructsOfType(ConstructTypeEnum.vocab)
|
||||
.map((id) => id.lemma)
|
||||
.toSet();
|
||||
|
||||
double get _percentVocabComplete {
|
||||
final vocabList = room.activityPlan?.vocabList ?? [];
|
||||
if (vocabList.isEmpty || _usedVocab == null) {
|
||||
return 0;
|
||||
}
|
||||
return _usedVocab!.intersection(vocabList.toSet()).length /
|
||||
vocabList.length;
|
||||
}
|
||||
|
||||
Future<void> _updateUsedVocab() async {
|
||||
final analytics = await room.getActivityAnalytics();
|
||||
if (mounted) {
|
||||
setState(() => this.analytics = analytics);
|
||||
}
|
||||
}
|
||||
|
||||
int _getAssignedRolesCount() {
|
||||
final assignedRoles = room.assignedRoles;
|
||||
if (assignedRoles == null) return 0;
|
||||
final nonBotRoles = assignedRoles.values.where(
|
||||
(role) => role.userId != BotName.byEnvironment,
|
||||
);
|
||||
|
||||
return nonBotRoles.length;
|
||||
}
|
||||
|
||||
int _getCompletedRolesCount() {
|
||||
final assignedRoles = room.assignedRoles;
|
||||
if (assignedRoles == null) return 0;
|
||||
|
||||
// Filter out the bot and count only finished non-bot roles
|
||||
return assignedRoles.values
|
||||
.where(
|
||||
(role) => role.userId != BotName.byEnvironment && role.isFinished,
|
||||
)
|
||||
.length;
|
||||
}
|
||||
|
||||
bool _isBotParticipant() {
|
||||
final assignedRoles = room.assignedRoles;
|
||||
if (assignedRoles == null) return false;
|
||||
return assignedRoles.values.any(
|
||||
(role) => role.userId == BotName.byEnvironment,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _finishActivity({bool forAll = false}) async {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
forAll
|
||||
? await room.finishActivityForAll()
|
||||
: await room.finishActivity();
|
||||
if (mounted) {
|
||||
widget.controller.setShowDropdown(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!room.showActivityChatUI) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
|
||||
// Completion status variables
|
||||
final bool userComplete = room.hasCompletedActivity;
|
||||
final bool activityComplete = room.activityIsFinished;
|
||||
bool shouldShowEndForAll = true;
|
||||
bool shouldShowImDone = true;
|
||||
String message = "";
|
||||
|
||||
if (!room.isRoomAdmin) {
|
||||
shouldShowEndForAll = false;
|
||||
}
|
||||
|
||||
//dont need endforall if only w bot
|
||||
if ((_getAssignedRolesCount() == 1) && (_isBotParticipant() == true)) {
|
||||
shouldShowEndForAll = false;
|
||||
}
|
||||
|
||||
if (activityComplete) {
|
||||
//activity is finished, no buttons
|
||||
shouldShowImDone = false;
|
||||
shouldShowEndForAll = false;
|
||||
message = L10n.of(context).activityComplete;
|
||||
} else {
|
||||
//activity is ongoing
|
||||
if (_getCompletedRolesCount() == 0 ||
|
||||
(_getAssignedRolesCount() == 1) && (_isBotParticipant() == true)) {
|
||||
//IF nobodys done or you're only playing with the bot,
|
||||
//Then it should show tips about your progress and nudge you to continue/end
|
||||
if ((_percentVocabComplete < .7) && (_usedVocab?.length ?? 0) < 50) {
|
||||
message = L10n.of(context).haventChattedMuch;
|
||||
} else {
|
||||
message = L10n.of(context).haveChatted;
|
||||
}
|
||||
} else {
|
||||
//user is in group with other users OR someone has wrapped up
|
||||
if (userComplete) {
|
||||
//user is done but group is ongoing, no buttons
|
||||
message = L10n.of(context).userDoneAndWaiting(
|
||||
_getCompletedRolesCount(),
|
||||
_getAssignedRolesCount(),
|
||||
);
|
||||
} else {
|
||||
//user is not done, buttons are present
|
||||
message = L10n.of(context).othersDoneAndWaiting(
|
||||
_getCompletedRolesCount(),
|
||||
_getAssignedRolesCount(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: widget.controller.showActivityDropdown ? 0 : null,
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRect(
|
||||
child: AnimatedAlign(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: Curves.easeInOut,
|
||||
heightFactor: widget.controller.showActivityDropdown ? 1.0 : 0.0,
|
||||
alignment: Alignment.topCenter,
|
||||
child: GestureDetector(
|
||||
onPanUpdate: (details) {
|
||||
if (details.delta.dy < -2) {
|
||||
widget.controller.setShowDropdown(false);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Column(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ActivitySessionDetailsRow(
|
||||
icon: Symbols.radar,
|
||||
iconSize: 16.0,
|
||||
child: Text(
|
||||
room.activityPlan!.learningObjective,
|
||||
style: const TextStyle(fontSize: 12.0),
|
||||
),
|
||||
),
|
||||
ActivitySessionDetailsRow(
|
||||
icon: Symbols.dictionary,
|
||||
iconSize: 16.0,
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: [
|
||||
...room.activityPlan!.vocabList.map(
|
||||
(vocabWord) => VocabTile(
|
||||
vocabWord: vocabWord,
|
||||
isUsed:
|
||||
(_usedVocab ?? {}).contains(vocabWord),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (!userComplete) ...[
|
||||
if (shouldShowEndForAll)
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
side: BorderSide(
|
||||
color: theme.colorScheme.secondaryContainer,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: theme.colorScheme.primary,
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
),
|
||||
onPressed: () => _finishActivity(forAll: true),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).endForAll,
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 16.0 : 12.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (shouldShowImDone)
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
),
|
||||
onPressed: _finishActivity,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).endActivityTitle,
|
||||
style: TextStyle(
|
||||
fontSize: isColumnMode ? 16.0 : 12.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.controller.showActivityDropdown)
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => widget.controller.setShowDropdown(false),
|
||||
child: Container(color: Colors.black.withAlpha(100)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VocabTile extends StatelessWidget {
|
||||
final String vocabWord;
|
||||
final bool isUsed;
|
||||
|
||||
const VocabTile({
|
||||
super.key,
|
||||
required this.vocabWord,
|
||||
required this.isUsed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color =
|
||||
isUsed ? AppConfig.goldLight.withAlpha(100) : Colors.transparent;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 4.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
vocabWord,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue