Initial commit
This commit is contained in:
68
lib/config/app_config.dart
Normal file
68
lib/config/app_config.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
abstract class AppConfig {
|
||||
static const Color primaryColor = Color(0xFF00D084);
|
||||
|
||||
static const Color chatColor = primaryColor;
|
||||
static const double messageFontSize = 16.0;
|
||||
static const bool allowOtherHomeservers = true;
|
||||
static const bool enableRegistration = true;
|
||||
static const bool hideTypingUsernames = false;
|
||||
|
||||
static const String inviteLinkPrefix = 'https://matrix.to/#/';
|
||||
static const String deepLinkPrefix = 'im.tatuchat://chat/';
|
||||
static const String schemePrefix = 'matrix:';
|
||||
static const String pushNotificationsChannelId = 'tatuchat_push';
|
||||
static const String pushNotificationsAppId = 'chat.fluffy.tatuchat';
|
||||
static const double borderRadius = 18.0;
|
||||
static const double spaceBorderRadius = 11.0;
|
||||
static const double columnWidth = 360.0;
|
||||
|
||||
static const String enablePushTutorial =
|
||||
'https://ko-fi.com/post/How-can-I-get-Push-Notifications-without-Google-N7Q825URG6?fromEditor=true';
|
||||
static const String encryptionTutorial =
|
||||
'https://ko-fi.com/post/How-to-use-end-to-end-encryption-in-FluffyChat-A5O725WDR5';
|
||||
static const String howDoIGetStickersTutorial =
|
||||
'https://ko-fi.com/post/How-to-add-a-sticker-pack-to-FluffyChat-N4N01OXATI';
|
||||
static const String appId = 'im.tatuchat.TatuChat';
|
||||
static const String appOpenUrlScheme = 'im.tatuchat';
|
||||
static const String appSsoUrlScheme = 'im.tatuchat.auth';
|
||||
|
||||
static const String sourceCodeUrl =
|
||||
'https://github.com/krille-chan/fluffychat';
|
||||
static const String supportUrl =
|
||||
'https://github.com/krille-chan/fluffychat/issues';
|
||||
static const String changelogUrl = 'https://fluffychat.im/changelog/';
|
||||
static const String helpUrl =
|
||||
'https://ko-fi.com/post/How-can-I-support-FluffyChat-J2G325WE6I';
|
||||
|
||||
static const Set<String> defaultReactions = {'👍', '❤️', '😂', '😮', '😢'};
|
||||
|
||||
static final Uri newIssueUrl = Uri(
|
||||
scheme: 'https',
|
||||
host: 'github.com',
|
||||
path: '/krille-chan/fluffychat/issues/new',
|
||||
);
|
||||
|
||||
static final Uri homeserverList = Uri(
|
||||
scheme: 'https',
|
||||
host: 'raw.githubusercontent.com',
|
||||
path: 'krille-chan/fluffychat/refs/heads/main/recommended_homeservers.json',
|
||||
);
|
||||
|
||||
static final Uri crashReportEndpoint = Uri(
|
||||
scheme: 'https',
|
||||
host: 'crash.fluffy.chat',
|
||||
);
|
||||
|
||||
static const String mainIsolatePortName = 'main_isolate';
|
||||
static const String pushIsolatePortName = 'push_isolate';
|
||||
static const String pushHelperCrashReportKey = 'push_helper_crash_report';
|
||||
|
||||
static const String vodozemacVersion = '0.8.1';
|
||||
}
|
||||
37
lib/config/isrg_x1.dart
Normal file
37
lib/config/isrg_x1.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
// ignore: constant_identifier_names
|
||||
const String ISRG_X1 = '''-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----''';
|
||||
20
lib/config/isrg_x2.dart
Normal file
20
lib/config/isrg_x2.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
// ignore: constant_identifier_names
|
||||
const String ISRG_X2 = '''-----BEGIN CERTIFICATE-----
|
||||
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
|
||||
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
|
||||
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
|
||||
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
|
||||
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
|
||||
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
|
||||
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
|
||||
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
|
||||
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
|
||||
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
|
||||
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
|
||||
/q4AaOeMSQ+2b1tbFfLn
|
||||
-----END CERTIFICATE-----''';
|
||||
551
lib/config/routes.dart
Normal file
551
lib/config/routes.dart
Normal file
@@ -0,0 +1,551 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:tatchat/config/themes.dart';
|
||||
import 'package:tatchat/pages/archive/archive.dart';
|
||||
import 'package:tatchat/pages/bootstrap/bootstrap_page.dart';
|
||||
import 'package:tatchat/pages/chat/chat.dart';
|
||||
import 'package:tatchat/pages/chat_access_settings/chat_access_settings_controller.dart';
|
||||
import 'package:tatchat/pages/chat_details/chat_details.dart';
|
||||
import 'package:tatchat/pages/chat_encryption_settings/chat_encryption_settings.dart';
|
||||
import 'package:tatchat/pages/chat_list/chat_list.dart';
|
||||
import 'package:tatchat/pages/chat_members/chat_members.dart';
|
||||
import 'package:tatchat/pages/chat_permissions_settings/chat_permissions_settings.dart';
|
||||
import 'package:tatchat/pages/chat_search/chat_search_page.dart';
|
||||
import 'package:tatchat/pages/device_settings/device_settings.dart';
|
||||
import 'package:tatchat/pages/intro/intro_page_presenter.dart';
|
||||
import 'package:tatchat/pages/invitation_selection/invitation_selection.dart';
|
||||
import 'package:tatchat/pages/login/login.dart';
|
||||
import 'package:tatchat/pages/new_group/new_group.dart';
|
||||
import 'package:tatchat/pages/new_private_chat/new_private_chat.dart';
|
||||
import 'package:tatchat/pages/register/registration.dart';
|
||||
import 'package:tatchat/pages/settings/settings.dart';
|
||||
import 'package:tatchat/pages/settings_3pid/settings_3pid.dart';
|
||||
import 'package:tatchat/pages/settings_chat/settings_chat.dart';
|
||||
import 'package:tatchat/pages/settings_emotes/settings_emotes.dart';
|
||||
import 'package:tatchat/pages/settings_homeserver/settings_homeserver.dart';
|
||||
import 'package:tatchat/pages/settings_ignore_list/settings_ignore_list.dart';
|
||||
import 'package:tatchat/pages/settings_notifications/settings_notifications.dart';
|
||||
import 'package:tatchat/pages/settings_password/settings_password.dart';
|
||||
import 'package:tatchat/pages/settings_security/settings_security.dart';
|
||||
import 'package:tatchat/pages/settings_style/settings_style.dart';
|
||||
import 'package:tatchat/pages/sign_in/sign_in_page.dart';
|
||||
import 'package:tatchat/widgets/config_viewer.dart';
|
||||
import 'package:tatchat/widgets/layouts/empty_page.dart';
|
||||
import 'package:tatchat/widgets/layouts/two_column_layout.dart';
|
||||
import 'package:tatchat/widgets/log_view.dart';
|
||||
import 'package:tatchat/widgets/matrix.dart';
|
||||
import 'package:tatchat/widgets/share_scaffold_dialog.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
abstract class AppRoutes {
|
||||
static FutureOr<String?> loggedInRedirect(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
) => Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||
? '/rooms'
|
||||
: null;
|
||||
|
||||
static FutureOr<String?> loggedOutRedirect(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
) => Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||
? null
|
||||
: '/home';
|
||||
|
||||
AppRoutes();
|
||||
|
||||
static final List<RouteBase> routes = [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
redirect: (context, state) =>
|
||||
Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||
? '/rooms'
|
||||
: '/home',
|
||||
),
|
||||
GoRoute(
|
||||
path: '/home',
|
||||
pageBuilder: (context, state) =>
|
||||
defaultPageBuilder(context, state, const IntroPagePresenter()),
|
||||
redirect: loggedInRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'sign_in',
|
||||
pageBuilder: (context, state) =>
|
||||
defaultPageBuilder(context, state, SignInPage(signUp: false)),
|
||||
redirect: loggedInRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'sign_up',
|
||||
pageBuilder: (context, state) =>
|
||||
defaultPageBuilder(context, state, SignInPage(signUp: true)),
|
||||
redirect: loggedInRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'login',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
Login(client: state.extra as Client),
|
||||
),
|
||||
redirect: loggedInRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'register',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
RegistrationPage(client: state.extra as Client),
|
||||
),
|
||||
redirect: loggedInRedirect,
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/logs',
|
||||
pageBuilder: (context, state) =>
|
||||
defaultPageBuilder(context, state, const LogViewer()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/configs',
|
||||
pageBuilder: (context, state) =>
|
||||
defaultPageBuilder(context, state, const ConfigViewer()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/backup',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
BootstrapPage(reset: state.uri.queryParameters['reset'] == 'true'),
|
||||
),
|
||||
),
|
||||
ShellRoute(
|
||||
// Never use a transition on the shell route. Changing the PageBuilder
|
||||
// here based on a MediaQuery causes the child to briefly be rendered
|
||||
// twice with the same GlobalKey, blowing up the rendering.
|
||||
pageBuilder: (context, state, child) => noTransitionPageBuilder(
|
||||
context,
|
||||
state,
|
||||
FluffyThemes.isColumnMode(context) &&
|
||||
state.fullPath?.startsWith('/rooms/settings') == false
|
||||
? TwoColumnLayout(
|
||||
mainView: ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
activeSpace: state.uri.queryParameters['spaceId'],
|
||||
displayNavigationRail:
|
||||
state.path?.startsWith('/rooms/settings') != true,
|
||||
),
|
||||
sideView: child,
|
||||
)
|
||||
: child,
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/rooms',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
FluffyThemes.isColumnMode(context)
|
||||
? const EmptyPage()
|
||||
: ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
activeSpace: state.uri.queryParameters['spaceId'],
|
||||
),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'archive',
|
||||
pageBuilder: (context, state) =>
|
||||
defaultPageBuilder(context, state, const Archive()),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':roomid',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatPage(
|
||||
key: ValueKey(state.uri),
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
eventId: state.uri.queryParameters['event'],
|
||||
timeline: state.extra as Timeline?,
|
||||
action: state.uri.queryParameters['action'],
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
],
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'newprivatechat',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
NewPrivateChat(
|
||||
key: ValueKey('new_chat_${state.uri.fragment}'),
|
||||
deeplink: state.uri.fragment,
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'newgroup',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
NewGroup(spaceId: state.uri.queryParameters['space_id']),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'newspace',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
NewGroup(
|
||||
createGroupType: CreateGroupType.space,
|
||||
spaceId: state.uri.queryParameters['space_id'],
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
ShellRoute(
|
||||
pageBuilder: (context, state, child) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
FluffyThemes.isColumnMode(context)
|
||||
? TwoColumnLayout(
|
||||
mainView: Settings(key: state.pageKey),
|
||||
sideView: child,
|
||||
hasNavigationRail: false,
|
||||
)
|
||||
: child,
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'settings',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
FluffyThemes.isColumnMode(context)
|
||||
? const EmptyPage()
|
||||
: const Settings(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'notifications',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SettingsNotifications(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'style',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SettingsStyle(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'devices',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const DevicesSettings(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chat',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SettingsChat(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'emotes',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
EmotesSettings(
|
||||
roomId: state.pathParameters['roomid'],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'addaccount',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const IntroPagePresenter(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'sign_in',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
SignInPage(signUp: false),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'sign_up',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
SignInPage(signUp: true),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'login',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
Login(client: state.extra as Client),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'register',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
RegistrationPage(client: state.extra as Client),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: 'homeserver',
|
||||
pageBuilder: (context, state) {
|
||||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SettingsHomeserver(),
|
||||
);
|
||||
},
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'security',
|
||||
redirect: loggedOutRedirect,
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SettingsSecurity(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'password',
|
||||
pageBuilder: (context, state) {
|
||||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SettingsPassword(),
|
||||
);
|
||||
},
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'ignorelist',
|
||||
pageBuilder: (context, state) {
|
||||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
SettingsIgnoreList(
|
||||
initialUserId: state.extra?.toString(),
|
||||
),
|
||||
);
|
||||
},
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: '3pid',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const Settings3Pid(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: ':roomid',
|
||||
pageBuilder: (context, state) {
|
||||
final body = state.uri.queryParameters['body'];
|
||||
var shareItems = state.extra is List<ShareItem>
|
||||
? state.extra as List<ShareItem>
|
||||
: null;
|
||||
if (body != null && body.isNotEmpty) {
|
||||
shareItems ??= [];
|
||||
shareItems.add(TextShareItem(body));
|
||||
}
|
||||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatPage(
|
||||
key: ValueKey(state.uri),
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
shareItems: shareItems,
|
||||
eventId: state.uri.queryParameters['event'],
|
||||
action: state.uri.queryParameters['action'],
|
||||
),
|
||||
);
|
||||
},
|
||||
redirect: loggedOutRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'search',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatSearchPage(roomId: state.pathParameters['roomid']!),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'encryption',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const ChatEncryptionSettings(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'invite',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
InvitationSelection(
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'details',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatDetails(roomId: state.pathParameters['roomid']!),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'access',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatAccessSettings(
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'members',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatMembersPage(
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'permissions',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const ChatPermissionsSettings(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'invite',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
InvitationSelection(
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'emotes',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
EmotesSettings(roomId: state.pathParameters['roomid']),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
],
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
static Page noTransitionPageBuilder(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
Widget child,
|
||||
) => NoTransitionPage(
|
||||
key: state.pageKey,
|
||||
restorationId: state.pageKey.value,
|
||||
child: child,
|
||||
);
|
||||
|
||||
static Page defaultPageBuilder(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
Widget child,
|
||||
) {
|
||||
final clientName = state.uri.queryParameters['client'];
|
||||
if (clientName != null) {
|
||||
final matrix = Matrix.of(context);
|
||||
final client = matrix.getClientByName(clientName);
|
||||
if (client != null) matrix.setActiveClient(client);
|
||||
}
|
||||
return FluffyThemes.isColumnMode(context)
|
||||
? noTransitionPageBuilder(context, state, child)
|
||||
: MaterialPage(
|
||||
key: state.pageKey,
|
||||
restorationId: state.pageKey.value,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
264
lib/config/setting_keys.dart
Normal file
264
lib/config/setting_keys.dart
Normal file
@@ -0,0 +1,264 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:tatchat/utils/platform_infos.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:managed_configurations/managed_configurations.dart';
|
||||
import 'package:matrix/matrix_api_lite/utils/logs.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
enum AppSettings<T> {
|
||||
textMessageMaxLength<int>('textMessageMaxLength', 16384),
|
||||
|
||||
/// Max lines for unselected HTML/text bubbles; 0 = unlimited (no fade).
|
||||
messagePreviewMaxLines<int>('chat.fluffy.message_preview_max_lines', 50),
|
||||
audioRecordingNumChannels<int>('audioRecordingNumChannels', 1),
|
||||
audioRecordingAutoGain<bool>('audioRecordingAutoGain', true),
|
||||
audioRecordingEchoCancel<bool>('audioRecordingEchoCancel', false),
|
||||
audioRecordingNoiseSuppress<bool>('audioRecordingNoiseSuppress', true),
|
||||
audioRecordingBitRate<int>('audioRecordingBitRate', 64000),
|
||||
audioRecordingSamplingRate<int>('audioRecordingSamplingRate', 44100),
|
||||
showNoGoogle<bool>('chat.fluffy.show_no_google', false),
|
||||
unifiedPushRegistered<bool>('chat.fluffy.unifiedpush.registered', false),
|
||||
unifiedPushEndpoint<String>('chat.fluffy.unifiedpush.endpoint', ''),
|
||||
pushNotificationsGatewayUrl<String>(
|
||||
'pushNotificationsGatewayUrl',
|
||||
'https://push.fluffychat.im/_matrix/push/v1/notify',
|
||||
),
|
||||
pushNotificationsPusherFormat<String>(
|
||||
'pushNotificationsPusherFormat',
|
||||
'event_id_only',
|
||||
),
|
||||
renderHtml<bool>('chat.fluffy.renderHtml', true),
|
||||
fontSizeFactor<double>('chat.fluffy.font_size_factor', 1.0),
|
||||
hideRedactedEvents<bool>('chat.fluffy.hideRedactedEvents', false),
|
||||
hideUnknownEvents<bool>('chat.fluffy.hideUnknownEvents', true),
|
||||
autoplayImages<bool>('chat.fluffy.autoplay_images', true),
|
||||
sendTypingNotifications<bool>('chat.fluffy.send_typing_notifications', true),
|
||||
sendPublicReadReceipts<bool>('chat.fluffy.send_public_read_receipts', true),
|
||||
swipeRightToLeftToReply<bool>('chat.fluffy.swipeRightToLeftToReply', true),
|
||||
sendOnEnter<bool>('chat.fluffy.send_on_enter', false),
|
||||
displayNavigationRail<bool>('chat.fluffy.display_navigation_rail', false),
|
||||
shareKeysWith<String>('chat.fluffy.share_keys_with_2', 'all'),
|
||||
noEncryptionWarningShown<bool>(
|
||||
'chat.fluffy.no_encryption_warning_shown',
|
||||
false,
|
||||
),
|
||||
displayChatDetailsColumn('chat.fluffy.display_chat_details_column', false),
|
||||
// AppConfig-mirrored settings
|
||||
applicationName<String>('chat.fluffy.application_name', 'TatuChat'),
|
||||
defaultHomeserver<String>(
|
||||
'chat.fluffy.default_homeserver',
|
||||
'https://matrix.weltonmoura.com.br',
|
||||
),
|
||||
// colorSchemeSeed stored as ARGB int
|
||||
colorSchemeSeedInt<int>('chat.fluffy.color_scheme_seed', 0xFF00D084),
|
||||
emojiSuggestionLocale<String>('emoji_suggestion_locale', ''),
|
||||
enableSoftLogout<bool>('chat.fluffy.enable_soft_logout', true),
|
||||
enableMatrixNativeOIDC<bool>('chat.fluffy.enable_matrix_native_oidc', true),
|
||||
presetHomeserver<String>(
|
||||
'chat.fluffy.preset_homeserver',
|
||||
'https://matrix.weltonmoura.com.br',
|
||||
),
|
||||
welcomeText<String>('chat.fluffy.welcome_text', ''),
|
||||
website<String>('chat.fluffy.website_url', 'https://fluffychat.im'),
|
||||
logoUrl<String>(
|
||||
'chat.fluffy.logo_url',
|
||||
'https://fluffychat.im/assets/favicon.png',
|
||||
),
|
||||
privacyPolicy<String>(
|
||||
'chat.fluffy.privacy_policy_url',
|
||||
'https://fluffychat.im/privacy',
|
||||
),
|
||||
tos<String>('chat.fluffy.tos_url', 'https://fluffychat.im/tos'),
|
||||
sendTimelineEventTimeout<int>('chat.fluffy.send_timeline_event_timeout', 15),
|
||||
webNotificationSound<bool>('chat.fluffy.web_notification_sound', true),
|
||||
chatFilter<String>('chat.fluffy.chat_filter', 'allChats'),
|
||||
hideRoomsInSpaces<bool>('chat.fluffy.hideRoomsInSpaces', false),
|
||||
showThumbnailsInTimeline<bool>('chat.fluffy.showThumbnailsInTimeline', true),
|
||||
doubleTapToReact<bool>('chat.fluffy.double_tap_to_react', false),
|
||||
doubleTapReaction<String>('chat.fluffy.double_tap_reaction', '❤️'),
|
||||
benchmarksInLogs<bool>('chat.fluffy.benchmarks_in_logs', false),
|
||||
autoSendErrorReports<bool?>('chat.fluffy.auto_send_eror_reports', null),
|
||||
knownErrorHashes<List<String>>('chat.fluffy.known_crash_hashes', []),
|
||||
customLiveKitInstance<String>('chat.fluffy.custom_live_kit_instance', '');
|
||||
|
||||
final String key;
|
||||
final T _defaultValue;
|
||||
|
||||
const AppSettings(this.key, this._defaultValue);
|
||||
|
||||
static SharedPreferences get store => _store!;
|
||||
static SharedPreferences? _store;
|
||||
|
||||
static Map<String, Object?>? _platformConfiguration;
|
||||
|
||||
T get defaultValue {
|
||||
final platformConfig = _platformConfiguration?[name];
|
||||
if (platformConfig is T) return platformConfig;
|
||||
return _defaultValue;
|
||||
}
|
||||
|
||||
static Future<void> reset({bool loadWebConfigFile = true}) async {
|
||||
await AppSettings._store!.clear();
|
||||
await init(loadWebConfigFile: loadWebConfigFile);
|
||||
}
|
||||
|
||||
static Future<SharedPreferences> init({bool loadWebConfigFile = true}) async {
|
||||
if (AppSettings._store != null) return AppSettings.store;
|
||||
|
||||
final store = AppSettings._store = await SharedPreferences.getInstance();
|
||||
|
||||
// Migrate wrong datatype for fontSizeFactor
|
||||
final fontSizeFactorString = Result(
|
||||
() => store.getString(AppSettings.fontSizeFactor.key),
|
||||
).asValue?.value;
|
||||
if (fontSizeFactorString != null) {
|
||||
Logs().i('Migrate wrong datatype for fontSizeFactor!');
|
||||
await store.remove(AppSettings.fontSizeFactor.key);
|
||||
final fontSizeFactor = double.tryParse(fontSizeFactorString);
|
||||
if (fontSizeFactor != null) {
|
||||
await store.setDouble(AppSettings.fontSizeFactor.key, fontSizeFactor);
|
||||
}
|
||||
}
|
||||
|
||||
if (store.getBool(AppSettings.sendOnEnter.key) == null) {
|
||||
await store.setBool(AppSettings.sendOnEnter.key, !PlatformInfos.isMobile);
|
||||
}
|
||||
if (store.getBool(AppSettings.doubleTapToReact.key) == null) {
|
||||
await store.setBool(
|
||||
AppSettings.doubleTapToReact.key,
|
||||
PlatformInfos.isMobile,
|
||||
);
|
||||
}
|
||||
|
||||
// Load configuration from config.json file or MDM:
|
||||
if (PlatformInfos.isMobile) {
|
||||
_platformConfiguration =
|
||||
await ManagedConfigurations().getManagedConfigurations;
|
||||
} else if (kIsWeb && loadWebConfigFile) {
|
||||
try {
|
||||
final configJsonString = utf8.decode(
|
||||
(await http.get(Uri.parse('config.json'))).bodyBytes,
|
||||
);
|
||||
_platformConfiguration =
|
||||
json.decode(configJsonString) as Map<String, Object?>;
|
||||
} on FormatException catch (_) {
|
||||
Logs().v('[ConfigLoader] config.json not found');
|
||||
} catch (e) {
|
||||
Logs().v('[ConfigLoader] config.json not found', e);
|
||||
}
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
extension AppSettingsBoolNExtension on AppSettings<bool?> {
|
||||
bool? get value {
|
||||
final value = Result(() => AppSettings.store.getBool(key));
|
||||
final error = value.asError;
|
||||
if (error != null) {
|
||||
Logs().e(
|
||||
'Unable to fetch $key from storage. Removing entry...',
|
||||
error.error,
|
||||
error.stackTrace,
|
||||
);
|
||||
}
|
||||
return value.asValue?.value;
|
||||
}
|
||||
|
||||
Future<void> setItem(bool value) => AppSettings.store.setBool(key, value);
|
||||
}
|
||||
|
||||
extension AppSettingsBoolExtension on AppSettings<bool> {
|
||||
bool get value {
|
||||
final value = Result(() => AppSettings.store.getBool(key));
|
||||
final error = value.asError;
|
||||
if (error != null) {
|
||||
Logs().e(
|
||||
'Unable to fetch $key from storage. Removing entry...',
|
||||
error.error,
|
||||
error.stackTrace,
|
||||
);
|
||||
}
|
||||
return value.asValue?.value ?? defaultValue;
|
||||
}
|
||||
|
||||
Future<void> setItem(bool value) => AppSettings.store.setBool(key, value);
|
||||
}
|
||||
|
||||
extension AppSettingsStringExtension on AppSettings<String> {
|
||||
String get value {
|
||||
final value = Result(() => AppSettings.store.getString(key));
|
||||
final error = value.asError;
|
||||
if (error != null) {
|
||||
Logs().e(
|
||||
'Unable to fetch $key from storage. Removing entry...',
|
||||
error.error,
|
||||
error.stackTrace,
|
||||
);
|
||||
}
|
||||
return value.asValue?.value ?? defaultValue;
|
||||
}
|
||||
|
||||
Future<void> setItem(String value) => AppSettings.store.setString(key, value);
|
||||
}
|
||||
|
||||
extension AppSettingsIntExtension on AppSettings<int> {
|
||||
int get value {
|
||||
final value = Result(() => AppSettings.store.getInt(key));
|
||||
final error = value.asError;
|
||||
if (error != null) {
|
||||
Logs().e(
|
||||
'Unable to fetch $key from storage. Removing entry...',
|
||||
error.error,
|
||||
error.stackTrace,
|
||||
);
|
||||
}
|
||||
return value.asValue?.value ?? defaultValue;
|
||||
}
|
||||
|
||||
Future<void> setItem(int value) => AppSettings.store.setInt(key, value);
|
||||
}
|
||||
|
||||
extension AppSettingsDoubleExtension on AppSettings<double> {
|
||||
double get value {
|
||||
final value = Result(() => AppSettings.store.getDouble(key));
|
||||
final error = value.asError;
|
||||
if (error != null) {
|
||||
Logs().e(
|
||||
'Unable to fetch $key from storage. Removing entry...',
|
||||
error.error,
|
||||
error.stackTrace,
|
||||
);
|
||||
}
|
||||
return value.asValue?.value ?? defaultValue;
|
||||
}
|
||||
|
||||
Future<void> setItem(double value) => AppSettings.store.setDouble(key, value);
|
||||
}
|
||||
|
||||
extension AppSettingsStringListExtension on AppSettings<List<String>> {
|
||||
List<String> get value {
|
||||
final value = Result(() => AppSettings.store.getStringList(key));
|
||||
final error = value.asError;
|
||||
if (error != null) {
|
||||
Logs().e(
|
||||
'Unable to fetch $key from storage. Removing entry...',
|
||||
error.error,
|
||||
error.stackTrace,
|
||||
);
|
||||
}
|
||||
return value.asValue?.value ?? defaultValue;
|
||||
}
|
||||
|
||||
Future<void> setItem(List<String> value) =>
|
||||
AppSettings.store.setStringList(key, value);
|
||||
}
|
||||
212
lib/config/tatu_palette.dart
Normal file
212
lib/config/tatu_palette.dart
Normal file
@@ -0,0 +1,212 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// TatuChat brand palette, adapted between light and dark themes.
|
||||
///
|
||||
/// The colors below follow the brand identity:
|
||||
/// * Primary (brand): Emerald Green #00D084
|
||||
/// * Background: Deep Graphite #121519 (dark mode)
|
||||
/// * Surfaces / Cards: Slate Dark #1D222A
|
||||
/// * Accent: Electric Cyan #00E5FF
|
||||
/// * Text primary: Pure White / Ice #F4F6F8
|
||||
/// * Text secondary: Cool Gray #8A94A6
|
||||
/// * Alert / Error: Crimson Rose #FF4D6D
|
||||
abstract class TatuPalette {
|
||||
static const Color brandEmerald = Color(0xFF00D084);
|
||||
static const Color brandGraphite = Color(0xFF121519);
|
||||
static const Color brandSlate = Color(0xFF1D222A);
|
||||
static const Color brandCyan = Color(0xFF00E5FF);
|
||||
static const Color brandIce = Color(0xFFF4F6F8);
|
||||
static const Color brandCoolGray = Color(0xFF8A94A6);
|
||||
static const Color brandCrimson = Color(0xFFFF4D6D);
|
||||
|
||||
static const _Scheme _dark = _Scheme(
|
||||
primary: Color(0xFF00D084),
|
||||
onPrimary: Color(0xFF00281E),
|
||||
primaryContainer: Color(0xFF0E4133),
|
||||
onPrimaryContainer: Color(0xFFBDFFE2),
|
||||
secondary: Color(0xFF8A94A6),
|
||||
onSecondary: Color(0xFF141A24),
|
||||
secondaryContainer: Color(0xFF293240),
|
||||
onSecondaryContainer: Color(0xFFDBE2EE),
|
||||
tertiary: Color(0xFF00E5FF),
|
||||
onTertiary: Color(0xFF003B46),
|
||||
tertiaryContainer: Color(0xFF004A55),
|
||||
onTertiaryContainer: Color(0xFFA6F3FF),
|
||||
error: Color(0xFFFF4D6D),
|
||||
onError: Color(0xFF410013),
|
||||
errorContainer: Color(0xFF7F0024),
|
||||
onErrorContainer: Color(0xFFFFD9DF),
|
||||
surface: Color(0xFF121519),
|
||||
onSurface: Color(0xFFF4F6F8),
|
||||
onSurfaceVariant: Color(0xFF9BA5B6),
|
||||
outline: Color(0xFF6F7B8C),
|
||||
outlineVariant: Color(0xFF323B46),
|
||||
inverseSurface: Color(0xFFE8EBF0),
|
||||
onInverseSurface: Color(0xFF121519),
|
||||
inversePrimary: Color(0xFF008E5F),
|
||||
surfaceLowest: Color(0xFF0C0F13),
|
||||
surfaceLow: Color(0xFF161B21),
|
||||
surfaceContainer: Color(0xFF1D222A),
|
||||
surfaceHigh: Color(0xFF232A34),
|
||||
surfaceHighest: Color(0xFF2A323D),
|
||||
surfaceDim: Color(0xFF121519),
|
||||
surfaceBright: Color(0xFF3A414C),
|
||||
);
|
||||
|
||||
static const _Scheme _light = _Scheme(
|
||||
primary: Color(0xFF00D084),
|
||||
onPrimary: Color(0xFF00291F),
|
||||
primaryContainer: Color(0xFFBDFFE4),
|
||||
onPrimaryContainer: Color(0xFF003A28),
|
||||
secondary: Color(0xFF5F6B7D),
|
||||
onSecondary: Color(0xFFFFFFFF),
|
||||
secondaryContainer: Color(0xFFE2E7EF),
|
||||
onSecondaryContainer: Color(0xFF1C2430),
|
||||
tertiary: Color(0xFF00A6C4),
|
||||
onTertiary: Color(0xFF002A33),
|
||||
tertiaryContainer: Color(0xFFB7F1FF),
|
||||
onTertiaryContainer: Color(0xFF00343D),
|
||||
error: Color(0xFFFF4D6D),
|
||||
onError: Color(0xFF430013),
|
||||
errorContainer: Color(0xFFFFDADD),
|
||||
onErrorContainer: Color(0xFF4B0012),
|
||||
surface: Color(0xFFF5F7FA),
|
||||
onSurface: Color(0xFF121519),
|
||||
onSurfaceVariant: Color(0xFF5F6B7D),
|
||||
outline: Color(0xFF7A8595),
|
||||
outlineVariant: Color(0xFFCCD4DD),
|
||||
inverseSurface: Color(0xFF2C3440),
|
||||
onInverseSurface: Color(0xFFF4F6F8),
|
||||
inversePrimary: Color(0xFF2ED9A0),
|
||||
surfaceLowest: Color(0xFFFFFFFF),
|
||||
surfaceLow: Color(0xFFEDF0F4),
|
||||
surfaceContainer: Color(0xFFE8EBF0),
|
||||
surfaceHigh: Color(0xFFE2E6EC),
|
||||
surfaceHighest: Color(0xFFDDE1E7),
|
||||
surfaceDim: Color(0xFFE8ECF1),
|
||||
surfaceBright: Color(0xFFFBFCFE),
|
||||
);
|
||||
|
||||
/// Builds the ColorScheme for [brightness].
|
||||
///
|
||||
/// When a [seed] is provided (e.g. a color chosen by the user or a system
|
||||
/// dynamic color), its tonal values are used for the primary family while
|
||||
/// the brand surfaces, accent and error colors are preserved.
|
||||
static ColorScheme scheme(Brightness brightness, [Color? seed]) {
|
||||
final isDark = brightness == Brightness.dark;
|
||||
final p = isDark ? _dark : _light;
|
||||
final base = ColorScheme.fromSeed(
|
||||
brightness: brightness,
|
||||
seedColor: seed ?? p.primary,
|
||||
dynamicSchemeVariant: DynamicSchemeVariant.rainbow,
|
||||
);
|
||||
final useBrandPrimary = seed == null;
|
||||
return base.copyWith(
|
||||
primary: useBrandPrimary ? p.primary : null,
|
||||
onPrimary: useBrandPrimary ? p.onPrimary : null,
|
||||
primaryContainer: useBrandPrimary ? p.primaryContainer : null,
|
||||
onPrimaryContainer: useBrandPrimary ? p.onPrimaryContainer : null,
|
||||
surfaceTint: useBrandPrimary ? p.primary : null,
|
||||
secondary: p.secondary,
|
||||
onSecondary: p.onSecondary,
|
||||
secondaryContainer: p.secondaryContainer,
|
||||
onSecondaryContainer: p.onSecondaryContainer,
|
||||
tertiary: p.tertiary,
|
||||
onTertiary: p.onTertiary,
|
||||
tertiaryContainer: p.tertiaryContainer,
|
||||
onTertiaryContainer: p.onTertiaryContainer,
|
||||
error: p.error,
|
||||
onError: p.onError,
|
||||
errorContainer: p.errorContainer,
|
||||
onErrorContainer: p.onErrorContainer,
|
||||
surface: p.surface,
|
||||
onSurface: p.onSurface,
|
||||
onSurfaceVariant: p.onSurfaceVariant,
|
||||
outline: p.outline,
|
||||
outlineVariant: p.outlineVariant,
|
||||
inverseSurface: p.inverseSurface,
|
||||
onInverseSurface: p.onInverseSurface,
|
||||
inversePrimary: p.inversePrimary,
|
||||
surfaceContainerLowest: p.surfaceLowest,
|
||||
surfaceContainerLow: p.surfaceLow,
|
||||
surfaceContainer: p.surfaceContainer,
|
||||
surfaceContainerHigh: p.surfaceHigh,
|
||||
surfaceContainerHighest: p.surfaceHighest,
|
||||
surfaceDim: p.surfaceDim,
|
||||
surfaceBright: p.surfaceBright,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Scheme {
|
||||
const _Scheme({
|
||||
required this.primary,
|
||||
required this.onPrimary,
|
||||
required this.primaryContainer,
|
||||
required this.onPrimaryContainer,
|
||||
required this.secondary,
|
||||
required this.onSecondary,
|
||||
required this.secondaryContainer,
|
||||
required this.onSecondaryContainer,
|
||||
required this.tertiary,
|
||||
required this.onTertiary,
|
||||
required this.tertiaryContainer,
|
||||
required this.onTertiaryContainer,
|
||||
required this.error,
|
||||
required this.onError,
|
||||
required this.errorContainer,
|
||||
required this.onErrorContainer,
|
||||
required this.surface,
|
||||
required this.onSurface,
|
||||
required this.onSurfaceVariant,
|
||||
required this.outline,
|
||||
required this.outlineVariant,
|
||||
required this.inverseSurface,
|
||||
required this.onInverseSurface,
|
||||
required this.inversePrimary,
|
||||
required this.surfaceLowest,
|
||||
required this.surfaceLow,
|
||||
required this.surfaceContainer,
|
||||
required this.surfaceHigh,
|
||||
required this.surfaceHighest,
|
||||
required this.surfaceDim,
|
||||
required this.surfaceBright,
|
||||
});
|
||||
|
||||
final Color primary;
|
||||
final Color onPrimary;
|
||||
final Color primaryContainer;
|
||||
final Color onPrimaryContainer;
|
||||
final Color secondary;
|
||||
final Color onSecondary;
|
||||
final Color secondaryContainer;
|
||||
final Color onSecondaryContainer;
|
||||
final Color tertiary;
|
||||
final Color onTertiary;
|
||||
final Color tertiaryContainer;
|
||||
final Color onTertiaryContainer;
|
||||
final Color error;
|
||||
final Color onError;
|
||||
final Color errorContainer;
|
||||
final Color onErrorContainer;
|
||||
final Color surface;
|
||||
final Color onSurface;
|
||||
final Color onSurfaceVariant;
|
||||
final Color outline;
|
||||
final Color outlineVariant;
|
||||
final Color inverseSurface;
|
||||
final Color onInverseSurface;
|
||||
final Color inversePrimary;
|
||||
final Color surfaceLowest;
|
||||
final Color surfaceLow;
|
||||
final Color surfaceContainer;
|
||||
final Color surfaceHigh;
|
||||
final Color surfaceHighest;
|
||||
final Color surfaceDim;
|
||||
final Color surfaceBright;
|
||||
}
|
||||
147
lib/config/themes.dart
Normal file
147
lib/config/themes.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
|
||||
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import 'package:tatchat/config/app_config.dart';
|
||||
import 'package:tatchat/config/tatu_palette.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
abstract class FluffyThemes {
|
||||
static const double columnWidth = 380.0;
|
||||
|
||||
static const double maxTimelineWidth = columnWidth * 2;
|
||||
|
||||
static const double navRailWidth = 80.0;
|
||||
|
||||
static bool isColumnModeByWidth(double width) =>
|
||||
width > columnWidth * 2 + navRailWidth;
|
||||
|
||||
static bool isColumnMode(BuildContext context) =>
|
||||
isColumnModeByWidth(MediaQuery.sizeOf(context).width);
|
||||
|
||||
static bool isThreeColumnMode(BuildContext context) =>
|
||||
MediaQuery.sizeOf(context).width > FluffyThemes.columnWidth * 3.5;
|
||||
|
||||
static LinearGradient backgroundGradient(BuildContext context, int alpha) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
colors: [
|
||||
colorScheme.primaryContainer.withAlpha(alpha),
|
||||
colorScheme.secondaryContainer.withAlpha(alpha),
|
||||
colorScheme.tertiaryContainer.withAlpha(alpha),
|
||||
colorScheme.primaryContainer.withAlpha(alpha),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static const Duration animationDuration = Duration(milliseconds: 250);
|
||||
static const Curve animationCurve = Curves.easeInOut;
|
||||
|
||||
static ThemeData buildTheme(
|
||||
BuildContext context,
|
||||
Brightness brightness, [
|
||||
Color? seed,
|
||||
]) {
|
||||
final colorScheme = TatuPalette.scheme(brightness, seed);
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
final dividerColor = brightness == Brightness.dark
|
||||
? colorScheme.surfaceContainerHighest
|
||||
: colorScheme.surfaceContainer;
|
||||
return ThemeData(
|
||||
visualDensity: VisualDensity.standard,
|
||||
useMaterial3: true,
|
||||
brightness: brightness,
|
||||
colorScheme: colorScheme,
|
||||
dividerColor: dividerColor,
|
||||
segmentedButtonTheme: SegmentedButtonThemeData(
|
||||
style: SegmentedButton.styleFrom(
|
||||
iconColor: colorScheme.onSurface,
|
||||
disabledIconColor: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
selectionColor: colorScheme.onSurface.withAlpha(128),
|
||||
selectionHandleColor: colorScheme.secondary,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
showCheckmark: false,
|
||||
backgroundColor: colorScheme.surfaceContainer,
|
||||
side: BorderSide.none,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
toolbarHeight: isColumnMode ? 72 : 56,
|
||||
surfaceTintColor: isColumnMode ? colorScheme.surface : null,
|
||||
backgroundColor: isColumnMode ? colorScheme.surface : null,
|
||||
actionsPadding: isColumnMode
|
||||
? const EdgeInsets.symmetric(horizontal: 16.0)
|
||||
: null,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: brightness.reversed,
|
||||
statusBarBrightness: brightness,
|
||||
systemNavigationBarIconBrightness: brightness.reversed,
|
||||
systemNavigationBarColor: colorScheme.surface,
|
||||
),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(width: 1, color: colorScheme.primary),
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(color: colorScheme.primary),
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||
strokeCap: StrokeCap.round,
|
||||
color: colorScheme.primary,
|
||||
refreshBackgroundColor: colorScheme.primaryContainer,
|
||||
),
|
||||
snackBarTheme: isColumnMode
|
||||
? const SnackBarThemeData(
|
||||
showCloseIcon: true,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
width: FluffyThemes.columnWidth * 1.5,
|
||||
)
|
||||
: const SnackBarThemeData(behavior: SnackBarBehavior.floating),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: colorScheme.secondaryContainer,
|
||||
foregroundColor: colorScheme.onSecondaryContainer,
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.all(16),
|
||||
textStyle: const TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension on Brightness {
|
||||
Brightness get reversed =>
|
||||
this == Brightness.dark ? Brightness.light : Brightness.dark;
|
||||
}
|
||||
|
||||
extension BubbleColorTheme on ThemeData {
|
||||
Color get bubbleColor => brightness == Brightness.light
|
||||
? colorScheme.primary
|
||||
: colorScheme.primaryContainer;
|
||||
|
||||
Color get onBubbleColor => brightness == Brightness.light
|
||||
? colorScheme.onPrimary
|
||||
: colorScheme.onPrimaryContainer;
|
||||
|
||||
Color get secondaryBubbleColor => colorScheme.surfaceContainer;
|
||||
}
|
||||
Reference in New Issue
Block a user