Merge branch 'main' of https://github.com/pangeachat/client into remove-duplicate-push
This commit is contained in:
commit
031f985d11
34 changed files with 1078 additions and 885 deletions
|
|
@ -4943,5 +4943,16 @@
|
|||
"launchActivityToChats": "Launch activity to chats",
|
||||
"searchChats": "Search chats",
|
||||
"selectChats": "Select chats",
|
||||
"selectChatToStart": "Complete! Select a chat to start"
|
||||
"selectChatToStart": "Complete! Select a chat to start",
|
||||
"configureSpace": "Configure space",
|
||||
"pinMessages": "Pin messages",
|
||||
"setJoinRules": "Set join rules",
|
||||
"displayNavigationRail": "Show navigation rail on mobile",
|
||||
"changeGeneralSettings": "Change general settings",
|
||||
"inviteOtherUsersToRoom": "Invite other users",
|
||||
"changeTheNameOfTheSpace": "Change the name of the space",
|
||||
"changeTheDescription": "Change the description",
|
||||
"changeThePermissions": "Change the permissions",
|
||||
"introductions": "Introductions",
|
||||
"announcements": "Announcements"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,6 +134,9 @@ abstract class AppConfig {
|
|||
static bool swipeRightToLeftToReply = true;
|
||||
static bool? sendOnEnter;
|
||||
static bool showPresences = true;
|
||||
// #Pangea
|
||||
static bool displayNavigationRail = true;
|
||||
// Pangea#
|
||||
static bool experimentalVoip = false;
|
||||
static const bool hideTypingUsernames = false;
|
||||
static const bool hideAllStateEvents = false;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import 'package:fluffychat/pangea/activity_generator/activity_generator.dart';
|
|||
import 'package:fluffychat/pangea/activity_planner/activity_planner_page.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/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/login_or_signup_view.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/signup.dart';
|
||||
|
|
@ -210,15 +209,7 @@ abstract class AppRoutes {
|
|||
),
|
||||
sideView: child,
|
||||
)
|
||||
// #Pangea
|
||||
// : child,
|
||||
: FluffyThemes.isColumnMode(context) ||
|
||||
(state.fullPath?.split("/").reversed.elementAt(1) ==
|
||||
'rooms' &&
|
||||
state.pathParameters['roomid'] != null)
|
||||
? child
|
||||
: BottomNavLayout(mainView: child),
|
||||
// Pangea#
|
||||
: child,
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
|
|
@ -352,6 +343,39 @@ abstract class AppRoutes {
|
|||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
// #Pangea
|
||||
GoRoute(
|
||||
path: 'homepage',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SuggestionsPage(),
|
||||
),
|
||||
routes: [
|
||||
...newRoomRoutes,
|
||||
GoRoute(
|
||||
path: '/planner',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const ActivityPlannerPage(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/generator',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const ActivityGenerator(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// Pangea#
|
||||
ShellRoute(
|
||||
pageBuilder: (context, state, child) => defaultPageBuilder(
|
||||
|
|
@ -365,40 +389,6 @@ abstract class AppRoutes {
|
|||
: child,
|
||||
),
|
||||
routes: [
|
||||
// #Pangea
|
||||
GoRoute(
|
||||
path: '/homepage',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SuggestionsPage(),
|
||||
),
|
||||
routes: [
|
||||
...newRoomRoutes,
|
||||
GoRoute(
|
||||
path: '/planner',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const ActivityPlannerPage(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/generator',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const ActivityGenerator(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// Pangea#
|
||||
GoRoute(
|
||||
path: 'settings',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
|
|
@ -802,21 +792,14 @@ abstract class AppRoutes {
|
|||
? TwoColumnLayout(
|
||||
mainView: ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
// #Pangea
|
||||
activeSpaceId: state.uri.queryParameters['spaceId'],
|
||||
activeFilter: state.uri.queryParameters['filter'],
|
||||
// Pangea#
|
||||
displayNavigationRail:
|
||||
state.path?.startsWith('/rooms/settings') != true,
|
||||
),
|
||||
sideView: child,
|
||||
)
|
||||
: FluffyThemes.isColumnMode(context) ||
|
||||
(state.fullPath?.split("/").reversed.elementAt(1) ==
|
||||
'rooms' &&
|
||||
state.pathParameters['roomid'] != null)
|
||||
? child
|
||||
: BottomNavLayout(mainView: child),
|
||||
: child,
|
||||
);
|
||||
// Pangea#
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ abstract class SettingKeys {
|
|||
'chat.fluffy.swipeRightToLeftToReply';
|
||||
static const String experimentalVoip = 'chat.fluffy.experimental_voip';
|
||||
static const String showPresences = 'chat.fluffy.show_presences';
|
||||
// #Pangea
|
||||
static const String displayNavigationRail =
|
||||
'chat.fluffy.display_navigation_rail';
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
enum AppSettings<T> {
|
||||
|
|
|
|||
|
|
@ -201,7 +201,8 @@ class ChatListViewBody extends StatelessWidget {
|
|||
// #Pangea
|
||||
// if (spaceDelegateCandidates.isNotEmpty &&
|
||||
// !controller.widget.displayNavigationRail)
|
||||
if (!controller.widget.displayNavigationRail)
|
||||
if (!AppConfig.displayNavigationRail &&
|
||||
!FluffyThemes.isColumnMode(context))
|
||||
// Pangea#
|
||||
ActiveFilter.spaces,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pangea/chat_list/widgets/chat_list_view_body_wrapper.dart';
|
||||
|
|
@ -31,8 +32,12 @@ class ChatListView extends StatelessWidget {
|
|||
},
|
||||
child: Row(
|
||||
children: [
|
||||
if (FluffyThemes.isColumnMode(context) &&
|
||||
controller.widget.displayNavigationRail) ...[
|
||||
// #Pangea
|
||||
// if (FluffyThemes.isColumnMode(context) &&
|
||||
// controller.widget.displayNavigationRail) ...[
|
||||
if (FluffyThemes.isColumnMode(context) ||
|
||||
AppConfig.displayNavigationRail) ...[
|
||||
// Pangea#
|
||||
SpacesNavigationRail(
|
||||
activeSpaceId: controller.activeSpaceId,
|
||||
onGoToChats: controller.clearActiveSpace,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,14 @@ class NaviRailItem extends StatelessWidget {
|
|||
bottom: 8,
|
||||
left: 0,
|
||||
child: AnimatedContainer(
|
||||
width: isSelected ? 8 : 0,
|
||||
// #Pangea
|
||||
// width: isSelected ? 8 : 0,
|
||||
width: isSelected
|
||||
? FluffyThemes.isColumnMode(context)
|
||||
? 8
|
||||
: 4
|
||||
: 0,
|
||||
// Pangea#
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
decoration: BoxDecoration(
|
||||
|
|
|
|||
|
|
@ -527,6 +527,18 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
final room = Matrix.of(context).client.getRoomById(widget.spaceId);
|
||||
final displayname =
|
||||
room?.getLocalizedDisplayname() ?? L10n.of(context).nothingFound;
|
||||
|
||||
// #Pangea
|
||||
final joinedParents = room?.spaceParents
|
||||
.map((parent) {
|
||||
final roomId = parent.roomId;
|
||||
if (roomId == null) return null;
|
||||
return room.client.getRoomById(roomId);
|
||||
})
|
||||
.whereType<Room>()
|
||||
.toList();
|
||||
// Pangea#
|
||||
|
||||
return Scaffold(
|
||||
// #Pangea
|
||||
// appBar: AppBar(
|
||||
|
|
@ -539,14 +551,51 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
_onSpaceAction(SpaceActions.settings);
|
||||
},
|
||||
child: AppBar(
|
||||
// Pangea#
|
||||
leading: FluffyThemes.isColumnMode(context)
|
||||
? null
|
||||
// leading: FluffyThemes.isColumnMode(context)
|
||||
// ? null
|
||||
// : Center(
|
||||
// child: CloseButton(
|
||||
// onPressed: widget.onBack,
|
||||
// ),
|
||||
// ),
|
||||
leading: joinedParents?.isEmpty ?? true
|
||||
? FluffyThemes.isColumnMode(context)
|
||||
? null
|
||||
: Center(
|
||||
child: CloseButton(
|
||||
onPressed: widget.onBack,
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: CloseButton(
|
||||
onPressed: widget.onBack,
|
||||
),
|
||||
child: joinedParents!.length == 1
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.arrow_back_outlined),
|
||||
onPressed: () =>
|
||||
widget.toParentSpace(joinedParents.first.id),
|
||||
)
|
||||
: PopupMenuButton(
|
||||
popUpAnimationStyle: AnimationStyle(
|
||||
duration: const Duration(milliseconds: 0),
|
||||
),
|
||||
tooltip: null,
|
||||
useRootNavigator: true,
|
||||
icon: const Icon(Icons.arrow_back_outlined),
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
...joinedParents.mapIndexed((i, room) {
|
||||
return PopupMenuItem(
|
||||
value: i,
|
||||
child: Text(room.getLocalizedDisplayname()),
|
||||
);
|
||||
}),
|
||||
];
|
||||
},
|
||||
onSelected: (i) {
|
||||
widget.toParentSpace(joinedParents[i].id);
|
||||
},
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: FluffyThemes.isColumnMode(context) ? null : 0,
|
||||
title: ListTile(
|
||||
|
|
@ -660,14 +709,16 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// Pangea#
|
||||
.toList();
|
||||
|
||||
final joinedParents = room.spaceParents
|
||||
.map((parent) {
|
||||
final roomId = parent.roomId;
|
||||
if (roomId == null) return null;
|
||||
return room.client.getRoomById(roomId);
|
||||
})
|
||||
.whereType<Room>()
|
||||
.toList();
|
||||
// #Pangea
|
||||
// final joinedParents = room.spaceParents
|
||||
// .map((parent) {
|
||||
// final roomId = parent.roomId;
|
||||
// if (roomId == null) return null;
|
||||
// return room.client.getRoomById(roomId);
|
||||
// })
|
||||
// .whereType<Room>()
|
||||
// .toList();
|
||||
// Pangea#
|
||||
final filter = _filterController.text.trim().toLowerCase();
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
|
|
@ -715,51 +766,51 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SliverList.builder(
|
||||
itemCount: joinedParents.length,
|
||||
itemBuilder: (context, i) {
|
||||
final displayname =
|
||||
joinedParents[i].getLocalizedDisplayname();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 1,
|
||||
),
|
||||
child: Material(
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: ListTile(
|
||||
minVerticalPadding: 0,
|
||||
leading: Icon(
|
||||
Icons.adaptive.arrow_back_outlined,
|
||||
size: 16,
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Avatar(
|
||||
mxContent: joinedParents[i].avatar,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
userId: joinedParents[i].directChatMatrixID,
|
||||
// Pangea#
|
||||
size: Avatar.defaultSize / 2,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius / 4,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(displayname)),
|
||||
],
|
||||
),
|
||||
onTap: () =>
|
||||
widget.toParentSpace(joinedParents[i].id),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// #Pangea
|
||||
// SliverList.builder(
|
||||
// itemCount: joinedParents.length,
|
||||
// itemBuilder: (context, i) {
|
||||
// final displayname =
|
||||
// joinedParents[i].getLocalizedDisplayname();
|
||||
// return Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 8,
|
||||
// vertical: 1,
|
||||
// ),
|
||||
// child: Material(
|
||||
// borderRadius:
|
||||
// BorderRadius.circular(AppConfig.borderRadius),
|
||||
// clipBehavior: Clip.hardEdge,
|
||||
// child: ListTile(
|
||||
// minVerticalPadding: 0,
|
||||
// leading: Icon(
|
||||
// Icons.adaptive.arrow_back_outlined,
|
||||
// size: 16,
|
||||
// ),
|
||||
// title: Row(
|
||||
// children: [
|
||||
// Avatar(
|
||||
// mxContent: joinedParents[i].avatar,
|
||||
// name: displayname,
|
||||
// // #Pangea
|
||||
// userId: joinedParents[i].directChatMatrixID,
|
||||
// // Pangea#
|
||||
// size: Avatar.defaultSize / 2,
|
||||
// borderRadius: BorderRadius.circular(
|
||||
// AppConfig.borderRadius / 4,
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 8),
|
||||
// Expanded(child: Text(displayname)),
|
||||
// ],
|
||||
// ),
|
||||
// onTap: () =>
|
||||
// widget.toParentSpace(joinedParents[i].id),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
KnockingUsersIndicator(room: room),
|
||||
// Pangea#
|
||||
SliverList.builder(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: const Center(child: BackButton()),
|
||||
title: Text(L10n.of(context).chatPermissions),
|
||||
// #Pangea
|
||||
// title: Text(L10n.of(context).chatPermissions),
|
||||
title: Text(L10n.of(context).permissions),
|
||||
// Pangea#
|
||||
),
|
||||
body: MaxWidthBody(
|
||||
child: StreamBuilder(
|
||||
|
|
@ -36,28 +39,11 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||
final powerLevelsContent = Map<String, Object?>.from(
|
||||
room.getState(EventTypes.RoomPowerLevels)?.content ?? {},
|
||||
);
|
||||
final powerLevels =
|
||||
Map<String, dynamic>.from(powerLevelsContent) // #Pangea
|
||||
// ..removeWhere((k, v) => v is! int);
|
||||
..removeWhere(
|
||||
(k, v) =>
|
||||
v is! int ||
|
||||
k.equals("m.call.invite") ||
|
||||
k.equals("historical") ||
|
||||
k.equals("state_default"),
|
||||
);
|
||||
// Pangea#
|
||||
final powerLevels = Map<String, dynamic>.from(powerLevelsContent)
|
||||
..removeWhere((k, v) => v is! int);
|
||||
final eventsPowerLevels = Map<String, int?>.from(
|
||||
powerLevelsContent.tryGetMap<String, int?>('events') ?? {},
|
||||
// #Pangea
|
||||
)..removeWhere(
|
||||
(k, v) =>
|
||||
v is! int ||
|
||||
k.equals("pangea.usranalytics") ||
|
||||
k.equals(EventTypes.RoomPowerLevels),
|
||||
);
|
||||
// )..removeWhere((k, v) => v is! int);
|
||||
// Pangea#
|
||||
)..removeWhere((k, v) => v is! int);
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
|
|
@ -69,7 +55,10 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||
Divider(color: theme.dividerColor),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context).chatPermissions,
|
||||
// #Pangea
|
||||
// L10n.of(context).chatPermissions,
|
||||
L10n.of(context).permissions,
|
||||
// Pangea#
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -90,48 +79,57 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||
newLevel: level,
|
||||
),
|
||||
canEdit: room.canChangePowerLevel,
|
||||
// #Pangea
|
||||
room: room,
|
||||
// Pangea#
|
||||
),
|
||||
// #Pangea
|
||||
// Divider(color: theme.dividerColor),
|
||||
// ListTile(
|
||||
// title: Text(
|
||||
// L10n.of(context).notifications,
|
||||
// style: TextStyle(
|
||||
// color: theme.colorScheme.primary,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Builder(
|
||||
// builder: (context) {
|
||||
// const key = 'rooms';
|
||||
// final value = powerLevelsContent
|
||||
// .containsKey('notifications')
|
||||
// ? powerLevelsContent
|
||||
// .tryGetMap<String, Object?>('notifications')
|
||||
// ?.tryGet<int>('rooms') ??
|
||||
// 0
|
||||
// : 0;
|
||||
// return PermissionsListTile(
|
||||
// permissionKey: key,
|
||||
// permission: value,
|
||||
// category: 'notifications',
|
||||
// canEdit: room.canChangePowerLevel,
|
||||
// onChanged: (level) => controller.editPowerLevel(
|
||||
// context,
|
||||
// key,
|
||||
// value,
|
||||
// newLevel: level,
|
||||
// category: 'notifications',
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// Pangea#
|
||||
Divider(color: theme.dividerColor),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context).configureChat,
|
||||
L10n.of(context).notifications,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
const key = 'rooms';
|
||||
final value = powerLevelsContent
|
||||
.containsKey('notifications')
|
||||
? powerLevelsContent
|
||||
.tryGetMap<String, Object?>('notifications')
|
||||
?.tryGet<int>('rooms') ??
|
||||
0
|
||||
: 0;
|
||||
return PermissionsListTile(
|
||||
permissionKey: key,
|
||||
permission: value,
|
||||
category: 'notifications',
|
||||
canEdit: room.canChangePowerLevel,
|
||||
onChanged: (level) => controller.editPowerLevel(
|
||||
context,
|
||||
key,
|
||||
value,
|
||||
newLevel: level,
|
||||
category: 'notifications',
|
||||
),
|
||||
// #Pangea
|
||||
room: room,
|
||||
// Pangea#
|
||||
);
|
||||
},
|
||||
),
|
||||
Divider(color: theme.dividerColor),
|
||||
ListTile(
|
||||
title: Text(
|
||||
// #Pangea
|
||||
// L10n.of(context).configureChat,
|
||||
room.isSpace
|
||||
? L10n.of(context).configureSpace
|
||||
: L10n.of(context).configureChat,
|
||||
// Pangea#
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -151,6 +149,9 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||
newLevel: level,
|
||||
category: 'events',
|
||||
),
|
||||
// #Pangea
|
||||
room: room,
|
||||
// Pangea#
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ class PermissionsListTile extends StatelessWidget {
|
|||
final String? category;
|
||||
final void Function(int? level)? onChanged;
|
||||
final bool canEdit;
|
||||
// #Pangea
|
||||
final Room room;
|
||||
// Pangea#
|
||||
|
||||
const PermissionsListTile({
|
||||
super.key,
|
||||
|
|
@ -19,6 +22,9 @@ class PermissionsListTile extends StatelessWidget {
|
|||
this.category,
|
||||
required this.onChanged,
|
||||
required this.canEdit,
|
||||
// #Pangea
|
||||
required this.room,
|
||||
// Pangea#
|
||||
});
|
||||
|
||||
String getLocalizedPowerLevelString(BuildContext context) {
|
||||
|
|
@ -29,15 +35,27 @@ class PermissionsListTile extends StatelessWidget {
|
|||
case 'events_default':
|
||||
return L10n.of(context).sendMessages;
|
||||
case 'state_default':
|
||||
return L10n.of(context).changeGeneralChatSettings;
|
||||
// #Pangea
|
||||
// return L10n.of(context).changeGeneralChatSettings;
|
||||
return L10n.of(context).changeGeneralSettings;
|
||||
// Pangea#
|
||||
case 'ban':
|
||||
return L10n.of(context).banFromChat;
|
||||
// #Pangea
|
||||
// return L10n.of(context).banFromChat;
|
||||
return L10n.of(context).ban;
|
||||
// Pangea#
|
||||
case 'kick':
|
||||
return L10n.of(context).kickFromChat;
|
||||
// #Pangea
|
||||
// return L10n.of(context).kickFromChat;
|
||||
return L10n.of(context).kick;
|
||||
// Pangea#
|
||||
case 'redact':
|
||||
return L10n.of(context).deleteMessage;
|
||||
case 'invite':
|
||||
return L10n.of(context).inviteOtherUsers;
|
||||
// #Pangea
|
||||
// return L10n.of(context).inviteOtherUsers;
|
||||
return L10n.of(context).inviteOtherUsersToRoom;
|
||||
// Pangea#
|
||||
}
|
||||
} else if (category == 'notifications') {
|
||||
switch (permissionKey) {
|
||||
|
|
@ -49,12 +67,20 @@ class PermissionsListTile extends StatelessWidget {
|
|||
case EventTypes.RoomName:
|
||||
// #Pangea
|
||||
// return L10n.of(context).changeTheNameOfTheGroup;
|
||||
return L10n.of(context).changeTheNameOfTheChat;
|
||||
return room.isSpace
|
||||
? L10n.of(context).changeTheNameOfTheSpace
|
||||
: L10n.of(context).changeTheNameOfTheChat;
|
||||
// Pangea#
|
||||
case EventTypes.RoomTopic:
|
||||
return L10n.of(context).changeTheDescriptionOfTheGroup;
|
||||
// #Pangea
|
||||
// return L10n.of(context).changeTheDescriptionOfTheGroup;
|
||||
return L10n.of(context).changeTheDescription;
|
||||
// Pangea#
|
||||
case EventTypes.RoomPowerLevels:
|
||||
return L10n.of(context).changeTheChatPermissions;
|
||||
// #Pangea
|
||||
// return L10n.of(context).changeTheChatPermissions;
|
||||
return L10n.of(context).changeThePermissions;
|
||||
// Pangea#
|
||||
case EventTypes.HistoryVisibility:
|
||||
return L10n.of(context).changeTheVisibilityOfChatHistory;
|
||||
case EventTypes.RoomCanonicalAlias:
|
||||
|
|
@ -70,6 +96,10 @@ class PermissionsListTile extends StatelessWidget {
|
|||
// #Pangea
|
||||
case EventTypes.SpaceChild:
|
||||
return L10n.of(context).spaceChildPermission;
|
||||
case EventTypes.RoomPinnedEvents:
|
||||
return L10n.of(context).pinMessages;
|
||||
case EventTypes.RoomJoinRules:
|
||||
return L10n.of(context).setJoinRules;
|
||||
// Pangea#
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:matrix/matrix.dart' as sdk;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.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/chat/constants/default_power_level.dart';
|
||||
|
|
@ -188,10 +187,8 @@ class NewGroupController extends State<NewGroup> {
|
|||
);
|
||||
}
|
||||
}
|
||||
// if a timeout happened, don't redirect to the chat
|
||||
if (error != null) return;
|
||||
// Pangea#
|
||||
context.go('/rooms/$roomId/invite?filter=groups');
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
Future<void> _createSpace() async {
|
||||
|
|
@ -220,6 +217,8 @@ class NewGroupController extends State<NewGroup> {
|
|||
// context.pop<String>(spaceId);
|
||||
final spaceId = await Matrix.of(context).client.createPangeaSpace(
|
||||
name: nameController.text,
|
||||
introChatName: L10n.of(context).introductions,
|
||||
announcementsChatName: L10n.of(context).announcements,
|
||||
visibility:
|
||||
groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private,
|
||||
joinRules:
|
||||
|
|
@ -237,8 +236,6 @@ class NewGroupController extends State<NewGroup> {
|
|||
GoogleAnalytics.createClass(room.name, spaceCode);
|
||||
}
|
||||
|
||||
// if a timeout happened, don't redirect to the space
|
||||
if (error != null) return;
|
||||
context.go("/rooms?spaceId=$spaceId");
|
||||
// Pangea#
|
||||
}
|
||||
|
|
@ -273,23 +270,9 @@ class NewGroupController extends State<NewGroup> {
|
|||
|
||||
switch (createGroupType) {
|
||||
case CreateGroupType.group:
|
||||
// #Pangea
|
||||
// await _createGroup();
|
||||
await _createGroup().timeout(
|
||||
const Duration(
|
||||
seconds: AppConfig.roomCreationTimeoutSeconds,
|
||||
),
|
||||
);
|
||||
// Pangea#
|
||||
await _createGroup();
|
||||
case CreateGroupType.space:
|
||||
// #Pangea
|
||||
// await _createSpace();
|
||||
await _createSpace().timeout(
|
||||
const Duration(
|
||||
seconds: AppConfig.roomCreationTimeoutSeconds,
|
||||
),
|
||||
);
|
||||
// Pangea#
|
||||
await _createSpace();
|
||||
}
|
||||
} catch (e, s) {
|
||||
sdk.Logs().d('Unable to create group', e, s);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ class SettingsView extends StatelessWidget {
|
|||
// Pangea#
|
||||
return Row(
|
||||
children: [
|
||||
if (FluffyThemes.isColumnMode(context)) ...[
|
||||
// #Pangea
|
||||
// if (FluffyThemes.isColumnMode(context)) ...[
|
||||
if (FluffyThemes.isColumnMode(context) ||
|
||||
AppConfig.displayNavigationRail) ...[
|
||||
// Pangea#
|
||||
SpacesNavigationRail(
|
||||
activeSpaceId: null,
|
||||
onGoToChats: () => context.go('/rooms'),
|
||||
|
|
|
|||
|
|
@ -359,6 +359,14 @@ class SettingsStyleView extends StatelessWidget {
|
|||
storeKey: SettingKeys.separateChatTypes,
|
||||
defaultValue: AppConfig.separateChatTypes,
|
||||
),
|
||||
// #Pangea
|
||||
// SettingsSwitchListTile.adaptive(
|
||||
// title: L10n.of(context).displayNavigationRail,
|
||||
// onChanged: (b) => AppConfig.displayNavigationRail = b,
|
||||
// storeKey: SettingKeys.displayNavigationRail,
|
||||
// defaultValue: AppConfig.displayNavigationRail,
|
||||
// ),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ class ActivityPlannerBuilderState extends State<ActivityPlannerBuilder> {
|
|||
}
|
||||
|
||||
Future<void> clearEdits() async {
|
||||
_resetActivity();
|
||||
await _resetActivity();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isEditing = false;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_planner_builder.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
|
|
@ -178,6 +179,13 @@ class ActivityRoomSelectionState extends State<ActivityRoomSelection> {
|
|||
preset: CreateRoomPreset.trustedPrivateChat,
|
||||
initialState: [
|
||||
BotOptionsModel(mode: BotMode.directChat).toStateEvent,
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(
|
||||
Matrix.of(context).client.userID!,
|
||||
),
|
||||
),
|
||||
if (avatar != null && avatarUrl != null)
|
||||
StateEvent(
|
||||
type: EventTypes.RoomAvatar,
|
||||
|
|
|
|||
|
|
@ -86,320 +86,350 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: _width,
|
||||
child: widget.controller.avatar != null
|
||||
? Image.memory(
|
||||
widget.controller.avatar!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: widget.controller.updatedActivity.imageURL !=
|
||||
null
|
||||
? widget.controller.updatedActivity
|
||||
.imageURL!
|
||||
.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(
|
||||
widget.controller.updatedActivity
|
||||
.imageURL!,
|
||||
),
|
||||
width: _width,
|
||||
height: 200,
|
||||
cacheKey: widget.controller
|
||||
.updatedActivity.bookmarkId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: widget.controller
|
||||
.updatedActivity.imageURL!,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (context, url) =>
|
||||
const Center(
|
||||
child:
|
||||
CircularProgressIndicator(),
|
||||
),
|
||||
errorWidget:
|
||||
(context, url, error) =>
|
||||
const SizedBox(),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
Positioned(
|
||||
bottom: 8.0,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(90),
|
||||
onTap: widget.controller.selectAvatar,
|
||||
child: const CircleAvatar(
|
||||
radius: 24.0,
|
||||
child: Icon(
|
||||
Icons.add_a_photo_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: TextFormField(
|
||||
controller:
|
||||
widget.controller.titleController,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
L10n.of(context).activityTitle,
|
||||
),
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: Text(
|
||||
widget.controller.updatedActivity.title,
|
||||
style:
|
||||
theme.textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 6,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: TextFormField(
|
||||
controller: widget.controller
|
||||
.learningObjectivesController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context)
|
||||
.learningObjectiveLabel,
|
||||
),
|
||||
maxLines: 4,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: Text(
|
||||
widget.controller.updatedActivity
|
||||
.learningObjective,
|
||||
maxLines: 6,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.steps,
|
||||
child: TextFormField(
|
||||
controller: widget
|
||||
.controller.instructionsController,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
L10n.of(context).instructions,
|
||||
),
|
||||
maxLines: 8,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.steps,
|
||||
child: Text(
|
||||
widget.controller.updatedActivity
|
||||
.instructions,
|
||||
maxLines: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.group_outlined,
|
||||
child: TextFormField(
|
||||
controller: widget
|
||||
.controller.participantsController,
|
||||
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.controller.updatedActivity.req
|
||||
.numberOfParticipants,
|
||||
),
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 60.0,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: widget.controller.vocab
|
||||
.mapIndexed(
|
||||
(i, vocab) => Container(
|
||||
padding: const EdgeInsets
|
||||
.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme
|
||||
.colorScheme.primary
|
||||
.withAlpha(20),
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
24.0,
|
||||
),
|
||||
),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors
|
||||
.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => widget
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: _width,
|
||||
child: widget.controller.avatar != null
|
||||
? Image.memory(
|
||||
widget.controller.avatar!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: widget.controller.updatedActivity
|
||||
.imageURL !=
|
||||
null
|
||||
? widget.controller.updatedActivity
|
||||
.imageURL!
|
||||
.startsWith("mxc")
|
||||
? MxcImage(
|
||||
uri: Uri.parse(
|
||||
widget
|
||||
.controller
|
||||
.removeVocab(i),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
Text(vocab.lemma),
|
||||
const Icon(
|
||||
Icons.close,
|
||||
size: 12.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
.updatedActivity
|
||||
.imageURL!,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 60.0,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: widget.controller.vocab
|
||||
.map(
|
||||
(vocab) => Container(
|
||||
padding: const EdgeInsets
|
||||
.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme
|
||||
.colorScheme.primary
|
||||
.withAlpha(20),
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
24.0,
|
||||
width: _width,
|
||||
height: 200,
|
||||
cacheKey: widget
|
||||
.controller
|
||||
.updatedActivity
|
||||
.bookmarkId,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: CachedNetworkImage(
|
||||
imageUrl: widget
|
||||
.controller
|
||||
.updatedActivity
|
||||
.imageURL!,
|
||||
fit: BoxFit.cover,
|
||||
placeholder:
|
||||
(context, url) =>
|
||||
const Center(
|
||||
child:
|
||||
CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
vocab.lemma,
|
||||
style: theme
|
||||
.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
errorWidget: (
|
||||
context,
|
||||
url,
|
||||
error,
|
||||
) =>
|
||||
const SizedBox(),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: widget
|
||||
.controller.vocabController,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context)
|
||||
.addVocabulary,
|
||||
),
|
||||
maxLines: 1,
|
||||
onFieldSubmitted: (_) =>
|
||||
widget.controller.addVocab(),
|
||||
if (widget.controller.isEditing)
|
||||
Positioned(
|
||||
bottom: 8.0,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(90),
|
||||
onTap: widget.controller.selectAvatar,
|
||||
child: const CircleAvatar(
|
||||
radius: 24.0,
|
||||
child: Icon(
|
||||
Icons.add_a_photo_outlined,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
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: widget.controller.addVocab,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: TextFormField(
|
||||
controller:
|
||||
widget.controller.titleController,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
L10n.of(context).activityTitle,
|
||||
),
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.event_note_outlined,
|
||||
child: Text(
|
||||
widget
|
||||
.controller.updatedActivity.title,
|
||||
style: theme.textTheme.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 6,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: TextFormField(
|
||||
controller: widget.controller
|
||||
.learningObjectivesController,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context)
|
||||
.learningObjectiveLabel,
|
||||
),
|
||||
maxLines: 4,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.target,
|
||||
child: Text(
|
||||
widget.controller.updatedActivity
|
||||
.learningObjective,
|
||||
maxLines: 6,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.steps,
|
||||
child: TextFormField(
|
||||
controller: widget.controller
|
||||
.instructionsController,
|
||||
decoration: InputDecoration(
|
||||
labelText:
|
||||
L10n.of(context).instructions,
|
||||
),
|
||||
maxLines: 8,
|
||||
minLines: 1,
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.steps,
|
||||
child: Text(
|
||||
widget.controller.updatedActivity
|
||||
.instructions,
|
||||
maxLines: 8,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Icons.group_outlined,
|
||||
child: TextFormField(
|
||||
controller: widget.controller
|
||||
.participantsController,
|
||||
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.controller.updatedActivity
|
||||
.req.numberOfParticipants,
|
||||
),
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 60.0,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: widget.controller.vocab
|
||||
.mapIndexed(
|
||||
(i, vocab) => Container(
|
||||
padding: const EdgeInsets
|
||||
.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme
|
||||
.colorScheme.primary
|
||||
.withAlpha(20),
|
||||
borderRadius:
|
||||
BorderRadius
|
||||
.circular(
|
||||
24.0,
|
||||
),
|
||||
),
|
||||
child: MouseRegion(
|
||||
cursor:
|
||||
SystemMouseCursors
|
||||
.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => widget
|
||||
.controller
|
||||
.removeVocab(i),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
mainAxisSize:
|
||||
MainAxisSize
|
||||
.min,
|
||||
children: [
|
||||
Text(vocab.lemma),
|
||||
const Icon(
|
||||
Icons.close,
|
||||
size: 12.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
ActivitySuggestionCardRow(
|
||||
icon: Symbols.dictionary,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 60.0,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: widget.controller.vocab
|
||||
.map(
|
||||
(vocab) => Container(
|
||||
padding: const EdgeInsets
|
||||
.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme
|
||||
.colorScheme.primary
|
||||
.withAlpha(20),
|
||||
borderRadius:
|
||||
BorderRadius
|
||||
.circular(
|
||||
24.0,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
vocab.lemma,
|
||||
style: theme.textTheme
|
||||
.bodyMedium,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
),
|
||||
child: Row(
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: widget
|
||||
.controller.vocabController,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context)
|
||||
.addVocabulary,
|
||||
),
|
||||
maxLines: 1,
|
||||
onFieldSubmitted: (_) => widget
|
||||
.controller
|
||||
.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:
|
||||
widget.controller.addVocab,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -459,13 +489,17 @@ class ActivitySuggestionDialogState extends State<ActivitySuggestionDialog> {
|
|||
),
|
||||
),
|
||||
if (widget.controller.isEditing)
|
||||
GestureDetector(
|
||||
child: const Icon(
|
||||
Icons.close_outlined,
|
||||
size: 16.0,
|
||||
IconButton.filled(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
),
|
||||
onTap: () {
|
||||
widget.controller.clearEdits();
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
constraints:
|
||||
const BoxConstraints(), // override default min size of 48px
|
||||
iconSize: 24.0,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: () async {
|
||||
await widget.controller.clearEdits();
|
||||
widget.controller.setEditing(false);
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_area.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart';
|
||||
import 'package:fluffychat/pangea/public_spaces/public_spaces_area.dart';
|
||||
import 'package:fluffychat/widgets/navigation_rail.dart';
|
||||
|
||||
class SuggestionsPage extends StatelessWidget {
|
||||
const SuggestionsPage({super.key});
|
||||
|
|
@ -11,24 +15,45 @@ class SuggestionsPage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 24.0,
|
||||
children: [
|
||||
if (!isColumnMode) const LearningProgressIndicators(),
|
||||
const ActivitySuggestionsArea(
|
||||
showTitle: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
return Material(
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isColumnMode && AppConfig.displayNavigationRail) ...[
|
||||
SpacesNavigationRail(
|
||||
activeSpaceId: null,
|
||||
onGoToChats: () => context.go('/rooms'),
|
||||
onGoToSpaceId: (spaceId) =>
|
||||
context.go('/rooms?spaceId=$spaceId'),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
const PublicSpacesArea(),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 24.0,
|
||||
children: [
|
||||
if (!isColumnMode) const LearningProgressIndicators(),
|
||||
const ActivitySuggestionsArea(
|
||||
showTitle: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
),
|
||||
const PublicSpacesArea(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -105,147 +105,156 @@ class LearningProgressIndicatorsState
|
|||
final mxid = client.userID ?? L10n.of(context).user;
|
||||
final displayname = _profile?.displayName ?? mxid.localpart ?? mxid;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Tooltip(
|
||||
message: L10n.of(context).settings,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => context.go("/rooms/settings"),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8.0,
|
||||
right: 8.0,
|
||||
),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none, // Allow overflow
|
||||
children: [
|
||||
FutureBuilder<Profile>(
|
||||
future: client.fetchOwnProfile(),
|
||||
builder: (context, snapshot) => Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
child: Avatar(
|
||||
mxContent: snapshot.data?.avatarUrl,
|
||||
name: snapshot.data?.displayName ??
|
||||
client.userID!.localpart,
|
||||
size: 60,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -3,
|
||||
right: -3,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Icon(
|
||||
size: 14,
|
||||
Icons.settings_outlined,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
weight: 1000,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 6.0,
|
||||
children: [
|
||||
Text(
|
||||
displayname,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
LearningSettingsButton(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (c) => const SettingsLearning(),
|
||||
barrierDismissible: false,
|
||||
),
|
||||
l2: userL2?.langCode.toUpperCase(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
spacing: 6.0,
|
||||
children: ConstructTypeEnum.values
|
||||
.map(
|
||||
(c) => ProgressIndicatorBadge(
|
||||
points: uniqueLemmas(c.indicator),
|
||||
loading: _loading,
|
||||
onTap: () {
|
||||
showDialog<AnalyticsPopupWrapper>(
|
||||
context: context,
|
||||
builder: (context) => AnalyticsPopupWrapper(
|
||||
view: c,
|
||||
),
|
||||
);
|
||||
},
|
||||
indicator: c.indicator,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
MouseRegion(
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Row(
|
||||
children: [
|
||||
Tooltip(
|
||||
message: L10n.of(context).settings,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
showDialog<LevelBarPopup>(
|
||||
context: context,
|
||||
builder: (c) => const LevelBarPopup(),
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 26,
|
||||
onTap: () => context.go("/rooms/settings"),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8.0,
|
||||
right: 8.0,
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
clipBehavior: Clip.none, // Allow overflow
|
||||
children: [
|
||||
Positioned(
|
||||
left: 16,
|
||||
right: 0,
|
||||
child: LearningProgressBar(
|
||||
level: _constructsModel.level,
|
||||
totalXP: _constructsModel.totalXP,
|
||||
FutureBuilder<Profile>(
|
||||
future: client.fetchOwnProfile(),
|
||||
builder: (context, snapshot) => Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
child: Avatar(
|
||||
mxContent: snapshot.data?.avatarUrl,
|
||||
name: snapshot.data?.displayName ??
|
||||
client.userID!.localpart,
|
||||
size: 60,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
child: LevelBadge(level: _constructsModel.level),
|
||||
bottom: -3,
|
||||
right: -3,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color:
|
||||
Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Icon(
|
||||
size: 14,
|
||||
Icons.settings_outlined,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
weight: 1000,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 6.0,
|
||||
children: [
|
||||
Text(
|
||||
displayname,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
LearningSettingsButton(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (c) => const SettingsLearning(),
|
||||
barrierDismissible: false,
|
||||
),
|
||||
l2: userL2?.langCode.toUpperCase(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
spacing: 6.0,
|
||||
children: ConstructTypeEnum.values
|
||||
.map(
|
||||
(c) => ProgressIndicatorBadge(
|
||||
points: uniqueLemmas(c.indicator),
|
||||
loading: _loading,
|
||||
onTap: () {
|
||||
showDialog<AnalyticsPopupWrapper>(
|
||||
context: context,
|
||||
builder: (context) => AnalyticsPopupWrapper(
|
||||
view: c,
|
||||
),
|
||||
);
|
||||
},
|
||||
indicator: c.indicator,
|
||||
mini: constraints.maxWidth < 300,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
showDialog<LevelBarPopup>(
|
||||
context: context,
|
||||
builder: (c) => const LevelBarPopup(),
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 26,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Positioned(
|
||||
left: 16,
|
||||
right: 0,
|
||||
child: LearningProgressBar(
|
||||
level: _constructsModel.level,
|
||||
totalXP: _constructsModel.totalXP,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
child: LevelBadge(
|
||||
level: _constructsModel.level,
|
||||
mini: constraints.maxWidth < 300,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/level_bar_popup.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
||||
|
||||
class LevelBadge extends StatelessWidget {
|
||||
final int level;
|
||||
final bool mini;
|
||||
|
||||
const LevelBadge({
|
||||
required this.level,
|
||||
this.mini = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -31,29 +33,13 @@ class LevelBadge extends StatelessWidget {
|
|||
color: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: AppConfig.gold,
|
||||
radius: 8,
|
||||
child: Icon(
|
||||
size: 12,
|
||||
Icons.star,
|
||||
color: Theme.of(context).colorScheme.surfaceBright,
|
||||
weight: 1000,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
L10n.of(context).levelShort(level),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Text(
|
||||
"⭐ ${mini ? "$level" : L10n.of(context).levelShort(level)}",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -43,27 +43,13 @@ class LevelBarPopup extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: AppConfig.gold,
|
||||
child: Icon(
|
||||
size: 30,
|
||||
Icons.star,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
L10n.of(context).levelShort(level),
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: AppConfig.gold,
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
"⭐ ${L10n.of(context).levelShort(level)}",
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: AppConfig.gold,
|
||||
),
|
||||
),
|
||||
Opacity(
|
||||
opacity: 0.25,
|
||||
|
|
|
|||
|
|
@ -95,14 +95,6 @@ class AnimatedLevelBarState extends State<AnimatedLevelBar>
|
|||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(50),
|
||||
spreadRadius: 0,
|
||||
blurRadius: 5,
|
||||
offset: const Offset(5, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class ProgressIndicatorBadge extends StatelessWidget {
|
|||
final int points;
|
||||
final VoidCallback onTap;
|
||||
final ProgressIndicatorEnum indicator;
|
||||
final bool mini;
|
||||
|
||||
const ProgressIndicatorBadge({
|
||||
super.key,
|
||||
|
|
@ -16,6 +17,7 @@ class ProgressIndicatorBadge extends StatelessWidget {
|
|||
required this.indicator,
|
||||
required this.loading,
|
||||
required this.points,
|
||||
this.mini = false,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -42,15 +44,17 @@ class ProgressIndicatorBadge extends StatelessWidget {
|
|||
color: indicator.color(context),
|
||||
weight: 1000,
|
||||
),
|
||||
const SizedBox(width: 4.0),
|
||||
Text(
|
||||
indicator.tooltip(context),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: indicator.color(context),
|
||||
if (!mini) ...[
|
||||
const SizedBox(width: 4.0),
|
||||
Text(
|
||||
indicator.tooltip(context),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: indicator.color(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 4.0),
|
||||
!loading
|
||||
? Text(
|
||||
|
|
|
|||
|
|
@ -1,32 +1,60 @@
|
|||
Map<String, dynamic> defaultPowerLevels(String userID) => {
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.avatar": 50,
|
||||
"m.room.canonical_alias": 50,
|
||||
"m.room.encryption": 100,
|
||||
"m.room.history_visibility": 100,
|
||||
"m.room.name": 50,
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.server_acl": 100,
|
||||
"m.room.tombstone": 100,
|
||||
"m.room.pinned_events": 50,
|
||||
},
|
||||
"events_default": 0,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
};
|
||||
|
||||
Map<String, dynamic> restrictedPowerLevels(String userID) => {
|
||||
"events_default": 50,
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.avatar": 50,
|
||||
"m.room.canonical_alias": 50,
|
||||
"m.room.encryption": 100,
|
||||
"m.room.history_visibility": 100,
|
||||
"m.room.name": 50,
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.server_acl": 100,
|
||||
"m.room.tombstone": 100,
|
||||
"m.room.pinned_events": 50,
|
||||
},
|
||||
"events_default": 50,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
};
|
||||
|
||||
Map<String, dynamic> defaultSpacePowerLevels(String userID) => {
|
||||
"ban": 50,
|
||||
"kick": 50,
|
||||
"invite": 50,
|
||||
"redact": 50,
|
||||
"events": {
|
||||
"m.room.power_levels": 100,
|
||||
"m.room.join_rules": 100,
|
||||
"m.space.child": 50,
|
||||
},
|
||||
"events_default": 0,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
userID: 100,
|
||||
},
|
||||
"users_default": 0,
|
||||
"notifications": {
|
||||
"room": 50,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -453,11 +453,6 @@ class Choreographer {
|
|||
if (!isNormalizationError) continue;
|
||||
final match = igc.igcTextData!.matches[i];
|
||||
|
||||
choreoRecord.addRecord(
|
||||
_textController.text,
|
||||
match: match.copyWith..status = PangeaMatchStatus.automatic,
|
||||
);
|
||||
|
||||
igc.igcTextData!.acceptReplacement(
|
||||
i,
|
||||
match.match.choices!.indexWhere(
|
||||
|
|
@ -465,6 +460,19 @@ class Choreographer {
|
|||
),
|
||||
);
|
||||
|
||||
final newMatch = match.copyWith;
|
||||
newMatch.status = PangeaMatchStatus.automatic;
|
||||
newMatch.match.length = match.match.choices!
|
||||
.firstWhere((c) => c.isBestCorrection)
|
||||
.value
|
||||
.characters
|
||||
.length;
|
||||
|
||||
choreoRecord.addRecord(
|
||||
_textController.text,
|
||||
match: newMatch,
|
||||
);
|
||||
|
||||
_textController.setSystemText(
|
||||
igc.igcTextData!.originalInput,
|
||||
EditType.igc,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,17 @@ class ErrorService {
|
|||
return Duration(seconds: coolDownSeconds);
|
||||
}
|
||||
|
||||
final List<String> _errorCache = [];
|
||||
|
||||
setError(ChoreoError? error, {Duration? duration}) {
|
||||
if (_errorCache.contains(error?.raw.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
_errorCache.add(error.raw.toString());
|
||||
}
|
||||
|
||||
_error = error;
|
||||
Future.delayed(duration ?? defaultCooldown, () {
|
||||
clear();
|
||||
|
|
|
|||
|
|
@ -295,6 +295,9 @@ class IgcController {
|
|||
igcTextData = null;
|
||||
spanDataController.clearCache();
|
||||
spanDataController.dispose();
|
||||
MatrixState.pAnyState.closeAllOverlays(
|
||||
filter: RegExp(r'span_card_overlay_\d+'),
|
||||
);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
|
|
|||
|
|
@ -292,12 +292,45 @@ class IGCTextData {
|
|||
// create a pointer to the current index in the original input
|
||||
// and iterate until the pointer has reached the end of the input
|
||||
int currentIndex = 0;
|
||||
int loops = 0;
|
||||
final List<PangeaMatch> addedMatches = [];
|
||||
while (currentIndex < originalInput.characters.length) {
|
||||
if (loops > 100) {
|
||||
ErrorHandler.logError(
|
||||
e: "In constructTokenSpan, infinite loop detected",
|
||||
data: {
|
||||
"currentIndex": currentIndex,
|
||||
"matches": textSpanMatches.map((m) => m.toJson()).toList(),
|
||||
},
|
||||
);
|
||||
throw "In constructTokenSpan, infinite loop detected";
|
||||
}
|
||||
|
||||
// check if the pointer is at a match, and if so, get the index of the match
|
||||
final int matchIndex = matchRanges.indexWhere(
|
||||
(range) => currentIndex >= range[0] && currentIndex < range[1],
|
||||
);
|
||||
final bool inMatch = matchIndex != -1;
|
||||
final bool inMatch = matchIndex != -1 &&
|
||||
!addedMatches.contains(
|
||||
textSpanMatches[matchIndex],
|
||||
);
|
||||
|
||||
if (matchIndex != -1 &&
|
||||
addedMatches.contains(
|
||||
textSpanMatches[matchIndex],
|
||||
)) {
|
||||
ErrorHandler.logError(
|
||||
e: "In constructTokenSpan, currentIndex is in match that has already been added",
|
||||
data: {
|
||||
"currentIndex": currentIndex,
|
||||
"matchIndex": matchIndex,
|
||||
"matches": textSpanMatches.map((m) => m.toJson()).toList(),
|
||||
},
|
||||
);
|
||||
throw "In constructTokenSpan, currentIndex is in match that has already been added";
|
||||
}
|
||||
|
||||
final prevIndex = currentIndex;
|
||||
|
||||
if (inMatch) {
|
||||
// if the pointer is in a match, then add that match to items
|
||||
|
|
@ -312,13 +345,7 @@ class IGCTextData {
|
|||
final span = originalInput.characters
|
||||
.getRange(
|
||||
match.match.offset,
|
||||
match.match.offset +
|
||||
(match.match.choices
|
||||
?.firstWhere((c) => c.isBestCorrection)
|
||||
.value
|
||||
.characters
|
||||
.length ??
|
||||
match.match.length),
|
||||
match.match.offset + match.match.length,
|
||||
)
|
||||
.toString();
|
||||
|
||||
|
|
@ -364,12 +391,8 @@ class IGCTextData {
|
|||
),
|
||||
);
|
||||
|
||||
currentIndex = match.match.offset +
|
||||
(match.match.choices
|
||||
?.firstWhere((c) => c.isBestCorrection)
|
||||
.value
|
||||
.length ??
|
||||
match.match.length);
|
||||
addedMatches.add(match);
|
||||
currentIndex = match.match.offset + match.match.length;
|
||||
} else {
|
||||
items.add(
|
||||
getSpanItem(
|
||||
|
|
@ -400,6 +423,20 @@ class IGCTextData {
|
|||
);
|
||||
currentIndex = nextIndex;
|
||||
}
|
||||
|
||||
if (prevIndex >= currentIndex) {
|
||||
ErrorHandler.logError(
|
||||
e: "In constructTokenSpan, currentIndex is less than prevIndex",
|
||||
data: {
|
||||
"currentIndex": currentIndex,
|
||||
"prevIndex": prevIndex,
|
||||
"matches": textSpanMatches.map((m) => m.toJson()).toList(),
|
||||
},
|
||||
);
|
||||
throw "In constructTokenSpan, currentIndex is less than prevIndex";
|
||||
}
|
||||
|
||||
loops++;
|
||||
}
|
||||
|
||||
return items;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:developer';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/igc_text_data_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/paywall_card.dart';
|
||||
|
|
@ -174,18 +175,29 @@ class PangeaTextController extends TextEditingController {
|
|||
|
||||
final choreoSteps = choreographer.choreoRecord.choreoSteps;
|
||||
|
||||
List<InlineSpan> inlineSpans = [];
|
||||
try {
|
||||
inlineSpans = choreographer.igc.igcTextData!.constructTokenSpan(
|
||||
choreoSteps: choreoSteps.isNotEmpty &&
|
||||
choreoSteps.last.acceptedOrIgnoredMatch?.status ==
|
||||
PangeaMatchStatus.automatic
|
||||
? choreoSteps
|
||||
: [],
|
||||
defaultStyle: style,
|
||||
onUndo: choreographer.onUndoReplacement,
|
||||
);
|
||||
} catch (e) {
|
||||
choreographer.errorService.setError(
|
||||
ChoreoError(type: ChoreoErrorType.unknown, raw: e),
|
||||
);
|
||||
inlineSpans = [TextSpan(text: text, style: style)];
|
||||
choreographer.igc.clear();
|
||||
}
|
||||
|
||||
return TextSpan(
|
||||
style: style,
|
||||
children: [
|
||||
...choreographer.igc.igcTextData!.constructTokenSpan(
|
||||
choreoSteps: choreoSteps.isNotEmpty &&
|
||||
choreoSteps.last.acceptedOrIgnoredMatch?.status ==
|
||||
PangeaMatchStatus.automatic
|
||||
? choreoSteps
|
||||
: [],
|
||||
defaultStyle: style,
|
||||
onUndo: choreographer.onUndoReplacement,
|
||||
),
|
||||
...inlineSpans,
|
||||
TextSpan(text: parts[1], style: style),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/contextual_definition_controller.dart';
|
||||
|
|
@ -268,6 +269,13 @@ class PangeaController {
|
|||
preset: CreateRoomPreset.trustedPrivateChat,
|
||||
initialState: [
|
||||
BotOptionsModel(mode: BotMode.directChat).toStateEvent,
|
||||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: defaultPowerLevels(
|
||||
matrixState.client.userID!,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class RepresentationEvent {
|
|||
if (tokenEvents.isEmpty) return null;
|
||||
|
||||
if (tokenEvents.length > 1) {
|
||||
debugger(when: kDebugMode);
|
||||
// debugger(when: kDebugMode);
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message:
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
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: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary.withAlpha(50),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: BottomNavigationBar(
|
||||
iconSize: 16.0,
|
||||
onTap: onItemTapped,
|
||||
selectedItemColor: Theme.of(context).colorScheme.primary,
|
||||
selectedFontSize: 14.0,
|
||||
unselectedFontSize: 14.0,
|
||||
currentIndex: selectedIndex,
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
activeIcon: const Icon(Icons.home),
|
||||
label: L10n.of(context).home,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.chat_bubble_outline),
|
||||
activeIcon: const Icon(Icons.chat_bubble),
|
||||
label: L10n.of(context).chats,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
activeIcon: const Icon(Icons.settings),
|
||||
label: L10n.of(context).settings,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ import 'package:fluffychat/pangea/spaces/utils/space_code.dart';
|
|||
extension SpacesClientExtension on Client {
|
||||
Future<String> createPangeaSpace({
|
||||
required String name,
|
||||
required String introChatName,
|
||||
required String announcementsChatName,
|
||||
Visibility visibility = Visibility.private,
|
||||
JoinRules joinRules = JoinRules.public,
|
||||
Uint8List? avatar,
|
||||
|
|
@ -24,6 +26,7 @@ extension SpacesClientExtension on Client {
|
|||
powerLevelContentOverride: {'events_default': 100},
|
||||
initialState: [
|
||||
..._spaceInitialState(
|
||||
userID!,
|
||||
joinCode,
|
||||
joinRules: joinRules,
|
||||
),
|
||||
|
|
@ -38,7 +41,11 @@ extension SpacesClientExtension on Client {
|
|||
final space = await _waitForRoom(roomId);
|
||||
if (space == null) return roomId;
|
||||
|
||||
await _addDefaultSpaceChats(space: space);
|
||||
await _addDefaultSpaceChats(
|
||||
space: space,
|
||||
introductionsName: introChatName,
|
||||
announcementsName: announcementsChatName,
|
||||
);
|
||||
return roomId;
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +115,13 @@ extension SpacesClientExtension on Client {
|
|||
throw Exception('Failed to create default space chats');
|
||||
}
|
||||
|
||||
for (final roomId in roomIds) {
|
||||
final room = getRoomById(roomId);
|
||||
if (room == null) {
|
||||
await waitForRoomInSync(roomId, join: true);
|
||||
}
|
||||
}
|
||||
|
||||
final addIntroChatFuture = space.pangeaSetSpaceChild(
|
||||
roomIds[0],
|
||||
);
|
||||
|
|
@ -123,6 +137,7 @@ extension SpacesClientExtension on Client {
|
|||
}
|
||||
|
||||
List<StateEvent> _spaceInitialState(
|
||||
String userID,
|
||||
String joinCode, {
|
||||
required JoinRules joinRules,
|
||||
}) {
|
||||
|
|
@ -130,15 +145,7 @@ extension SpacesClientExtension on Client {
|
|||
StateEvent(
|
||||
type: EventTypes.RoomPowerLevels,
|
||||
stateKey: '',
|
||||
content: {
|
||||
'events': {
|
||||
EventTypes.SpaceChild: 50,
|
||||
},
|
||||
'users_default': 0,
|
||||
'users': {
|
||||
userID: SpaceConstants.powerLevelOfAdmin,
|
||||
},
|
||||
},
|
||||
content: defaultSpacePowerLevels(userID),
|
||||
),
|
||||
StateEvent(
|
||||
type: EventTypes.RoomJoinRules,
|
||||
|
|
|
|||
|
|
@ -484,6 +484,12 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
|||
|
||||
AppConfig.showPresences =
|
||||
store.getBool(SettingKeys.showPresences) ?? AppConfig.showPresences;
|
||||
|
||||
// #Pangea
|
||||
AppConfig.displayNavigationRail =
|
||||
store.getBool(SettingKeys.displayNavigationRail) ??
|
||||
AppConfig.displayNavigationRail;
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -34,6 +34,15 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
.uri
|
||||
.path
|
||||
.startsWith('/rooms/settings');
|
||||
// #Pangea
|
||||
final isHomepage = GoRouter.of(context)
|
||||
.routeInformationProvider
|
||||
.value
|
||||
.uri
|
||||
.path
|
||||
.contains('homepage');
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
// Pangea#
|
||||
return StreamBuilder(
|
||||
key: ValueKey(
|
||||
client.userID.toString(),
|
||||
|
|
@ -53,7 +62,12 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
.toList();
|
||||
|
||||
return SizedBox(
|
||||
width: FluffyThemes.navRailWidth,
|
||||
// #Pangea
|
||||
// width: FluffyThemes.navRailWidth,
|
||||
width: isColumnMode
|
||||
? FluffyThemes.navRailWidth
|
||||
: FluffyThemes.navRailWidth * 0.75,
|
||||
// Pangea#
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
|
|
@ -61,35 +75,56 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
scrollDirection: Axis.vertical,
|
||||
// #Pangea
|
||||
// itemCount: rootSpaces.length + 2,
|
||||
itemCount: rootSpaces.length + 3,
|
||||
itemCount: rootSpaces.length + 4,
|
||||
// Pangea#
|
||||
itemBuilder: (context, i) {
|
||||
// #Pangea
|
||||
if (i == 0) {
|
||||
return NaviRailItem(
|
||||
isSelected: activeSpaceId == null && !isSettings,
|
||||
onTap: onGoToChats,
|
||||
isSelected: isColumnMode
|
||||
? activeSpaceId == null && !isSettings
|
||||
: isHomepage,
|
||||
onTap: () => isColumnMode
|
||||
? onGoToChats()
|
||||
: context.go("/rooms/homepage"),
|
||||
icon: const Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
// #Pangea
|
||||
// child: Icon(Icons.forum_outlined),
|
||||
child: Icon(Icons.home_outlined),
|
||||
// Pangea#
|
||||
),
|
||||
selectedIcon: const Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
// #Pangea
|
||||
// child: Icon(Icons.forum),
|
||||
child: Icon(Icons.home),
|
||||
// Pangea#
|
||||
),
|
||||
// #Pangea
|
||||
// toolTip: L10n.of(context).chats,
|
||||
toolTip: L10n.of(context).home,
|
||||
// Pangea#
|
||||
unreadBadgeFilter: (room) => true,
|
||||
);
|
||||
}
|
||||
i--;
|
||||
// Pangea#
|
||||
if (i == 0) {
|
||||
return isColumnMode
|
||||
? const SizedBox()
|
||||
: NaviRailItem(
|
||||
// #Pangea
|
||||
// isSelected: activeSpaceId == null && !isSettings,
|
||||
isSelected: activeSpaceId == null &&
|
||||
!isSettings &&
|
||||
!isHomepage,
|
||||
// Pangea#
|
||||
onTap: onGoToChats,
|
||||
icon: const Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(Icons.forum_outlined),
|
||||
),
|
||||
selectedIcon: const Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Icon(Icons.forum),
|
||||
),
|
||||
toolTip: L10n.of(context).chats,
|
||||
unreadBadgeFilter: (room) => true,
|
||||
);
|
||||
}
|
||||
i--;
|
||||
if (i == rootSpaces.length) {
|
||||
// #Pangea
|
||||
return NaviRailItem(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue