feat: New material 3 design

This commit is contained in:
Christian Pauly 2022-07-07 18:50:13 +02:00
parent 802ff0fa9d
commit 091958be0b
28 changed files with 821 additions and 1311 deletions

View File

@ -2826,5 +2826,7 @@
"user": {}
}
},
"noEmailWarning": "Please enter a valid email address. Otherwise you won't be able to reset your password. If you don't want to, tap again on the button to continue."
"noEmailWarning": "Please enter a valid email address. Otherwise you won't be able to reset your password. If you don't want to, tap again on the button to continue.",
"stories": "Stories",
"users": "Users"
}

View File

@ -17,7 +17,6 @@ import 'package:fluffychat/pages/login/login.dart';
import 'package:fluffychat/pages/new_group/new_group.dart';
import 'package:fluffychat/pages/new_private_chat/new_private_chat.dart';
import 'package:fluffychat/pages/new_space/new_space.dart';
import 'package:fluffychat/pages/search/search.dart';
import 'package:fluffychat/pages/settings/settings.dart';
import 'package:fluffychat/pages/settings_3pid/settings_3pid.dart';
import 'package:fluffychat/pages/settings_account/settings_account.dart';
@ -92,10 +91,6 @@ class AppRoutes {
widget: const Settings(),
stackedRoutes: _settingsRoutes,
),
VWidget(
path: '/search',
widget: const Search(),
),
VWidget(
path: '/archive',
widget: const Archive(),
@ -225,14 +220,6 @@ class AppRoutes {
),
],
),
VWidget(
path: '/search',
widget: const TwoColumnLayout(
mainView: Search(),
sideView: EmptyPage(),
),
buildTransition: _fadeTransition,
),
VWidget(
path: '/archive',
widget: const TwoColumnLayout(

View File

@ -9,56 +9,10 @@ abstract class FluffyThemes {
static bool isColumnMode(BuildContext context) =>
MediaQuery.of(context).size.width > columnWidth * 2;
static const fallbackTextStyle =
TextStyle(fontFamily: 'Roboto', fontFamilyFallback: ['NotoEmoji']);
static const TextStyle loginTextFieldStyle = TextStyle(color: Colors.black);
static InputDecoration loginTextFieldDecoration({
String? errorText,
String? labelText,
String? hintText,
Widget? suffixIcon,
Widget? prefixIcon,
Color? errorColor,
}) =>
InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
),
fillColor: Colors.white.withAlpha(200),
labelText: labelText,
hintText: hintText,
suffixIcon: suffixIcon,
prefixIcon: prefixIcon,
suffixIconColor: Colors.black,
prefixIconColor: Colors.black,
iconColor: Colors.black,
errorText: errorText,
errorMaxLines: 4,
errorStyle: TextStyle(
color: errorColor ?? Colors.redAccent.shade200,
shadows: const [
Shadow(
color: Colors.black,
offset: Offset(0, 0),
blurRadius: 10,
),
],
),
hintStyle: TextStyle(color: Colors.grey.shade700),
labelStyle: const TextStyle(
color: Colors.white,
shadows: [
Shadow(
color: Colors.black,
offset: Offset(0, 0),
blurRadius: 5,
),
],
),
contentPadding: const EdgeInsets.all(16),
);
static const fallbackTextStyle = TextStyle(
fontFamily: 'Roboto',
fontFamilyFallback: ['NotoEmoji'],
);
static var fallbackTextTheme = const TextTheme(
bodyText1: fallbackTextStyle,
@ -83,12 +37,12 @@ abstract class FluffyThemes {
colorSchemeSeed: AppConfig.colorSchemeSeed ??
colorScheme?.primary ??
AppConfig.chatColor,
scaffoldBackgroundColor: Colors.white,
textTheme: PlatformInfos.isDesktop
? Typography.material2018().black.merge(fallbackTextTheme)
: null,
snackBarTheme:
const SnackBarThemeData(behavior: SnackBarBehavior.floating),
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
@ -100,31 +54,12 @@ abstract class FluffyThemes {
},
),
dividerColor: Colors.blueGrey.shade50,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 16),
elevation: 6,
shadowColor: const Color(0x44000000),
minimumSize: const Size.fromHeight(48),
padding: const EdgeInsets.all(12),
),
),
cardTheme: const CardTheme(
elevation: 6,
// shadowColor: Color(0x44000000),
clipBehavior: Clip.hardEdge,
),
inputDecorationTheme: InputDecorationTheme(
border: const UnderlineInputBorder(borderSide: BorderSide(width: 1)),
inputDecorationTheme: const InputDecorationTheme(
border: UnderlineInputBorder(borderSide: BorderSide(width: 1)),
filled: true,
fillColor: Colors.blueGrey.shade50,
),
appBarTheme: const AppBarTheme(
elevation: 6,
shadowColor: Color(0x44000000),
systemOverlayStyle: SystemUiOverlayStyle.dark,
surfaceTintColor: Colors.white,
backgroundColor: Colors.white,
),
);
@ -135,7 +70,6 @@ abstract class FluffyThemes {
colorSchemeSeed: AppConfig.colorSchemeSeed ??
colorScheme?.primary ??
AppConfig.chatColor,
scaffoldBackgroundColor: Colors.black,
textTheme: PlatformInfos.isDesktop
? Typography.material2018().white.merge(fallbackTextTheme)
: null,
@ -151,20 +85,11 @@ abstract class FluffyThemes {
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
),
dividerColor: Colors.blueGrey.shade600,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
primary: AppConfig.chatColor,
onPrimary: Colors.white,
minimumSize: const Size.fromHeight(48),
textStyle: const TextStyle(fontSize: 16),
padding: const EdgeInsets.all(12),
),
),
appBarTheme: const AppBarTheme(
elevation: 6,
backgroundColor: Color(0xff1D1D1D),
inputDecorationTheme: const InputDecorationTheme(
border: UnderlineInputBorder(borderSide: BorderSide(width: 1)),
filled: true,
),
dividerColor: Colors.blueGrey.shade900,
);
static Color blackWhiteColor(BuildContext context) =>

View File

@ -255,12 +255,13 @@ class ChatView extends StatelessWidget {
),
elevation: 6,
shadowColor: Theme.of(context)
.secondaryHeaderColor
.dividerColor
.withAlpha(100),
clipBehavior: Clip.hardEdge,
color: Theme.of(context)
.appBarTheme
.backgroundColor,
color: Theme.of(context).brightness ==
Brightness.light
? Colors.white
: Colors.black,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [

View File

@ -68,7 +68,9 @@ class Message extends StatelessWidget {
final client = Matrix.of(context).client;
final ownMessage = event.senderId == client.userID;
final alignment = ownMessage ? Alignment.topRight : Alignment.topLeft;
var color = Theme.of(context).scaffoldBackgroundColor;
var color = Theme.of(context).brightness == Brightness.light
? Colors.white
: Colors.black;
final displayTime = event.type == EventTypes.RoomCreate ||
nextEvent == null ||
!event.originServerTs.sameEnvironment(nextEvent!.originServerTs);

View File

@ -14,9 +14,9 @@ import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart';
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/famedlysdk_store.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import '../../../utils/account_bundles.dart';
import '../../main.dart';
@ -52,6 +52,97 @@ class ChatListController extends State<ChatList> with TickerProviderStateMixin {
SpacesEntry? _activeSpacesEntry;
bool isSearchMode = false;
Future<QueryPublicRoomsResponse>? publicRoomsResponse;
String? searchServer;
Timer? _coolDown;
SearchUserDirectoryResponse? userSearchResult;
QueryPublicRoomsResponse? roomSearchResult;
bool isSearching = false;
static const String _serverStoreNamespace = 'im.fluffychat.search.server';
void setServer() async {
final newServer = await showTextInputDialog(
useRootNavigator: false,
title: L10n.of(context)!.changeTheHomeserver,
context: context,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [
DialogTextField(
prefixText: 'https://',
hintText: Matrix.of(context).client.homeserver?.host,
initialText: searchServer,
keyboardType: TextInputType.url,
autocorrect: false)
]);
if (newServer == null) return;
Store().setItem(_serverStoreNamespace, newServer.single);
setState(() {
searchServer = newServer.single;
});
onSearchEnter(searchController.text);
}
final TextEditingController searchController = TextEditingController();
void _search() async {
final client = Matrix.of(context).client;
if (!isSearching) {
setState(() {
isSearching = true;
});
}
SearchUserDirectoryResponse? userSearchResult;
QueryPublicRoomsResponse? roomSearchResult;
try {
roomSearchResult = await client.queryPublicRooms(
server: searchServer,
filter: PublicRoomQueryFilter(genericSearchTerm: searchController.text),
limit: 20,
);
userSearchResult = await client.searchUserDirectory(
searchController.text,
limit: 20,
);
} catch (e, s) {
Logs().w('Searching has crashed', e, s);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
e.toLocalizedString(context),
),
),
);
}
setState(() {
isSearching = false;
this.roomSearchResult = roomSearchResult;
this.userSearchResult = userSearchResult;
});
}
void onSearchEnter(String text) {
if (text.isEmpty) {
cancelSearch();
return;
}
setState(() {
isSearchMode = true;
});
_coolDown?.cancel();
_coolDown = Timer(const Duration(milliseconds: 500), _search);
}
void cancelSearch() => setState(() {
searchController.clear();
isSearchMode = false;
roomSearchResult = userSearchResult = null;
isSearching = false;
});
SpacesEntry get activeSpacesEntry {
final id = _activeSpacesEntry;
return (id == null || !id.stillValid(context)) ? defaultSpacesEntry : id;
@ -72,6 +163,8 @@ class ChatListController extends State<ChatList> with TickerProviderStateMixin {
Stream<Client> get clientStream => _clientStream.stream;
void addAccountAction() => VRouter.of(context).to('/settings/account/add');
void _onScroll() {
final newScrolledToTop = scrollController.position.pixels <= 0;
if (newScrolledToTop != scrolledToTop) {
@ -82,12 +175,7 @@ class ChatListController extends State<ChatList> with TickerProviderStateMixin {
}
void setActiveSpacesEntry(BuildContext context, SpacesEntry? spaceId) {
if ((snappingSheetController.isAttached
? snappingSheetController.currentPosition
: 0) !=
kSpacesBottomBarHeight) {
snapBackSpacesSheet();
}
Scaffold.of(context).closeDrawer();
setState(() => _activeSpacesEntry = spaceId);
}
@ -212,6 +300,10 @@ class ChatListController extends State<ChatList> with TickerProviderStateMixin {
scrollController.addListener(_onScroll);
_waitForFirstSync();
_hackyWebRTCFixForWeb();
WidgetsBinding.instance.addPostFrameCallback((_) async {
searchServer = await Store().getItem(_serverStoreNamespace);
});
super.initState();
}
@ -338,32 +430,6 @@ class ChatListController extends State<ChatList> with TickerProviderStateMixin {
);
}
void onPopupMenuSelect(action) {
switch (action) {
case PopupMenuAction.setStatus:
setStatus();
break;
case PopupMenuAction.settings:
VRouter.of(context).to('/settings');
break;
case PopupMenuAction.invite:
FluffyShare.share(
L10n.of(context)!.inviteText(Matrix.of(context).client.userID!,
'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'),
context);
break;
case PopupMenuAction.newGroup:
VRouter.of(context).to('/newgroup');
break;
case PopupMenuAction.newSpace:
VRouter.of(context).to('/newspace');
break;
case PopupMenuAction.archive:
VRouter.of(context).to('/archive');
break;
}
}
Future<void> _archiveSelectedRooms() async {
final client = Matrix.of(context).client;
while (selectedRoomIds.isNotEmpty) {
@ -593,15 +659,6 @@ class ChatListController extends State<ChatList> with TickerProviderStateMixin {
Matrix.of(context).voipPlugin?.context = context;
}
void snapBackSpacesSheet() {
snappingSheetController.snapToPosition(
const SnappingPosition.pixels(
positionPixels: kSpacesBottomBarHeight,
snappingDuration: Duration(milliseconds: 500),
),
);
}
expandSpaces() {
snappingSheetController.snapToPosition(
const SnappingPosition.factor(positionFactor: 0.5),

View File

@ -8,9 +8,12 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart';
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
import 'package:fluffychat/pages/chat_list/stories_header.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/connection_status_header.dart';
import 'package:fluffychat/widgets/profile_bottom_sheet.dart';
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
import '../../utils/stream_extension.dart';
import '../../widgets/matrix.dart';
@ -46,6 +49,8 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
@override
Widget build(BuildContext context) {
final reversed = !_animationReversed();
final roomSearchResult = widget.controller.roomSearchResult;
final userSearchResult = widget.controller.userSearchResult;
Widget child;
if (widget.controller.waitForFirstSync &&
Matrix.of(context).client.prevBatch != null) {
@ -86,13 +91,113 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
itemBuilder: (BuildContext context, int i) {
if (displayStoriesHeader) {
if (i == 0) {
return const StoriesHeader();
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const ConnectionStatusHeader(),
if (roomSearchResult != null) ...[
_SearchTitle(title: L10n.of(context)!.publicRooms),
AnimatedContainer(
height: roomSearchResult.chunk.isEmpty ? 0 : 106,
duration: const Duration(milliseconds: 250),
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: roomSearchResult.chunk.length,
itemBuilder: (context, i) => _SearchItem(
title: roomSearchResult.chunk[i].name ??
roomSearchResult
.chunk[i].canonicalAlias?.localpart ??
L10n.of(context)!.group,
avatar: roomSearchResult.chunk[i].avatarUrl,
onPressed: () => showModalBottomSheet(
context: context,
builder: (c) => PublicRoomBottomSheet(
roomAlias:
roomSearchResult.chunk[i].canonicalAlias ??
roomSearchResult.chunk[i].roomId,
outerContext: context,
chunk: roomSearchResult.chunk[i],
),
),
),
),
),
],
if (userSearchResult != null) ...[
_SearchTitle(title: L10n.of(context)!.users),
AnimatedContainer(
height: userSearchResult.results.isEmpty ? 0 : 106,
duration: const Duration(milliseconds: 250),
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: userSearchResult.results.length,
itemBuilder: (context, i) => _SearchItem(
title: userSearchResult.results[i].displayName ??
userSearchResult.results[i].userId.localpart ??
L10n.of(context)!.unknownDevice,
avatar: userSearchResult.results[i].avatarUrl,
onPressed: () => showModalBottomSheet(
context: context,
builder: (c) => ProfileBottomSheet(
userId: userSearchResult.results[i].userId,
outerContext: context,
),
),
),
),
),
],
if (widget.controller.isSearchMode)
_SearchTitle(title: L10n.of(context)!.stories),
StoriesHeader(
filter: widget.controller.searchController.text,
),
AnimatedContainer(
height: !widget.controller.isSearchMode &&
widget.controller.showChatBackupBanner
? 54
: 0,
duration: const Duration(milliseconds: 300),
clipBehavior: Clip.hardEdge,
curve: Curves.bounceInOut,
decoration: const BoxDecoration(),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: ListTile(
leading: Image.asset(
'assets/backup.png',
fit: BoxFit.contain,
width: 44,
),
title: Text(
L10n.of(context)!.setupChatBackupNow,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
),
),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: widget.controller.firstRunBootstrapAction,
),
),
),
if (widget.controller.isSearchMode)
_SearchTitle(title: L10n.of(context)!.chats),
],
);
}
i--;
}
if (i >= rooms.length) {
return const ListTile();
}
if (!rooms[i].displayname.toLowerCase().contains(
widget.controller.searchController.text.toLowerCase())) {
return Container();
}
return ChatListItem(
rooms[i],
selected: widget.controller.selectedRoomIds.contains(rooms[i].id),
@ -176,13 +281,7 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: (widget.controller.snappingSheetController.isAttached
? widget
.controller.snappingSheetController.currentPosition
: 0) ==
kSpacesBottomBarHeight
? SharedAxisTransitionType.horizontal
: SharedAxisTransitionType.vertical,
transitionType: SharedAxisTransitionType.vertical,
fillColor: Theme.of(context).scaffoldBackgroundColor,
child: child,
);
@ -221,3 +320,77 @@ class _ChatListViewBodyState extends State<ChatListViewBody> {
return reversed;
}
}
class _SearchTitle extends StatelessWidget {
final String title;
const _SearchTitle({required this.title, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Container(
decoration: BoxDecoration(
border: Border.symmetric(
horizontal: BorderSide(
color: Theme.of(context).dividerColor,
width: 1,
)),
color: Theme.of(context).colorScheme.surface,
),
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: Text(title,
textAlign: TextAlign.left,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontSize: 12,
fontWeight: FontWeight.bold,
)),
),
),
);
}
class _SearchItem extends StatelessWidget {
final String title;
final Uri? avatar;
final void Function() onPressed;
const _SearchItem({
required this.title,
this.avatar,
required this.onPressed,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) => InkWell(
onTap: onPressed,
child: SizedBox(
width: 84,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
Avatar(
mxContent: avatar,
name: title,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
title,
maxLines: 2,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
),
],
),
),
);
}

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/spaces_drawer.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/widgets/matrix.dart';
class ChatListDrawer extends StatelessWidget {
final ChatListController controller;
const ChatListDrawer(this.controller, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Drawer(
child: SafeArea(
child: Column(
children: [
Expanded(
child: SpacesDrawer(
controller: controller,
),
),
const Divider(),
ListTile(
leading: Icon(
Icons.group_add_outlined,
color: Theme.of(context).colorScheme.onBackground,
),
title: Text(L10n.of(context)!.createNewGroup),
onTap: () {
Scaffold.of(context).closeDrawer();
VRouter.of(context).to('/newgroup');
},
),
ListTile(
leading: Icon(
Icons.adaptive.share_outlined,
color: Theme.of(context).colorScheme.onBackground,
),
title: Text(L10n.of(context)!.inviteContact),
onTap: () {
Scaffold.of(context).closeDrawer();
FluffyShare.share(
L10n.of(context)!.inviteText(
Matrix.of(context).client.userID!,
'https://matrix.to/#/${Matrix.of(context).client.userID}?client=im.fluffychat'),
context);
},
),
ListTile(
leading: Icon(
Icons.settings_outlined,
color: Theme.of(context).colorScheme.onBackground,
),
title: Text(L10n.of(context)!.settings),
onTap: () {
Scaffold.of(context).closeDrawer();
VRouter.of(context).to('/settings');
},
),
],
),
),
);
}

View File

@ -1,15 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:animations/animations.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/client_chooser_button.dart';
import '../../widgets/matrix.dart';
import 'package:fluffychat/widgets/matrix.dart';
class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
final ChatListController controller;
@ -21,23 +17,92 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
final selectMode = controller.selectMode;
return AppBar(
elevation: controller.scrolledToTop ? 0 : null,
actionsIconTheme: IconThemeData(
color: controller.selectedRoomIds.isEmpty
? null
: Theme.of(context).colorScheme.primary,
),
leading: Matrix.of(context).isMultiAccount
? ClientChooserButton(controller)
: selectMode == SelectMode.normal
? null
: IconButton(
tooltip: L10n.of(context)!.cancel,
icon: const Icon(Icons.close_outlined),
onPressed: controller.cancelAction,
color: Theme.of(context).colorScheme.primary,
titleSpacing: 8,
automaticallyImplyLeading: false,
leading: selectMode == SelectMode.normal
? null
: IconButton(
tooltip: L10n.of(context)!.cancel,
icon: const Icon(Icons.close_outlined),
onPressed: controller.cancelAction,
color: Theme.of(context).colorScheme.primary,
),
title: selectMode == SelectMode.share
? Text(
L10n.of(context)!.share,
key: const ValueKey(SelectMode.share),
)
: selectMode == SelectMode.select
? Text(
controller.selectedRoomIds.length.toString(),
key: const ValueKey(SelectMode.select),
)
: TextField(
controller: controller.searchController,
textInputAction: TextInputAction.search,
onChanged: controller.onSearchEnter,
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(90),
borderSide: BorderSide.none,
),
hintText: L10n.of(context)!.search,
prefixIcon: controller.isSearchMode
? IconButton(
tooltip: L10n.of(context)!.cancel,
icon: const Icon(Icons.close_outlined),
onPressed: controller.cancelSearch,
color: Theme.of(context).colorScheme.primary,
)
: IconButton(
onPressed: Scaffold.of(context).openDrawer,
icon: Icon(
Icons.menu,
color: Theme.of(context).colorScheme.onBackground,
),
),
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
children: controller.isSearchMode
? [
if (controller.isSearching)
const CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
TextButton(
onPressed: controller.setServer,
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 12),
),
child: Text(
controller.searchServer ??
Matrix.of(context)
.client
.homeserver!
.host,
maxLines: 2,
),
),
]
: [
IconButton(
icon: Icon(
Icons.camera_alt_outlined,
color: Theme.of(context)
.colorScheme
.onBackground,
),
tooltip: L10n.of(context)!.addToStory,
onPressed: () =>
VRouter.of(context).to('/stories/create'),
),
ClientChooserButton(controller),
const SizedBox(width: 12),
],
),
),
),
centerTitle: false,
actions: selectMode == SelectMode.share
? null
: selectMode == SelectMode.select
@ -75,138 +140,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
onPressed: controller.archiveAction,
),
]
: [
KeyBoardShortcuts(
keysToPress: {
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.keyF
},
onKeysPressed: () => VRouter.of(context).to('/search'),
helpLabel: L10n.of(context)!.search,
child: IconButton(
icon: const Icon(Icons.search_outlined),
tooltip: L10n.of(context)!.search,
onPressed: () => VRouter.of(context).to('/search'),
),
),
if (selectMode == SelectMode.normal)
IconButton(
icon: const Icon(Icons.camera_alt_outlined),
tooltip: L10n.of(context)!.addToStory,
onPressed: () =>
VRouter.of(context).to('/stories/create'),
),
PopupMenuButton<PopupMenuAction>(
onSelected: controller.onPopupMenuSelect,
itemBuilder: (_) => [
PopupMenuItem(
value: PopupMenuAction.setStatus,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.edit_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.setStatus),
],
),
),
PopupMenuItem(
value: PopupMenuAction.newGroup,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.group_add_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.createNewGroup),
],
),
),
PopupMenuItem(
value: PopupMenuAction.newSpace,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.group_work_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.createNewSpace),
],
),
),
PopupMenuItem(
value: PopupMenuAction.invite,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.adaptive.share_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.inviteContact),
],
),
),
PopupMenuItem(
value: PopupMenuAction.archive,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.archive_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.archive),
],
),
),
PopupMenuItem(
value: PopupMenuAction.settings,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.settings_outlined),
const SizedBox(width: 12),
Text(L10n.of(context)!.settings),
],
),
),
],
),
],
title: PageTransitionSwitcher(
reverse: false,
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
},
layoutBuilder: (children) => Stack(
alignment: AlignmentDirectional.centerStart,
children: children,
),
child: selectMode == SelectMode.share
? Text(
L10n.of(context)!.share,
key: const ValueKey(SelectMode.share),
)
: selectMode == SelectMode.select
? Text(
controller.selectedRoomIds.length.toString(),
key: const ValueKey(SelectMode.select),
)
: (() {
final name = controller.activeSpaceId == null
? AppConfig.applicationName
: Matrix.of(context)
.client
.getRoomById(controller.activeSpaceId!)!
.displayname;
return Text(name, key: ValueKey(name));
})(),
),
: null,
);
}

View File

@ -4,12 +4,10 @@ import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:keyboard_shortcuts/keyboard_shortcuts.dart';
import 'package:snapping_sheet/snapping_sheet.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/spaces_bottom_bar.dart';
import 'package:fluffychat/widgets/connection_status_header.dart';
import 'package:fluffychat/pages/chat_list/chat_list_drawer.dart';
import '../../widgets/matrix.dart';
import 'chat_list_body.dart';
import 'chat_list_header.dart';
@ -25,8 +23,6 @@ class ChatListView extends StatelessWidget {
stream: Matrix.of(context).onShareContentChanged.stream,
builder: (_, __) {
final selectMode = controller.selectMode;
final showSpaces = controller.spacesEntries.length > 1 &&
controller.selectedRoomIds.isEmpty;
return VWidgetGuard(
onSystemPop: (redirector) async {
final selMode = controller.selectMode;
@ -35,87 +31,28 @@ class ChatListView extends StatelessWidget {
},
child: Scaffold(
appBar: ChatListHeader(controller: controller),
body: LayoutBuilder(
builder: (context, size) {
controller.snappingSheetContainerSize = size;
return SnappingSheet(
key: ValueKey(Matrix.of(context).client.userID.toString() +
showSpaces.toString()),
controller: controller.snappingSheetController,
child: Column(
children: [
AnimatedContainer(
height: controller.showChatBackupBanner ? 54 : 0,
duration: const Duration(milliseconds: 300),
clipBehavior: Clip.hardEdge,
curve: Curves.bounceInOut,
decoration: const BoxDecoration(),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: ListTile(
leading: Image.asset(
'assets/backup.png',
fit: BoxFit.contain,
width: 44,
),
title: Text(L10n.of(context)!.setupChatBackupNow),
trailing: const Icon(Icons.chevron_right_outlined),
onTap: controller.firstRunBootstrapAction,
),
),
),
Expanded(child: ChatListViewBody(controller)),
],
),
initialSnappingPosition: showSpaces
? const SnappingPosition.pixels(
positionPixels: kSpacesBottomBarHeight)
: const SnappingPosition.factor(positionFactor: 0.0),
snappingPositions: showSpaces
? const [
SnappingPosition.pixels(
positionPixels: kSpacesBottomBarHeight),
SnappingPosition.factor(positionFactor: 0.5),
SnappingPosition.factor(positionFactor: 0.9),
]
: [const SnappingPosition.factor(positionFactor: 0.0)],
sheetBelow: showSpaces
? SnappingSheetContent(
childScrollController:
controller.snappingSheetScrollContentController,
draggable: true,
child: SpacesBottomBar(controller),
)
: null,
);
},
),
body: ChatListViewBody(controller),
drawer: ChatListDrawer(controller),
floatingActionButton: selectMode == SelectMode.normal
? Padding(
padding: showSpaces
? const EdgeInsets.only(bottom: 64.0)
: const EdgeInsets.all(0),
child: KeyBoardShortcuts(
child: FloatingActionButton.extended(
isExtended: controller.scrolledToTop,
onPressed: () =>
VRouter.of(context).to('/newprivatechat'),
icon: const Icon(CupertinoIcons.chat_bubble),
label: Text(L10n.of(context)!.newChat),
),
keysToPress: {
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.keyN
},
onKeysPressed: () =>
? KeyBoardShortcuts(
child: FloatingActionButton.extended(
isExtended: controller.scrolledToTop,
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
onPressed: () =>
VRouter.of(context).to('/newprivatechat'),
helpLabel: L10n.of(context)!.newChat,
icon: const Icon(CupertinoIcons.chat_bubble),
label: Text(L10n.of(context)!.newChat),
),
keysToPress: {
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.keyN
},
onKeysPressed: () =>
VRouter.of(context).to('/newprivatechat'),
helpLabel: L10n.of(context)!.newChat,
)
: null,
bottomNavigationBar: const SafeArea(
child: ConnectionStatusHeader(),
),
),
);
},

View File

@ -79,6 +79,16 @@ class ClientChooserButton extends StatelessWidget {
)
.toList(),
],
PopupMenuItem(
value: AddAccountAction.addAccount,
child: Row(
children: [
const Icon(Icons.person_add_outlined),
const SizedBox(width: 18),
Text(L10n.of(context)!.addAccount),
],
),
),
];
}
@ -124,7 +134,8 @@ class ClientChooserButton extends StatelessWidget {
),
PopupMenuButton<Object>(
child: Material(
borderRadius: BorderRadius.zero,
color: Colors.transparent,
borderRadius: BorderRadius.circular(99),
child: Avatar(
mxContent: snapshot.data?.avatarUrl,
name: snapshot.data?.displayName ??
@ -158,6 +169,8 @@ class ClientChooserButton extends StatelessWidget {
controller.setActiveClient(object);
} else if (object is String) {
controller.setActiveBundle(object);
} else if (object == AddAccountAction.addAccount) {
controller.addAccountAction();
}
}
@ -222,3 +235,5 @@ class ClientChooserButton extends StatelessWidget {
_handleKeyboardShortcut(matrix, lastIndex! - 1);
}
}
enum AddAccountAction { addAccount }

View File

@ -1,164 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:salomon_bottom_bar/salomon_bottom_bar.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/spaces_drawer.dart';
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
const kSpacesBottomBarHeight = 56.0;
final GlobalKey _globalKey = GlobalKey();
class SpacesBottomBar extends StatelessWidget {
final ChatListController controller;
const SpacesBottomBar(this.controller, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).navigationBarTheme.backgroundColor,
elevation: 6,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppConfig.borderRadius)),
clipBehavior: Clip.hardEdge,
child: SafeArea(
child: StreamBuilder<Object>(
stream: Matrix.of(context).client.onSync.stream.where((sync) =>
(sync.rooms?.join?.values.any((r) =>
r.state?.any((s) => s.type.startsWith('m.space')) ??
false) ??
false) ||
(sync.rooms?.leave?.isNotEmpty ?? false)),
builder: (context, snapshot) {
return SingleChildScrollView(
controller: controller.snappingSheetScrollContentController,
child: AnimatedBuilder(
child: _SpacesBottomNavigation(
key: _globalKey, controller: controller),
builder: (context, child) {
if (controller.snappingSheetContainerSize == null) {
return child!;
}
final rawPosition =
controller.snappingSheetController.isAttached
? controller.snappingSheetController.currentPosition
: 0;
final position = rawPosition /
controller.snappingSheetContainerSize!.maxHeight;
if (rawPosition <= kSpacesBottomBarHeight) {
return child!;
} else if (position >= 0.5) {
return SpacesDrawer(controller: controller);
} else {
final normalized = (rawPosition - kSpacesBottomBarHeight) /
(controller.snappingSheetContainerSize!.maxHeight -
kSpacesBottomBarHeight) *
2;
var boxHeight = (1 - normalized) * kSpacesBottomBarHeight;
if (boxHeight < 0) boxHeight = 0;
return Column(
children: [
SizedBox(
height: boxHeight,
child: ClipRect(
clipBehavior: Clip.hardEdge,
child: Opacity(
opacity: 1 - normalized, child: child!)),
),
Opacity(
opacity: normalized,
child: SpacesDrawer(controller: controller),
),
],
);
}
},
animation: controller.snappingSheetController,
),
);
},
),
),
);
}
}
class _SpacesBottomNavigation extends StatelessWidget {
final ChatListController controller;
const _SpacesBottomNavigation({Key? key, required this.controller})
: super(key: key);
@override
Widget build(BuildContext context) {
final currentIndex = controller.spacesEntries.indexWhere((space) =>
controller.activeSpacesEntry.runtimeType == space.runtimeType &&
(controller.activeSpaceId == space.getSpace(context)?.id)) +
1;
return Container(
height: 56,
alignment: Alignment.center,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SalomonBottomBar(
itemPadding: const EdgeInsets.all(8),
currentIndex: currentIndex,
onTap: (i) => i == 0
? controller.expandSpaces()
: controller.setActiveSpacesEntry(
context,
controller.spacesEntries[i - 1],
),
selectedItemColor: Theme.of(context).colorScheme.primary,
items: [
SalomonBottomBarItem(
icon: const Icon(Icons.keyboard_arrow_up),
title: Text(L10n.of(context)!.showSpaces),
),
...controller.spacesEntries
.map((space) => _buildSpacesEntryUI(context, space))
.toList(),
],
),
),
);
}
SalomonBottomBarItem _buildSpacesEntryUI(
BuildContext context, SpacesEntry entry) {
final space = entry.getSpace(context);
if (space != null) {
return SalomonBottomBarItem(
icon: InkWell(
borderRadius: BorderRadius.circular(28),
onTap: () => controller.setActiveSpacesEntry(
context,
entry,
),
onLongPress: () => controller.editSpace(context, space.id),
child: Avatar(
mxContent: space.avatar,
name: space.displayname,
size: 24,
fontSize: 12,
),
),
title: Text(entry.getName(context)),
);
}
return SalomonBottomBarItem(
icon: entry.getIcon(false),
activeIcon: entry.getIcon(true),
title: Text(entry.getName(context)),
);
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/pages/chat_list/spaces_entry.dart';
import 'package:fluffychat/widgets/avatar.dart';
@ -22,53 +23,93 @@ class SpacesDrawer extends StatelessWidget {
// TODO(TheOeWithTheBraid): wait for space hierarchy https://gitlab.com/famedly/company/frontend/libraries/matrix_api_lite/-/merge_requests/58
return WillPopScope(
onWillPop: () async {
controller.snapBackSpacesSheet();
return false;
},
child: Column(
children: List.generate(spaceHierarchy.length, (index) {
final space = spaceHierarchy.keys.toList()[index];
final room = space.getSpace(context);
final active = currentIndex == index;
return ListView.builder(
itemCount: spaceHierarchy.length + 2,
itemBuilder: (context, i) {
if (i == spaceHierarchy.length) {
return ListTile(
selected: active,
leading: index == 0
? const Icon(Icons.keyboard_arrow_down)
: room == null
? space.getIcon(active)
: Avatar(
mxContent: room.avatar,
name: space.getName(context),
size: 24,
fontSize: 12,
),
title: Text(space.getName(context)),
subtitle: room?.topic.isEmpty ?? true
? null
: Tooltip(
message: room!.topic,
child: Text(
room.topic.replaceAll('\n', ' '),
softWrap: false,
overflow: TextOverflow.fade,
),
),
onTap: () => controller.setActiveSpacesEntry(
context,
space,
leading: CircleAvatar(
radius: Avatar.defaultSize / 2,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onSecondaryContainer,
child: const Icon(
Icons.archive_outlined,
),
),
trailing: room != null
? IconButton(
icon: const Icon(Icons.edit),
title: Text(L10n.of(context)!.archive),
onTap: () {
Scaffold.of(context).closeDrawer();
VRouter.of(context).to('/archive');
},
);
}
if (i == spaceHierarchy.length + 1) {
return ListTile(
leading: CircleAvatar(
child: const Icon(Icons.group_work_outlined),
radius: Avatar.defaultSize / 2,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onSecondaryContainer,
),
title: Text(L10n.of(context)!.createNewSpace),
onTap: () {
Scaffold.of(context).closeDrawer();
VRouter.of(context).to('/newspace');
},
);
}
final space = spaceHierarchy.keys.toList()[i];
final room = space.getSpace(context);
final active = currentIndex == i;
return ListTile(
selected: active,
leading: room == null
? CircleAvatar(
child: space.getIcon(active),
radius: Avatar.defaultSize / 2,
backgroundColor:
Theme.of(context).colorScheme.secondaryContainer,
foregroundColor:
Theme.of(context).colorScheme.onSecondaryContainer,
)
: Avatar(
mxContent: room.avatar,
name: space.getName(context),
),
title: Text(
space.getName(context),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: room?.topic.isEmpty ?? true
? null
: Tooltip(
message: room!.topic,
child: Text(
room.topic.replaceAll('\n', ' '),
softWrap: false,
overflow: TextOverflow.fade,
),
),
onTap: () => controller.setActiveSpacesEntry(
context,
space,
),
trailing: room != null
? SizedBox(
width: 32,
child: IconButton(
splashRadius: 24,
icon: const Icon(Icons.edit_outlined),
tooltip: L10n.of(context)!.edit,
onPressed: () => controller.editSpace(context, room.id),
)
: null,
);
}),
),
),
)
: null,
);
},
);
}
}

View File

@ -18,7 +18,8 @@ enum ContextualRoomAction {
}
class StoriesHeader extends StatelessWidget {
const StoriesHeader({Key? key}) : super(key: key);
final String filter;
const StoriesHeader({required this.filter, Key? key}) : super(key: key);
void _addToStoryAction(BuildContext context) =>
VRouter.of(context).to('/stories/create');
@ -105,7 +106,10 @@ class StoriesHeader extends StatelessWidget {
onTap: () => _addToStoryAction(context),
);
}
if (client.storiesRooms.isEmpty) {
if (client.storiesRooms.isEmpty ||
!client.storiesRooms.any((room) => room.displayname
.toLowerCase()
.contains(filter.toLowerCase()))) {
return Container();
}
final ownStoryRoom = client.storiesRooms
@ -130,6 +134,11 @@ class StoriesHeader extends StatelessWidget {
userId?.localpart ??
'Unknown';
final avatarUrl = snapshot.data?.avatarUrl;
if (!displayname
.toLowerCase()
.contains(filter.toLowerCase())) {
return Container();
}
return _StoryButton(
profile: Profile(
displayName: displayname,
@ -139,7 +148,7 @@ class StoriesHeader extends StatelessWidget {
hasPosts: room.hasPosts || room == ownStoryRoom,
showEditFab: userId == client.userID,
unread: room.membership == Membership.invite ||
room.hasNewMessages,
(room.hasNewMessages && room.hasPosts),
onPressed: () => _goToStoryAction(context, room.id),
onLongPressed: () =>
_contextualActions(context, room),

View File

@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/connect/connect_page.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -90,14 +89,14 @@ class ConnectPageView extends StatelessWidget {
child: TextField(
controller: controller.usernameController,
onSubmitted: (_) => controller.signUp(),
style: FluffyThemes.loginTextFieldStyle,
decoration: FluffyThemes.loginTextFieldDecoration(
prefixIcon: const Icon(
Icons.account_box_outlined,
color: Colors.black,
),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.account_box_outlined),
hintText: L10n.of(context)!.chooseAUsername,
errorText: controller.signupError,
fillColor: Theme.of(context)
.colorScheme
.background
.withOpacity(0.75),
),
),
),
@ -106,12 +105,7 @@ class ConnectPageView extends StatelessWidget {
child: Hero(
tag: 'loginButton',
child: ElevatedButton(
onPressed: controller.loading ? null : controller.signUp,
style: ElevatedButton.styleFrom(
primary: Colors.white.withAlpha(200),
onPrimary: Colors.black,
shadowColor: Colors.white,
),
onPressed: controller.loading ? () {} : controller.signUp,
child: controller.loading
? const LinearProgressIndicator()
: Text(L10n.of(context)!.signUp),
@ -148,11 +142,6 @@ class ConnectPageView extends StatelessWidget {
child: ElevatedButton(
onPressed: () => controller
.ssoLoginAction(identityProviders.single.id!),
style: ElevatedButton.styleFrom(
primary: Colors.white.withAlpha(200),
onPrimary: Colors.black,
shadowColor: Colors.white,
),
child: Text(identityProviders.single.name ??
identityProviders.single.brand ??
L10n.of(context)!.loginWithOneClick),
@ -176,11 +165,6 @@ class ConnectPageView extends StatelessWidget {
tag: 'signinButton',
child: ElevatedButton(
onPressed: controller.loading ? () {} : controller.login,
style: ElevatedButton.styleFrom(
primary: Colors.white.withAlpha(200),
onPrimary: Colors.black,
shadowColor: Colors.white,
),
child: Text(L10n.of(context)!.login),
),
),

View File

@ -22,17 +22,17 @@ class SsoButton extends StatelessWidget {
onTap: onPressed,
borderRadius: BorderRadius.circular(7),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Material(
color: Colors.white,
borderRadius: BorderRadius.circular(7),
borderRadius: BorderRadius.circular(8),
clipBehavior: Clip.hardEdge,
child: Padding(
padding: const EdgeInsets.all(2.0),
padding: const EdgeInsets.all(4.0),
child: identityProvider.icon == null
? const Icon(Icons.web_outlined)
: CachedNetworkImage(

View File

@ -37,60 +37,53 @@ class DevicesSettingsView extends StatelessWidget {
return const Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 2));
}
return Column(
children: <Widget>[
if (controller.thisDevice != null)
UserDeviceListItem(
controller.thisDevice!,
rename: controller.renameDeviceAction,
remove: (d) => controller.removeDevicesAction([d]),
verify: controller.verifyDeviceAction,
block: controller.blockDeviceAction,
unblock: controller.unblockDeviceAction,
),
const Divider(height: 1),
if (controller.notThisDevice.isNotEmpty)
ListTile(
title: Text(
controller.errorDeletingDevices ??
L10n.of(context)!.removeAllOtherDevices,
style: const TextStyle(color: Colors.red),
),
trailing: controller.loadingDeletingDevices
? const CircularProgressIndicator.adaptive(
strokeWidth: 2)
: const Icon(Icons.delete_outline),
onTap: controller.loadingDeletingDevices
? null
: () => controller
.removeDevicesAction(controller.notThisDevice),
),
const Divider(height: 1),
Expanded(
child: controller.notThisDevice.isEmpty
? Center(
child: Icon(
Icons.devices_other,
size: 60,
color: Theme.of(context).secondaryHeaderColor,
),
)
: ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
const Divider(height: 1),
itemCount: controller.notThisDevice.length,
itemBuilder: (BuildContext context, int i) =>
UserDeviceListItem(
controller.notThisDevice[i],
rename: controller.renameDeviceAction,
remove: (d) => controller.removeDevicesAction([d]),
verify: controller.verifyDeviceAction,
block: controller.blockDeviceAction,
unblock: controller.unblockDeviceAction,
),
return ListView.builder(
itemCount: controller.notThisDevice.length + 1,
itemBuilder: (BuildContext context, int i) {
if (i == 0) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (controller.thisDevice != null)
UserDeviceListItem(
controller.thisDevice!,
rename: controller.renameDeviceAction,
remove: (d) => controller.removeDevicesAction([d]),
verify: controller.verifyDeviceAction,
block: controller.blockDeviceAction,
unblock: controller.unblockDeviceAction,
),
),
],
const Divider(height: 1),
if (controller.notThisDevice.isNotEmpty)
ListTile(
title: Text(
controller.errorDeletingDevices ??
L10n.of(context)!.removeAllOtherDevices,
style: const TextStyle(color: Colors.red),
),
trailing: controller.loadingDeletingDevices
? const CircularProgressIndicator.adaptive(
strokeWidth: 2)
: const Icon(Icons.delete_outline),
onTap: controller.loadingDeletingDevices
? null
: () => controller.removeDevicesAction(
controller.notThisDevice),
),
const Divider(height: 1),
],
);
}
i--;
return UserDeviceListItem(
controller.notThisDevice[i],
rename: controller.renameDeviceAction,
remove: (d) => controller.removeDevicesAction([d]),
verify: controller.verifyDeviceAction,
block: controller.blockDeviceAction,
unblock: controller.unblockDeviceAction,
);
},
);
},
),

View File

@ -108,12 +108,13 @@ class UserDeviceListItem extends StatelessWidget {
),
title: Row(
children: <Widget>[
Text(
userDevice.displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
Expanded(
child: Text(
userDevice.displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Spacer(),
if (keys != null)
Text(
keys.blocked

View File

@ -5,7 +5,6 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'homeserver_picker.dart';
@ -22,131 +21,127 @@ class HomeserverPickerView extends StatelessWidget {
appBar: VRouter.of(context).path == '/home'
? null
: AppBar(title: Text(L10n.of(context)!.addAccount)),
body: Column(
children: [
Expanded(
child: ListView(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
constraints: BoxConstraints(
maxHeight: controller.displayServerList ? 0 : 256),
alignment: Alignment.center,
child: Image.asset('assets/info-logo.png'),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
focusNode: controller.homeserverFocusNode,
controller: controller.homeserverController,
onChanged: controller.onChanged,
style: FluffyThemes.loginTextFieldStyle,
decoration: FluffyThemes.loginTextFieldDecoration(
labelText: L10n.of(context)!.homeserver,
hintText: L10n.of(context)!.enterYourHomeserver,
suffixIcon: const Icon(
Icons.search,
color: Colors.black,
),
errorText: controller.error,
),
readOnly: !AppConfig.allowOtherHomeservers,
onSubmitted: (_) => controller.checkHomeserverAction(),
autocorrect: false,
body: SafeArea(
child: Column(
children: [
Expanded(
child: ListView(
children: [
Container(
alignment: Alignment.center,
height: 256,
child: Image.asset('assets/info-logo.png'),
),
),
if (controller.displayServerList)
Padding(
padding: const EdgeInsets.all(16.0),
child: Material(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
color: Colors.white.withAlpha(200),
clipBehavior: Clip.hardEdge,
child: benchmarkResults == null
? const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator.adaptive(),
))
: Column(
children: controller.filteredHomeservers
.map(
(server) => ListTile(
trailing: IconButton(
icon: const Icon(
Icons.info_outlined,
color: Colors.black,
),
onPressed: () =>
controller.showServerInfo(server),
),
onTap: () => controller.setServer(
server.homeserver.baseUrl.host),
title: Text(
server.homeserver.baseUrl.host,
style: const TextStyle(
color: Colors.black),
),
subtitle: Text(
server.homeserver.description ?? '',
style: TextStyle(
color: Colors.grey.shade700),
),
),
)
.toList(),
),
child: TextField(
focusNode: controller.homeserverFocusNode,
controller: controller.homeserverController,
onChanged: controller.onChanged,
decoration: InputDecoration(
prefixText: '${L10n.of(context)!.homeserver}: ',
hintText: L10n.of(context)!.enterYourHomeserver,
suffixIcon: const Icon(Icons.search),
errorText: controller.error,
fillColor: Theme.of(context)
.colorScheme
.background
.withOpacity(0.75),
),
readOnly: !AppConfig.allowOtherHomeservers,
onSubmitted: (_) => controller.checkHomeserverAction(),
autocorrect: false,
),
),
Wrap(
alignment: WrapAlignment.center,
children: [
TextButton(
onPressed: () => launch(AppConfig.privacyUrl),
child: Text(
L10n.of(context)!.privacy,
style: const TextStyle(
decoration: TextDecoration.underline,
color: Colors.white,
),
if (controller.displayServerList)
Padding(
padding: const EdgeInsets.all(16.0),
child: Material(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
color: Colors.white.withAlpha(200),
clipBehavior: Clip.hardEdge,
child: benchmarkResults == null
? const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator.adaptive(),
))
: Column(
children: controller.filteredHomeservers
.map(
(server) => ListTile(
trailing: IconButton(
icon: const Icon(
Icons.info_outlined,
color: Colors.black,
),
onPressed: () =>
controller.showServerInfo(server),
),
onTap: () => controller.setServer(
server.homeserver.baseUrl.host),
title: Text(
server.homeserver.baseUrl.host,
style: const TextStyle(
color: Colors.black),
),
subtitle: Text(
server.homeserver.description ?? '',
style: TextStyle(
color: Colors.grey.shade700),
),
),
)
.toList(),
),
),
),
TextButton(
onPressed: () => PlatformInfos.showDialog(context),
child: Text(
L10n.of(context)!.about,
style: const TextStyle(
decoration: TextDecoration.underline,
color: Colors.white,
Wrap(
alignment: WrapAlignment.center,
children: [
TextButton(
onPressed: () => launch(AppConfig.privacyUrl),
child: Text(
L10n.of(context)!.privacy,
style: const TextStyle(
decoration: TextDecoration.underline,
color: Colors.white,
),
),
),
),
],
),
],
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Hero(
tag: 'loginButton',
child: ElevatedButton(
onPressed: controller.isLoading
? () {}
: controller.checkHomeserverAction,
style: ElevatedButton.styleFrom(
primary: Colors.white.withAlpha(200),
onPrimary: Colors.black,
shadowColor: Colors.white,
),
child: controller.isLoading
? const LinearProgressIndicator()
: Text(L10n.of(context)!.connect),
TextButton(
onPressed: () => PlatformInfos.showDialog(context),
child: Text(
L10n.of(context)!.about,
style: const TextStyle(
decoration: TextDecoration.underline,
color: Colors.white,
),
),
),
],
),
],
),
),
),
],
Container(
padding: const EdgeInsets.all(16),
width: double.infinity,
child: Hero(
tag: 'loginButton',
child: ElevatedButton(
onPressed: controller.isLoading
? null
: controller.checkHomeserverAction,
child: controller.isLoading
? const LinearProgressIndicator()
: Text(L10n.of(context)!.connect),
),
),
),
],
),
),
);
}

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'login.dart';
@ -44,16 +43,16 @@ class LoginView extends StatelessWidget {
controller: controller.usernameController,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
style: FluffyThemes.loginTextFieldStyle,
autofillHints:
controller.loading ? null : [AutofillHints.username],
decoration: FluffyThemes.loginTextFieldDecoration(
prefixIcon: const Icon(
Icons.account_box_outlined,
color: Colors.black,
),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.account_box_outlined),
errorText: controller.usernameError,
hintText: L10n.of(context)!.emailOrUsername,
fillColor: Theme.of(context)
.colorScheme
.background
.withOpacity(0.75),
),
),
),
@ -68,12 +67,8 @@ class LoginView extends StatelessWidget {
textInputAction: TextInputAction.next,
obscureText: !controller.showPassword,
onSubmitted: controller.login,
style: FluffyThemes.loginTextFieldStyle,
decoration: FluffyThemes.loginTextFieldDecoration(
prefixIcon: const Icon(
Icons.lock_outlined,
color: Colors.black,
),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.lock_outlined),
errorText: controller.passwordError,
suffixIcon: IconButton(
tooltip: L10n.of(context)!.showPassword,
@ -86,6 +81,10 @@ class LoginView extends StatelessWidget {
onPressed: controller.toggleShowPassword,
),
hintText: L10n.of(context)!.password,
fillColor: Theme.of(context)
.colorScheme
.background
.withOpacity(0.75),
),
),
),
@ -97,11 +96,6 @@ class LoginView extends StatelessWidget {
onPressed: controller.loading
? null
: () => controller.login(context),
style: ElevatedButton.styleFrom(
primary: Colors.white.withAlpha(200),
onPrimary: Colors.black,
shadowColor: Colors.white,
),
child: controller.loading
? const LinearProgressIndicator()
: Text(L10n.of(context)!.login),
@ -126,11 +120,7 @@ class LoginView extends StatelessWidget {
child: ElevatedButton(
onPressed:
controller.loading ? () {} : controller.passwordForgotten,
style: ElevatedButton.styleFrom(
primary: Colors.white.withAlpha(156),
onPrimary: Colors.red,
shadowColor: Colors.white,
),
style: ElevatedButton.styleFrom(onPrimary: Colors.red),
child: Text(L10n.of(context)!.passwordForgotten),
),
),

View File

@ -1,125 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/utils/famedlysdk_store.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
import 'search_view.dart';
class Search extends StatefulWidget {
const Search({Key? key}) : super(key: key);
@override
SearchController createState() => SearchController();
}
class SearchController extends State<Search> {
final TextEditingController controller = TextEditingController();
Future<QueryPublicRoomsResponse>? publicRoomsResponse;
String? lastServer;
Timer? _coolDown;
String? genericSearchTerm;
void search(String query) async {
setState(() {});
_coolDown?.cancel();
_coolDown = Timer(
const Duration(milliseconds: 500),
() => setState(() {
genericSearchTerm = query;
publicRoomsResponse = null;
searchUser(context, controller.text);
}),
);
}
void joinGroupAction(PublicRoomsChunk room) {
showModalBottomSheet(
context: context,
builder: (c) => PublicRoomBottomSheet(
roomAlias: room.canonicalAlias ?? room.roomId,
outerContext: context,
chunk: room,
),
);
}
String? server;
static const String _serverStoreNamespace = 'im.fluffychat.search.server';
void setServer() async {
final newServer = await showTextInputDialog(
useRootNavigator: false,
title: L10n.of(context)!.changeTheHomeserver,
context: context,
okLabel: L10n.of(context)!.ok,
cancelLabel: L10n.of(context)!.cancel,
textFields: [
DialogTextField(
prefixText: 'https://',
hintText: Matrix.of(context).client.homeserver?.host,
initialText: server,
keyboardType: TextInputType.url,
autocorrect: false)
]);
if (newServer == null) return;
Store().setItem(_serverStoreNamespace, newServer.single);
setState(() {
server = newServer.single;
});
}
String? currentSearchTerm;
List<Profile> foundProfiles = [];
static const searchUserDirectoryLimit = 10;
void searchUser(BuildContext context, String text) async {
if (text.isEmpty) {
setState(() {
foundProfiles = [];
});
}
currentSearchTerm = text;
if (currentSearchTerm?.isEmpty ?? true) return;
final matrix = Matrix.of(context);
SearchUserDirectoryResponse? response;
try {
response = await matrix.client.searchUserDirectory(
text,
limit: searchUserDirectoryLimit,
);
} catch (_) {}
foundProfiles = List<Profile>.from(response?.results ?? []);
if (foundProfiles.isEmpty && text.isValidMatrixId && text.sigil == '@') {
foundProfiles.add(Profile.fromJson({
'displayname': text.localpart,
'user_id': text,
}));
}
setState(() {});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
controller.text = VRouter.of(context).queryParameters['query'] ?? '';
final server = await Store().getItem(_serverStoreNamespace);
if (server?.isNotEmpty ?? false) {
this.server = server;
}
search(controller.text);
});
}
@override
Widget build(BuildContext context) => SearchView(this);
}

View File

@ -1,299 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
import 'package:fluffychat/utils/string_extension.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/contacts_list.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../utils/localized_exception_extension.dart';
import '../../utils/platform_infos.dart';
import 'search.dart';
class SearchView extends StatelessWidget {
final SearchController controller;
const SearchView(this.controller, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final server = controller.genericSearchTerm?.isValidMatrixId ?? false
? controller.genericSearchTerm!.domain
: controller.server;
if (controller.lastServer != server) {
controller.lastServer = server;
controller.publicRoomsResponse = null;
}
controller.publicRoomsResponse ??= Matrix.of(context)
.client
.queryPublicRooms(
server: server,
filter: PublicRoomQueryFilter(
genericSearchTerm: controller.genericSearchTerm,
),
)
.catchError((error) {
if (!(controller.genericSearchTerm?.isValidMatrixId ?? false)) {
throw error;
}
return QueryPublicRoomsResponse.fromJson({
'chunk': [],
});
}).then((QueryPublicRoomsResponse res) {
final genericSearchTerm = controller.genericSearchTerm;
if (genericSearchTerm != null &&
!res.chunk.any(
(room) => room.canonicalAlias == controller.genericSearchTerm)) {
// we have to tack on the original alias
res.chunk.add(
PublicRoomsChunk(
name: genericSearchTerm,
numJoinedMembers: 0,
roomId: '!unknown',
worldReadable: true,
guestCanJoin: true,
),
);
}
return res;
});
final rooms = List<Room>.from(Matrix.of(context).client.rooms);
rooms.removeWhere(
(room) =>
room.lastEvent == null ||
!room.displayname.toLowerCase().removeDiacritics().contains(
controller.controller.text.toLowerCase().removeDiacritics()),
);
const tabCount = 3;
return DefaultTabController(
length: tabCount,
initialIndex: controller.controller.text.startsWith('#') ? 0 : 1,
child: Scaffold(
appBar: AppBar(
leading: const BackButton(),
titleSpacing: 0,
title: TextField(
autofocus: true,
controller: controller.controller,
decoration: InputDecoration(
suffix: const Icon(Icons.search_outlined),
hintText: L10n.of(context)!.search,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
),
onChanged: controller.search,
),
bottom: TabBar(
indicatorColor: Theme.of(context).colorScheme.secondary,
labelColor: Theme.of(context).colorScheme.secondary,
unselectedLabelColor: Theme.of(context).textTheme.bodyText1!.color,
labelStyle: const TextStyle(fontSize: 16),
labelPadding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 0,
),
tabs: [
Tab(child: Text(L10n.of(context)!.discover, maxLines: 1)),
Tab(child: Text(L10n.of(context)!.chats, maxLines: 1)),
Tab(child: Text(L10n.of(context)!.people, maxLines: 1)),
],
),
),
body: TabBarView(
children: [
ListView(
keyboardDismissBehavior: PlatformInfos.isIOS
? ScrollViewKeyboardDismissBehavior.onDrag
: ScrollViewKeyboardDismissBehavior.manual,
children: [
const SizedBox(height: 12),
ListTile(
leading: CircleAvatar(
foregroundColor: Theme.of(context).colorScheme.secondary,
backgroundColor: Theme.of(context).secondaryHeaderColor,
child: const Icon(Icons.edit_outlined),
),
title: Text(L10n.of(context)!.changeTheServer),
onTap: controller.setServer,
),
FutureBuilder<QueryPublicRoomsResponse>(
future: controller.publicRoomsResponse,
builder: (BuildContext context,
AsyncSnapshot<QueryPublicRoomsResponse> snapshot) {
if (snapshot.hasError) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 32),
const Icon(
Icons.error_outlined,
size: 80,
color: Colors.grey,
),
Center(
child: Text(
snapshot.error!.toLocalizedString(context),
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.grey,
fontSize: 16,
),
),
),
],
);
}
if (snapshot.connectionState != ConnectionState.done) {
return const Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 2));
}
final publicRoomsResponse = snapshot.data!;
if (publicRoomsResponse.chunk.isEmpty) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 32),
const Icon(
Icons.search_outlined,
size: 80,
color: Colors.grey,
),
Center(
child: Text(
L10n.of(context)!.noPublicRoomsFound,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.grey,
fontSize: 16,
),
),
),
],
);
}
return GridView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(12),
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 1,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: publicRoomsResponse.chunk.length,
itemBuilder: (BuildContext context, int i) => Material(
elevation: 2,
borderRadius: BorderRadius.circular(16),
child: InkWell(
onTap: () => controller.joinGroupAction(
publicRoomsResponse.chunk[i],
),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Avatar(
mxContent:
publicRoomsResponse.chunk[i].avatarUrl,
name: publicRoomsResponse.chunk[i].name,
),
Text(
publicRoomsResponse.chunk[i].name!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
textAlign: TextAlign.center,
),
Text(
L10n.of(context)!.countParticipants(
publicRoomsResponse
.chunk[i].numJoinedMembers),
style: const TextStyle(fontSize: 10.5),
maxLines: 1,
textAlign: TextAlign.center,
),
Text(
publicRoomsResponse.chunk[i].topic ??
L10n.of(context)!.noDescription,
maxLines: 4,
textAlign: TextAlign.center,
),
],
),
),
),
),
);
}),
],
),
ListView.builder(
keyboardDismissBehavior: PlatformInfos.isIOS
? ScrollViewKeyboardDismissBehavior.onDrag
: ScrollViewKeyboardDismissBehavior.manual,
itemCount: rooms.length,
itemBuilder: (_, i) => ChatListItem(rooms[i]),
),
controller.foundProfiles.isNotEmpty
? ListView.builder(
keyboardDismissBehavior: PlatformInfos.isIOS
? ScrollViewKeyboardDismissBehavior.onDrag
: ScrollViewKeyboardDismissBehavior.manual,
itemCount: controller.foundProfiles.length,
itemBuilder: (BuildContext context, int i) {
final foundProfile = controller.foundProfiles[i];
return ListTile(
onTap: () async {
final roomID = await showFutureLoadingDialog(
context: context,
future: () async {
final client = Matrix.of(context).client;
final roomId = await client
.startDirectChat(foundProfile.userId);
return roomId;
},
);
if (roomID.error == null) {
VRouter.of(context)
.toSegments(['rooms', roomID.result!]);
}
},
leading: Avatar(
mxContent: foundProfile.avatarUrl,
name: foundProfile.displayName ?? foundProfile.userId,
//size: 24,
),
title: Text(
foundProfile.displayName ??
foundProfile.userId.localpart!,
style: const TextStyle(),
maxLines: 1,
),
subtitle: Text(
foundProfile.userId,
maxLines: 1,
style: const TextStyle(
fontSize: 12,
),
),
);
},
)
: ContactsList(searchController: controller.controller),
],
),
),
);
}
}

View File

@ -37,7 +37,7 @@ class SettingsView extends StatelessWidget {
),
],
body: ListTileTheme(
iconColor: Theme.of(context).textTheme.bodyText1!.color,
iconColor: Theme.of(context).colorScheme.onBackground,
child: ListView(
children: <Widget>[
ListTile(

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'signup.dart';
@ -38,11 +37,8 @@ class SignupPageView extends StatelessWidget {
controller: controller.passwordController,
obscureText: !controller.showPassword,
validator: controller.password1TextFieldValidator,
decoration: FluffyThemes.loginTextFieldDecoration(
prefixIcon: const Icon(
Icons.vpn_key_outlined,
color: Colors.black,
),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.vpn_key_outlined),
suffixIcon: IconButton(
tooltip: L10n.of(context)!.showPassword,
icon: Icon(
@ -53,7 +49,12 @@ class SignupPageView extends StatelessWidget {
),
onPressed: controller.toggleShowPassword,
),
errorStyle: const TextStyle(color: Colors.orange),
hintText: L10n.of(context)!.chooseAStrongPassword,
fillColor: Theme.of(context)
.colorScheme
.background
.withOpacity(0.75),
),
),
),
@ -68,12 +69,14 @@ class SignupPageView extends StatelessWidget {
controller: controller.password2Controller,
obscureText: !controller.showPassword,
validator: controller.password2TextFieldValidator,
decoration: FluffyThemes.loginTextFieldDecoration(
prefixIcon: const Icon(
Icons.repeat_outlined,
color: Colors.black,
),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.repeat_outlined),
hintText: L10n.of(context)!.repeatPassword,
errorStyle: const TextStyle(color: Colors.orange),
fillColor: Theme.of(context)
.colorScheme
.background
.withOpacity(0.75),
),
),
),
@ -87,16 +90,19 @@ class SignupPageView extends StatelessWidget {
autofillHints:
controller.loading ? null : [AutofillHints.username],
validator: controller.emailTextFieldValidator,
decoration: FluffyThemes.loginTextFieldDecoration(
prefixIcon: const Icon(
Icons.mail_outlined,
color: Colors.black,
),
decoration: InputDecoration(
prefixIcon: const Icon(Icons.mail_outlined),
hintText: L10n.of(context)!.enterAnEmailAddress,
errorText: controller.error,
errorColor: controller.emailController.text.isEmpty
? Colors.orangeAccent
: null,
fillColor: Theme.of(context)
.colorScheme
.background
.withOpacity(0.75),
errorStyle: TextStyle(
color: controller.emailController.text.isEmpty
? Colors.orangeAccent
: Colors.orange,
),
),
),
),
@ -106,11 +112,6 @@ class SignupPageView extends StatelessWidget {
padding: const EdgeInsets.all(16),
child: ElevatedButton(
onPressed: controller.loading ? () {} : controller.signup,
style: ElevatedButton.styleFrom(
primary: Colors.white.withAlpha(200),
onPrimary: Colors.black,
shadowColor: Colors.white,
),
child: controller.loading
? const LinearProgressIndicator()
: Text(L10n.of(context)!.signUp),

View File

@ -12,10 +12,6 @@ PODS:
- FlutterMacOS
- emoji_picker_flutter (0.0.1):
- FlutterMacOS
- file_selector_macos (0.0.1):
- FlutterMacOS
- flutter_app_badger (1.3.0):
- FlutterMacOS
- flutter_local_notifications (0.0.1):
- FlutterMacOS
- flutter_secure_storage_macos (3.3.1):
@ -24,7 +20,7 @@ PODS:
- FlutterMacOS
- flutter_webrtc (0.7.1):
- FlutterMacOS
- WebRTC-SDK (= 97.4692.02)
- WebRTC-SDK (= 97.4692.05)
- FlutterMacOS (1.0.0)
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
@ -33,13 +29,15 @@ PODS:
- FlutterMacOS
- just_audio (0.0.1):
- FlutterMacOS
- package_info (0.0.1):
- FlutterMacOS
- package_info_plus_macos (0.0.1):
- FlutterMacOS
- path_provider_macos (0.0.1):
- FlutterMacOS
- ReachabilitySwift (5.0.0)
- record_macos (1.0.0):
- FlutterMacOS
- share_plus_macos (0.0.1):
- FlutterMacOS
- shared_preferences_macos (0.0.1):
- FlutterMacOS
- sqflite (0.0.2):
@ -51,7 +49,7 @@ PODS:
- FlutterMacOS
- wakelock_macos (0.0.1):
- FlutterMacOS
- WebRTC-SDK (97.4692.02)
- WebRTC-SDK (97.4692.05)
DEPENDENCIES:
- audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`)
@ -60,8 +58,6 @@ DEPENDENCIES:
- desktop_lifecycle (from `Flutter/ephemeral/.symlinks/plugins/desktop_lifecycle/macos`)
- device_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus_macos/macos`)
- emoji_picker_flutter (from `Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- flutter_app_badger (from `Flutter/ephemeral/.symlinks/plugins/flutter_app_badger/macos`)
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- flutter_web_auth (from `Flutter/ephemeral/.symlinks/plugins/flutter_web_auth/macos`)
@ -69,9 +65,10 @@ DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/macos`)
- just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/macos`)
- package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
- shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
@ -97,10 +94,6 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus_macos/macos
emoji_picker_flutter:
:path: Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos
file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
flutter_app_badger:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_app_badger/macos
flutter_local_notifications:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
flutter_secure_storage_macos:
@ -115,12 +108,14 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/macos
just_audio:
:path: Flutter/ephemeral/.symlinks/plugins/just_audio/macos
package_info:
:path: Flutter/ephemeral/.symlinks/plugins/package_info/macos
package_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
record_macos:
:path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos
share_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
shared_preferences_macos:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos
sqflite:
@ -139,26 +134,25 @@ SPEC CHECKSUMS:
desktop_lifecycle: a600c10e12fe033c7be9078f2e929b8241f2c1e3
device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7
emoji_picker_flutter: 533634326b1c5de9a181ba14b9758e6dfe967a20
file_selector_macos: ff6dc948d4ddd34e8602a1f60b7d0b4cc6051a47
flutter_app_badger: 55a64b179f8438e89d574320c77b306e327a1730
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
flutter_secure_storage_macos: 6ceee8fbc7f484553ad17f79361b556259df89aa
flutter_web_auth: ae2c29ca9b98c00b4e0e8c0919bb4a05d44b76df
flutter_webrtc: 238124d0a7ba1c43543791f31a92a672370497c2
flutter_webrtc: 37c4efd66d9d306878c1323d5ac5a1d10c748b3a
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
geolocator_apple: 821be05bbdb1b49500e029ebcbf2d6acf2dfb966
just_audio: 9b67ca7b97c61cfc9784ea23cd8cc55eb226d489
package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
record_macos: dcf4f2bb654970437e012521cb4ea1fca4f78bb9
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
video_compress: c896234f100791b5fef7f049afa38f6d2ef7b42f
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
WebRTC-SDK: dda4e50186f9eed672dc6bcf4faafb30c6ce48e3
WebRTC-SDK: a6ee40bda0e3f7dba057907c3897374005c5715b
PODFILE CHECKSUM: 9b8d08a513b178c33212d1b54cc9e3cba756d95b

View File

@ -268,17 +268,16 @@
"${BUILT_PRODUCTS_DIR}/desktop_lifecycle/desktop_lifecycle.framework",
"${BUILT_PRODUCTS_DIR}/device_info_plus_macos/device_info_plus_macos.framework",
"${BUILT_PRODUCTS_DIR}/emoji_picker_flutter/emoji_picker_flutter.framework",
"${BUILT_PRODUCTS_DIR}/file_selector_macos/file_selector_macos.framework",
"${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework",
"${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework",
"${BUILT_PRODUCTS_DIR}/flutter_secure_storage_macos/flutter_secure_storage_macos.framework",
"${BUILT_PRODUCTS_DIR}/flutter_web_auth/flutter_web_auth.framework",
"${BUILT_PRODUCTS_DIR}/flutter_webrtc/flutter_webrtc.framework",
"${BUILT_PRODUCTS_DIR}/geolocator_apple/geolocator_apple.framework",
"${BUILT_PRODUCTS_DIR}/just_audio/just_audio.framework",
"${BUILT_PRODUCTS_DIR}/package_info/package_info.framework",
"${BUILT_PRODUCTS_DIR}/package_info_plus_macos/package_info_plus_macos.framework",
"${BUILT_PRODUCTS_DIR}/path_provider_macos/path_provider_macos.framework",
"${BUILT_PRODUCTS_DIR}/record_macos/record_macos.framework",
"${BUILT_PRODUCTS_DIR}/share_plus_macos/share_plus_macos.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences_macos/shared_preferences_macos.framework",
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher_macos/url_launcher_macos.framework",
@ -296,17 +295,16 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/desktop_lifecycle.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/emoji_picker_flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_selector_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_web_auth.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_webrtc.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/geolocator_apple.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/just_audio.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/record_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_macos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_macos.framework",

View File

@ -1423,13 +1423,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.27.3"
salomon_bottom_bar:
dependency: "direct main"
description:
name: salomon_bottom_bar
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.0"
scroll_to_index:
dependency: "direct main"
description:

View File

@ -71,7 +71,6 @@ dependencies:
qr_flutter: ^4.0.0
receive_sharing_intent: ^1.4.5
record: ^4.1.1
salomon_bottom_bar: ^3.2.0
scroll_to_index: ^3.0.1
sentry: ^6.3.0
share_plus: ^4.0.9