unify handling of themes

accessible theming/high contrast support
This commit is contained in:
Christien Rioux 2024-04-12 20:55:05 -04:00
parent 23ec185324
commit 4f02435964
41 changed files with 958 additions and 622 deletions

View File

@ -6,7 +6,6 @@
"settings_tooltip": "Settings"
},
"pager": {
"account": "Account",
"chats": "Chats",
"contacts": "Contacts"
},
@ -67,6 +66,9 @@
"add_chat_sheet": {
"new_chat": "New Chat"
},
"chat": {
"say_something": "Say Something"
},
"create_invitation_dialog": {
"title": "Create Contact Invitation",
"connect_with_me": "Connect with me on VeilidChat!",

View File

@ -7,6 +7,7 @@ import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:go_router/go_router.dart';
import '../../../layout/default_app_bar.dart';
import '../../../theme/theme.dart';
import '../../../tools/tools.dart';
import '../../../veilid_processor/veilid_processor.dart';
import '../../account_manager.dart';

View File

@ -31,11 +31,14 @@ class ProfileWidget extends StatelessWidget {
child: Column(children: [
Text(
_profile.name,
style: textTheme.headlineSmall,
style: textTheme.headlineSmall!
.copyWith(color: scale.primaryScale.borderText),
textAlign: TextAlign.left,
).paddingAll(4),
if (_profile.pronouns.isNotEmpty)
Text(_profile.pronouns, style: textTheme.bodyMedium)
Text(_profile.pronouns,
style: textTheme.bodyMedium!
.copyWith(color: scale.primaryScale.borderText))
.paddingLTRB(4, 0, 4, 4),
]),
);

View File

@ -13,7 +13,6 @@ import '../../chat_list/chat_list.dart';
import '../../contacts/contacts.dart';
import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import '../chat.dart';
class ChatComponent extends StatelessWidget {
@ -173,11 +172,13 @@ class ChatComponent extends StatelessWidget {
16, 0, 16, 0),
child: Text(_remoteUser.firstName!,
textAlign: TextAlign.start,
style: textTheme.titleMedium),
style: textTheme.titleMedium!.copyWith(
color: scale.primaryScale.borderText)),
)),
const Spacer(),
IconButton(
icon: const Icon(Icons.close),
icon: Icon(Icons.close,
color: scale.primaryScale.borderText),
onPressed: () async {
context.read<ActiveChatCubit>().setActiveChat(null);
}).paddingLTRB(16, 0, 16, 0)

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../theme/theme.dart';
class EmptyChatWidget extends StatelessWidget {
const EmptyChatWidget({super.key});
@ -7,8 +10,11 @@ class EmptyChatWidget extends StatelessWidget {
// ignore: prefer_expression_function_bodies
Widget build(
BuildContext context,
) =>
Container(
) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
@ -19,16 +25,17 @@ class EmptyChatWidget extends StatelessWidget {
children: [
Icon(
Icons.chat,
color: Theme.of(context).disabledColor,
color: scale.primaryScale.subtleBorder,
size: 48,
),
Text(
'Say Something',
translate('chat.say_something'),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).disabledColor,
color: scale.primaryScale.subtleBorder,
),
),
],
),
);
}
}

View File

@ -4,12 +4,11 @@ import 'package:flutter/services.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../theme/theme.dart';
import '../../tools/tools.dart';
Widget newChatBottomSheetBuilder(
BuildContext sheetContext, BuildContext context) {
final theme = Theme.of(sheetContext);
final scale = theme.extension<ScaleScheme>()!;
//final theme = Theme.of(sheetContext);
//final scale = theme.extension<ScaleScheme>()!;
return KeyboardListener(
focusNode: FocusNode(),
@ -23,49 +22,10 @@ Widget newChatBottomSheetBuilder(
title: translate('add_chat_sheet.new_chat'),
child: SizedBox(
height: 160,
child: Row(
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'Group and custom chat functionality is not available yet')
// Column(children: [
// IconButton(
// onPressed: () async {
// Navigator.pop(sheetContext);
// await CreateInvitationDialog.show(context);
// },
// iconSize: 64,
// icon: const Icon(Icons.contact_page),
// color: scale.primaryScale.background),
// Text(
// translate('accounts_menu.create_invite'),
// )
// ]),
// Column(children: [
// IconButton(
// onPressed: () async {
// Navigator.pop(sheetContext);
// await ScanInvitationDialog.show(context);
// },
// iconSize: 64,
// icon: const Icon(Icons.qr_code_scanner),
// color: scale.primaryScale.background),
// Text(
// translate('accounts_menu.scan_invite'),
// )
// ]),
// Column(children: [
// IconButton(
// onPressed: () async {
// Navigator.pop(sheetContext);
// await PasteInvitationDialog.show(context);
// },
// iconSize: 64,
// icon: const Icon(Icons.paste),
// color: scale.primaryScale.background),
// Text(
// translate('accounts_menu.paste_invite'),
// )
// ])
]).paddingAll(16))));
}

View File

@ -1,8 +1,6 @@
import 'package:async_tools/async_tools.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../chat/cubits/active_chat_cubit.dart';
import '../../proto/proto.dart' as proto;
@ -25,85 +23,35 @@ class ChatSingleContactItemWidget extends StatelessWidget {
Widget build(
BuildContext context,
) {
final theme = Theme.of(context);
//final textTheme = theme.textTheme;
final scale = theme.extension<ScaleScheme>()!;
final activeChatCubit = context.watch<ActiveChatCubit>();
final remoteConversationRecordKey =
_contact.remoteConversationRecordKey.toVeilid();
final selected = activeChatCubit.state == remoteConversationRecordKey;
return Container(
margin: const EdgeInsets.fromLTRB(0, 4, 0, 0),
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: selected
? scale.primaryScale.activeElementBackground
: scale.primaryScale.hoverElementBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
)),
child: Slidable(
return SliderTile(
key: ObjectKey(_contact),
endActionPane: ActionPane(
motion: const DrawerMotion(),
children: [
SlidableAction(
onPressed: _disabled
? null
: (context) async {
final chatListCubit = context.read<ChatListCubit>();
await chatListCubit.deleteChat(
remoteConversationRecordKey:
remoteConversationRecordKey);
},
backgroundColor: scale.tertiaryScale.background,
foregroundColor: scale.tertiaryScale.foregroundText,
icon: Icons.delete,
label: translate('button.delete'),
padding: const EdgeInsets.all(2)),
// SlidableAction(
// onPressed: (context) => (),
// backgroundColor: scale.secondaryScale.background,
// foregroundColor: scale.secondaryScale.text,
// icon: Icons.edit,
// label: 'Edit',
// ),
],
),
// The child of the Slidable is what the user sees when the
// component is not dragged.
child: ListTile(
onTap: _disabled
? null
: () {
disabled: _disabled,
selected: selected,
tileScale: ScaleKind.secondary,
title: _contact.editedProfile.name,
subtitle: _contact.editedProfile.pronouns,
icon: Icons.chat,
onTap: () {
singleFuture(activeChatCubit, () async {
activeChatCubit
.setActiveChat(remoteConversationRecordKey);
activeChatCubit.setActiveChat(remoteConversationRecordKey);
});
},
title: Text(_contact.editedProfile.name),
/// xxx show last message here
subtitle: (_contact.editedProfile.pronouns.isNotEmpty)
? Text(_contact.editedProfile.pronouns)
: null,
iconColor: selected
? scale.primaryScale.appText
: scale.primaryScale.subtleText,
textColor: selected
? scale.primaryScale.appText
: scale.primaryScale.subtleText,
selectedColor: scale.primaryScale.appText,
//Text(Timestamp.fromInt64(contactInvitationRecord.expiration) / ),
leading: const Icon(Icons.chat))));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<proto.Contact>('contact', _contact));
endActions: [
SliderTileAction(
icon: Icons.delete,
label: translate('button.delete'),
actionScale: ScaleKind.tertiary,
onPressed: (context) async {
final chatListCubit = context.read<ChatListCubit>();
await chatListCubit.deleteChat(
remoteConversationRecordKey: remoteConversationRecordKey);
})
],
);
}
}

View File

@ -7,7 +7,7 @@ import 'package:searchable_listview/searchable_listview.dart';
import '../../contacts/contacts.dart';
import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart';
import '../../theme/theme.dart';
import '../chat_list.dart';
class ChatSingleContactListWidget extends StatelessWidget {
@ -42,7 +42,8 @@ class ChatSingleContactListWidget extends StatelessWidget {
}
return ChatSingleContactItemWidget(
contact: contact,
disabled: contactListV.busy);
disabled: contactListV.busy)
.paddingLTRB(0, 4, 0, 0);
},
filter: (value) {
final lowerValue = value.toLowerCase();

View File

@ -10,6 +10,7 @@ import 'package:flutter_translate/flutter_translate.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import '../contact_invitation.dart';

View File

@ -1,7 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
@ -28,80 +27,26 @@ class ContactInvitationItemWidget extends StatelessWidget {
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
final theme = Theme.of(context);
//final textTheme = theme.textTheme;
final scale = theme.extension<ScaleScheme>()!;
// final remoteConversationKey =
// contact.remoteConversationRecordKey.toVeilid();
return Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: scale.tertiaryScale.subtleBorder,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
)),
child: Slidable(
// Specify a key if the Slidable is dismissible.
const selected =
false; // xxx: eventually when we have selectable invitations:
// activeContactCubit.state == remoteConversationRecordKey;
final tileDisabled =
disabled || context.watch<ContactInvitationListCubit>().isBusy;
return SliderTile(
key: ObjectKey(contactInvitationRecord),
endActionPane: ActionPane(
// A motion is a widget used to control how the pane animates.
motion: const DrawerMotion(),
// A pane can dismiss the Slidable.
//dismissible: DismissiblePane(onDismissed: () {}),
// All actions are defined in the children parameter.
children: [
// A SlidableAction can have an icon and/or a label.
SlidableAction(
onPressed: disabled
? null
: (context) async {
final contactInvitationListCubit =
context.read<ContactInvitationListCubit>();
await contactInvitationListCubit.deleteInvitation(
accepted: false,
contactRequestInboxRecordKey:
contactInvitationRecord
.contactRequestInbox.recordKey
.toVeilid());
},
backgroundColor: scale.tertiaryScale.background,
foregroundColor: scale.tertiaryScale.appText,
icon: Icons.delete,
label: translate('button.delete'),
padding: const EdgeInsets.all(2)),
],
),
// startActionPane: ActionPane(
// motion: const DrawerMotion(),
// children: [
// SlidableAction(
// // An action can be bigger than the others.
// flex: 2,
// onPressed: (context) => (),
// backgroundColor: Color(0xFF7BC043),
// foregroundColor: Colors.white,
// icon: Icons.archive,
// label: 'Archive',
// ),
// SlidableAction(
// onPressed: (context) => (),
// backgroundColor: Color(0xFF0392CF),
// foregroundColor: Colors.white,
// icon: Icons.save,
// label: 'Save',
// ),
// ],
// ),
// The child of the Slidable is what the user sees when the
// component is not dragged.
child: ListTile(
//title: Text(translate('contact_list.invitation')),
onTap: disabled
? null
: () async {
disabled: tileDisabled,
selected: selected,
tileScale: ScaleKind.primary,
title: contactInvitationRecord.message.isEmpty
? translate('contact_list.invitation')
: contactInvitationRecord.message,
icon: Icons.person_add,
onTap: () async {
if (!context.mounted) {
return;
}
@ -109,18 +54,24 @@ class ContactInvitationItemWidget extends StatelessWidget {
context: context,
message: contactInvitationRecord.message,
create: (context) => InvitationGeneratorCubit.value(
Uint8List.fromList(
contactInvitationRecord.invitation)));
Uint8List.fromList(contactInvitationRecord.invitation)));
},
title: Text(
contactInvitationRecord.message.isEmpty
? translate('contact_list.invitation')
: contactInvitationRecord.message,
softWrap: true,
),
iconColor: scale.tertiaryScale.background,
textColor: scale.tertiaryScale.appText,
//Text(Timestamp.fromInt64(contactInvitationRecord.expiration) / ),
leading: const Icon(Icons.person_add))));
endActions: [
SliderTileAction(
icon: Icons.delete,
label: translate('button.delete'),
actionScale: ScaleKind.tertiary,
onPressed: (context) async {
final contactInvitationListCubit =
context.read<ContactInvitationListCubit>();
await contactInvitationListCubit.deleteInvitation(
accepted: false,
contactRequestInboxRecordKey: contactInvitationRecord
.contactRequestInbox.recordKey
.toVeilid());
},
)
],
);
}
}

View File

@ -10,6 +10,7 @@ import 'package:flutter_translate/flutter_translate.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import '../contact_invitation.dart';

View File

@ -9,6 +9,7 @@ import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../contacts/contacts.dart';
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import '../contact_invitation.dart';

View File

@ -4,7 +4,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import 'create_invitation_dialog.dart';
import 'paste_invitation_dialog.dart';
import 'scan_invitation_dialog.dart';
@ -37,7 +36,7 @@ Widget newContactBottomSheetBuilder(
},
iconSize: 64,
icon: const Icon(Icons.contact_page),
color: scale.primaryScale.background),
color: scale.primaryScale.hoverBorder),
Text(
translate('add_contact_sheet.create_invite'),
)
@ -50,7 +49,7 @@ Widget newContactBottomSheetBuilder(
},
iconSize: 64,
icon: const Icon(Icons.qr_code_scanner),
color: scale.primaryScale.background),
color: scale.primaryScale.hoverBorder),
Text(
translate('add_contact_sheet.scan_invite'),
)
@ -63,7 +62,7 @@ Widget newContactBottomSheetBuilder(
},
iconSize: 64,
icon: const Icon(Icons.paste),
color: scale.primaryScale.background),
color: scale.primaryScale.hoverBorder),
Text(
translate('add_contact_sheet.paste_invite'),
)

View File

@ -211,7 +211,7 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
scale.grayScale.subtleBackground);
case TorchState.on:
return Icon(Icons.flash_on,
color: scale.primaryScale.background);
color: scale.primaryScale.primary);
}
},
),
@ -258,8 +258,8 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
alignment: Alignment.topRight,
child: IconButton(
color: Colors.white,
icon: Icon(Icons.close,
color: scale.grayScale.background),
icon:
Icon(Icons.close, color: scale.grayScale.primary),
iconSize: 32,
onPressed: () => {
SchedulerBinding.instance

View File

@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../chat_list/chat_list.dart';
import '../../layout/layout.dart';
@ -17,106 +16,65 @@ class ContactItemWidget extends StatelessWidget {
final proto.Contact contact;
final bool disabled;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<proto.Contact>('contact', contact))
..add(DiagnosticsProperty<bool>('disabled', disabled));
}
@override
// ignore: prefer_expression_function_bodies
Widget build(
BuildContext context,
) {
final theme = Theme.of(context);
//final textTheme = theme.textTheme;
final scale = theme.extension<ScaleScheme>()!;
final remoteConversationKey =
contact.remoteConversationRecordKey.toVeilid();
const selected = false; // xxx: eventually when we have selectable contacts:
// activeContactCubit.state == remoteConversationRecordKey;
return Container(
margin: const EdgeInsets.fromLTRB(0, 4, 0, 0),
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: selected
// ignore: dead_code
? scale.primaryScale.activeElementBackground
: scale.primaryScale.hoverElementBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
)),
child: Slidable(
final tileDisabled = disabled || context.watch<ContactListCubit>().isBusy;
return SliderTile(
key: ObjectKey(contact),
endActionPane: ActionPane(
motion: const DrawerMotion(),
children: [
SlidableAction(
onPressed: disabled || context.watch<ChatListCubit>().isBusy
? null
: (context) async {
final contactListCubit =
context.read<ContactListCubit>();
final chatListCubit = context.read<ChatListCubit>();
// Remove any chats for this contact
await chatListCubit.deleteChat(
remoteConversationRecordKey:
remoteConversationKey);
// Delete the contact itself
await contactListCubit.deleteContact(
contact: contact);
},
backgroundColor: scale.tertiaryScale.background,
foregroundColor: scale.tertiaryScale.appText,
icon: Icons.delete,
label: translate('button.delete'),
padding: const EdgeInsets.all(2)),
// SlidableAction(
// onPressed: (context) => (),
// backgroundColor: scale.secondaryScale.background,
// foregroundColor: scale.secondaryScale.text,
// icon: Icons.edit,
// label: 'Edit',
// ),
],
),
// The child of the Slidable is what the user sees when the
// component is not dragged.
child: ListTile(
onTap: disabled || context.watch<ChatListCubit>().isBusy
? null
: () async {
disabled: tileDisabled,
selected: selected,
tileScale: ScaleKind.primary,
title: contact.editedProfile.name,
subtitle: contact.editedProfile.pronouns,
icon: Icons.person,
onTap: () async {
// Start a chat
final chatListCubit = context.read<ChatListCubit>();
await chatListCubit.getOrCreateChatSingleContact(
remoteConversationRecordKey: remoteConversationKey);
// Click over to chats
if (context.mounted) {
await MainPager.of(context)
?.pageController
.animateToPage(1,
duration: 250.ms, curve: Curves.easeInOut);
.animateToPage(1, duration: 250.ms, curve: Curves.easeInOut);
}
},
title: Text(contact.editedProfile.name),
subtitle: (contact.editedProfile.pronouns.isNotEmpty)
? Text(contact.editedProfile.pronouns)
: null,
iconColor: selected
// ignore: dead_code
? scale.primaryScale.appText
: scale.primaryScale.subtleText,
textColor: selected
// ignore: dead_code
? scale.primaryScale.appText
: scale.primaryScale.subtleText,
selectedColor: scale.primaryScale.appText,
leading: const Icon(Icons.person))));
}
endActions: [
SliderTileAction(
icon: Icons.delete,
label: translate('button.delete'),
actionScale: ScaleKind.tertiary,
onPressed: (context) async {
final contactListCubit = context.read<ContactListCubit>();
final chatListCubit = context.read<ChatListCubit>();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<proto.Contact>('contact', contact));
// Remove any chats for this contact
await chatListCubit.deleteChat(
remoteConversationRecordKey: remoteConversationKey);
// Delete the contact itself
await contactListCubit.deleteContact(contact: contact);
})
],
);
}
}

View File

@ -6,7 +6,7 @@ import 'package:flutter_translate/flutter_translate.dart';
import 'package:searchable_listview/searchable_listview.dart';
import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart';
import '../../theme/theme.dart';
import 'contact_item_widget.dart';
import 'empty_contact_list_widget.dart';
@ -25,7 +25,11 @@ class ContactListWidget extends StatelessWidget {
}
@override
Widget build(BuildContext context) => SizedBox.expand(
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return SizedBox.expand(
child: styledTitleContainer(
context: context,
title: translate('contact_list.title'),
@ -35,7 +39,8 @@ class ContactListWidget extends StatelessWidget {
: SearchableList<proto.Contact>(
initialList: contactList.toList(),
builder: (l, i, c) =>
ContactItemWidget(contact: c, disabled: disabled),
ContactItemWidget(contact: c, disabled: disabled)
.paddingLTRB(0, 4, 0, 0),
filter: (value) {
final lowerValue = value.toLowerCase();
return contactList
@ -49,9 +54,11 @@ class ContactListWidget extends StatelessWidget {
.toList();
},
spaceBetweenSearchAndList: 4,
defaultSuffixIconColor: scale.primaryScale.border,
inputDecoration: InputDecoration(
labelText: translate('contact_list.search'),
),
).paddingAll(8),
))).paddingLTRB(8, 0, 8, 8);
}
}

View File

@ -37,11 +37,11 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
Row(children: [
IconButton(
icon: const Icon(Icons.settings),
color: scale.secondaryScale.appText,
color: scale.secondaryScale.borderText,
constraints: const BoxConstraints.expand(height: 64, width: 64),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(scale.secondaryScale.border),
backgroundColor: MaterialStateProperty.all(
scale.primaryScale.hoverBorder),
shape: MaterialStateProperty.all(
const RoundedRectangleBorder(
borderRadius:

View File

@ -11,7 +11,7 @@ import '../../../chat_list/chat_list.dart';
import '../../../contact_invitation/contact_invitation.dart';
import '../../../contacts/contacts.dart';
import '../../../router/router.dart';
import '../../../tools/tools.dart';
import '../../../theme/theme.dart';
class HomeAccountReadyShell extends StatefulWidget {
factory HomeAccountReadyShell(

View File

@ -61,8 +61,9 @@ class AccountPageState extends State<AccountPage> {
translate('account_page.contact_invitations'),
textAlign: TextAlign.center,
style: textTheme.titleMedium!
.copyWith(color: scale.primaryScale.subtleText),
.copyWith(color: scale.primaryScale.borderText),
),
iconColor: scale.primaryScale.borderText,
initiallyExpanded: true,
children: [
ContactInvitationListWidget(

View File

@ -10,7 +10,6 @@ import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
import '../../../../chat/chat.dart';
import '../../../../contact_invitation/contact_invitation.dart';
import '../../../../theme/theme.dart';
import '../../../../tools/tools.dart';
import 'account_page.dart';
import 'bottom_sheet_action_button.dart';
import 'chats_page.dart';
@ -41,7 +40,7 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
Icons.add_comment_sharp,
];
final _bottomLabelList = <String>[
translate('pager.account'),
translate('pager.contacts'),
translate('pager.chats'),
];
@ -82,12 +81,11 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
final scale = theme.extension<ScaleScheme>()!;
return BottomBarItem(
title: Text(_bottomLabelList[index]),
icon: Icon(_selectedIconList[index], color: scale.primaryScale.appText),
icon:
Icon(_selectedIconList[index], color: scale.primaryScale.borderText),
selectedIcon:
Icon(_selectedIconList[index], color: scale.primaryScale.appText),
backgroundColor: scale.primaryScale.appText,
//unSelectedColor: theme.colorScheme.primaryContainer,
//selectedColor: theme.colorScheme.primary,
Icon(_selectedIconList[index], color: scale.primaryScale.borderText),
backgroundColor: scale.primaryScale.borderText,
//badge: const Text('9+'),
//showBadge: true,
);
@ -169,21 +167,10 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
// ),
bottomNavigationBar: StylishBottomBar(
backgroundColor: scale.primaryScale.hoverBorder,
// gradient: LinearGradient(
// begin: Alignment.topCenter,
// end: Alignment.bottomCenter,
// colors: <Color>[
// theme.colorScheme.primary,
// theme.colorScheme.primaryContainer,
// ]),
//borderRadius: BorderRadius.all(Radius.circular(16)),
option: AnimatedBarOptions(
// iconSize: 32,
//barAnimation: BarAnimation.fade,
iconStyle: IconStyle.animated,
inkEffect: true,
inkColor: scale.primaryScale.hoverBackground,
//opacity: 0.3,
inkColor: scale.primaryScale.hoverPrimary,
opacity: 0.3,
),
items: _buildBottomBarItems(),
hasNotch: true,
@ -198,11 +185,11 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
floatingActionButton: BottomSheetActionButton(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(14))),
foregroundColor: scale.secondaryScale.appText,
foregroundColor: scale.secondaryScale.borderText,
backgroundColor: scale.secondaryScale.hoverBorder,
builder: (context) => Icon(
_fabIconList[_currentPage],
color: scale.secondaryScale.appText,
color: scale.secondaryScale.borderText,
),
bottomSheetBuilder: (sheetContext) =>
_bottomSheetBuilder(sheetContext, context)),

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import '../../tools/tools.dart';
import '../../theme/theme.dart';
class HomeNoActive extends StatefulWidget {
const HomeNoActive({super.key});

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'scale_scheme.dart';
ChatTheme makeChatTheme(ScaleScheme scale, TextTheme textTheme) =>
DefaultChatTheme(
primaryColor: scale.primaryScale.calloutBackground,
secondaryColor: scale.secondaryScale.calloutBackground,
backgroundColor: scale.grayScale.appBackground,
sendButtonIcon: Image.asset(
'assets/icon-send.png',
color: scale.primaryScale.borderText,
package: 'flutter_chat_ui',
),
inputBackgroundColor: Colors.blue,
inputBorderRadius: BorderRadius.zero,
inputTextDecoration: InputDecoration(
filled: true,
fillColor: scale.primaryScale.elementBackground,
isDense: true,
contentPadding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
border: const OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(8))),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(8))),
),
inputContainerDecoration:
BoxDecoration(color: scale.primaryScale.border),
inputPadding: const EdgeInsets.all(9),
inputTextColor: scale.primaryScale.appText,
attachmentButtonIcon: const Icon(Icons.attach_file),
sentMessageBodyTextStyle: TextStyle(
color: scale.primaryScale.calloutText,
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1.5,
),
sentEmojiMessageTextStyle: const TextStyle(
color: Colors.white,
fontSize: 64,
),
receivedMessageBodyTextStyle: TextStyle(
color: scale.secondaryScale.calloutText,
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1.5,
),
receivedEmojiMessageTextStyle: const TextStyle(
color: Colors.white,
fontSize: 64,
));

View File

@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'radix_generator.dart';
import 'scale_color.dart';
import 'scale_input_decorator_theme.dart';
import 'scale_scheme.dart';
ScaleScheme _contrastScale(Brightness brightness) {
final back = brightness == Brightness.light ? Colors.white : Colors.black;
final front = brightness == Brightness.light ? Colors.black : Colors.white;
final primaryScale = ScaleColor(
appBackground: back,
subtleBackground: back,
elementBackground: back,
hoverElementBackground: back,
activeElementBackground: back,
subtleBorder: front,
border: front,
hoverBorder: front,
primary: back,
hoverPrimary: back,
subtleText: front,
appText: front,
primaryText: front,
borderText: back,
dialogBorder: front,
calloutBackground: front,
calloutText: back,
);
return ScaleScheme(
primaryScale: primaryScale,
primaryAlphaScale: primaryScale,
secondaryScale: primaryScale,
tertiaryScale: primaryScale,
grayScale: primaryScale,
errorScale: primaryScale);
}
ThemeData contrastGenerator(Brightness brightness) {
final textTheme = makeRadixTextTheme(brightness);
final scaleScheme = _contrastScale(brightness);
final colorScheme = scaleScheme.toColorScheme(brightness);
final scaleConfig = ScaleConfig(useVisualIndicators: true);
final themeData = ThemeData.from(
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
return themeData.copyWith(
bottomSheetTheme: themeData.bottomSheetTheme.copyWith(
elevation: 0,
modalElevation: 0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16)))),
canvasColor: scaleScheme.primaryScale.subtleBackground,
chipTheme: themeData.chipTheme.copyWith(
backgroundColor: scaleScheme.primaryScale.elementBackground,
selectedColor: scaleScheme.primaryScale.activeElementBackground,
surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground,
checkmarkColor: scaleScheme.primaryScale.border,
side: BorderSide(color: scaleScheme.primaryScale.border)),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: scaleScheme.primaryScale.elementBackground,
foregroundColor: scaleScheme.primaryScale.appText,
disabledBackgroundColor: scaleScheme.grayScale.elementBackground,
disabledForegroundColor: scaleScheme.grayScale.appText,
shape: RoundedRectangleBorder(
side: BorderSide(color: scaleScheme.primaryScale.border),
borderRadius: BorderRadius.circular(8))),
),
textSelectionTheme: TextSelectionThemeData(
cursorColor: scaleScheme.primaryScale.appText,
selectionColor: scaleScheme.primaryScale.appText.withAlpha(0x7F),
selectionHandleColor: scaleScheme.primaryScale.appText),
inputDecorationTheme: ScaleInputDecoratorTheme(scaleScheme, textTheme),
extensions: <ThemeExtension<dynamic>>[
scaleScheme,
scaleConfig,
]);
}

View File

@ -1,4 +1,6 @@
export 'chat_theme.dart';
export 'radix_generator.dart';
export 'scale_color.dart';
export 'scale_scheme.dart';
export 'slider_tile.dart';
export 'theme_preference.dart';

View File

@ -1,16 +1,16 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:radix_colors/radix_colors.dart';
import '../../tools/tools.dart';
import 'scale_color.dart';
import 'scale_input_decorator_theme.dart';
import 'scale_scheme.dart';
enum RadixThemeColor {
scarlet, // tomato + red + violet
babydoll, // crimson + purple + pink
scarlet, // red + violet + tomato
babydoll, // crimson + pink + purple
vapor, // pink + cyan + plum
gold, // yellow + amber + orange
garden, // grass + orange + brown
@ -19,7 +19,7 @@ enum RadixThemeColor {
lapis, // blue + indigo + mint
eggplant, // violet + purple + indigo
lime, // lime + yellow + orange
grim, // mauve + slate + sage
grim, // grey + purple + brown
}
enum _RadixBaseColor {
@ -282,11 +282,15 @@ extension ToScaleColor on RadixColor {
subtleBorder: step6,
border: step7,
hoverBorder: step8,
background: step9,
hoverBackground: step10,
primary: step9,
hoverPrimary: step10,
subtleText: step11,
appText: step12,
foregroundText: scaleExtra.foregroundText,
primaryText: scaleExtra.foregroundText,
borderText: step12,
dialogBorder: step9,
calloutBackground: step9,
calloutText: scaleExtra.foregroundText,
);
}
@ -338,28 +342,27 @@ class RadixScheme {
RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
late RadixScheme radixScheme;
switch (themeColor) {
// tomato + red + violet
// red + violet + tomato
case RadixThemeColor.scarlet:
radixScheme = RadixScheme(
primaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.tomato),
primaryScale: _radixColorSteps(brightness, false, _RadixBaseColor.red),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.tomato),
_radixColorSteps(brightness, true, _RadixBaseColor.red),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.red),
_radixColorSteps(brightness, false, _RadixBaseColor.violet),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.violet),
_radixColorSteps(brightness, false, _RadixBaseColor.tomato),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.tomato),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.red),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(brightness, false, _RadixBaseColor.yellow),
errorExtra: RadixScaleExtra(foregroundText: Colors.black),
);
// crimson + purple + pink
// crimson + pink + purple
case RadixThemeColor.babydoll:
radixScheme = RadixScheme(
primaryScale:
@ -369,10 +372,10 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
_radixColorSteps(brightness, true, _RadixBaseColor.crimson),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.purple),
_radixColorSteps(brightness, false, _RadixBaseColor.pink),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.pink),
_radixColorSteps(brightness, false, _RadixBaseColor.purple),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale:
_radixGraySteps(brightness, false, _RadixBaseColor.crimson),
@ -546,13 +549,13 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
_radixGraySteps(brightness, false, _RadixBaseColor.tomato),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.tomato),
_radixGraySteps(brightness, true, _RadixBaseColor.tomato),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.indigo),
_radixColorSteps(brightness, false, _RadixBaseColor.purple),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.teal),
_radixColorSteps(brightness, false, _RadixBaseColor.brown),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: brightness == Brightness.dark
? RadixColors.dark.gray
@ -565,87 +568,7 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
return radixScheme;
}
ColorScheme _scaleToColorScheme(Brightness brightness, ScaleScheme scale) =>
ColorScheme(
brightness: brightness,
primary: scale.primaryScale.background, // reviewed
onPrimary: scale.primaryScale.foregroundText, // reviewed
primaryContainer:
Colors.red, // scale.primaryScale.hoverElementBackground,
onPrimaryContainer: Colors.green, //scale.primaryScale.subtleText,
secondary: scale.secondaryScale.background,
onSecondary: scale.secondaryScale.foregroundText,
secondaryContainer: scale.secondaryScale.hoverElementBackground,
onSecondaryContainer: scale.secondaryScale.subtleText,
tertiary: scale.tertiaryScale.background,
onTertiary: scale.tertiaryScale.foregroundText,
tertiaryContainer: scale.tertiaryScale.hoverElementBackground,
onTertiaryContainer: scale.tertiaryScale.subtleText,
error: scale.errorScale.background,
onError: scale.errorScale.foregroundText,
errorContainer: scale.errorScale.hoverElementBackground,
onErrorContainer: scale.errorScale.subtleText,
background: scale.grayScale.appBackground, // reviewed
onBackground: scale.grayScale.appText, // reviewed
surface: scale.primaryScale.background, // reviewed
onSurface: scale.primaryScale.foregroundText, // reviewed
surfaceVariant: scale.primaryScale.elementBackground,
onSurfaceVariant:
scale.primaryScale.foregroundText, // ?? reviewed a little
outline: scale.primaryScale.border,
outlineVariant: scale.primaryScale.subtleBorder,
shadow: const Color(0xFF000000),
scrim: scale.primaryScale.background,
inverseSurface: scale.primaryScale.subtleText,
onInverseSurface: scale.primaryScale.subtleBackground,
inversePrimary: scale.primaryScale.hoverBackground,
surfaceTint: scale.primaryAlphaScale.hoverElementBackground,
);
ChatTheme makeChatTheme(ScaleScheme scale, TextTheme textTheme) =>
DefaultChatTheme(
primaryColor: scale.primaryScale.background,
secondaryColor: scale.secondaryScale.background,
backgroundColor: scale.grayScale.appBackground,
inputBackgroundColor: Colors.blue,
inputBorderRadius: BorderRadius.zero,
inputTextDecoration: InputDecoration(
filled: true,
fillColor: scale.primaryScale.elementBackground,
isDense: true,
contentPadding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
border: const OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(8))),
),
inputContainerDecoration:
BoxDecoration(color: scale.primaryScale.border),
inputPadding: const EdgeInsets.all(9),
inputTextColor: scale.primaryScale.appText,
attachmentButtonIcon: const Icon(Icons.attach_file),
sentMessageBodyTextStyle: TextStyle(
color: scale.primaryScale.foregroundText,
decorationColor: Colors.red,
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1.5,
),
sentEmojiMessageTextStyle: const TextStyle(
color: Colors.white,
fontSize: 64,
),
receivedMessageBodyTextStyle: TextStyle(
color: scale.primaryScale.foregroundText,
fontSize: 16,
fontWeight: FontWeight.w500,
height: 1.5,
),
receivedEmojiMessageTextStyle: const TextStyle(
color: Colors.white,
fontSize: 64,
));
TextTheme _makeTextTheme(Brightness brightness) {
TextTheme makeRadixTextTheme(Brightness brightness) {
late final TextTheme textTheme;
if (Platform.isIOS) {
textTheme = (brightness == Brightness.light)
@ -677,10 +600,11 @@ TextTheme _makeTextTheme(Brightness brightness) {
}
ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
final textTheme = _makeTextTheme(brightness);
final textTheme = makeRadixTextTheme(brightness);
final radix = _radixScheme(brightness, themeColor);
final scaleScheme = radix.toScale();
final colorScheme = _scaleToColorScheme(brightness, scaleScheme);
final colorScheme = scaleScheme.toColorScheme(brightness);
final scaleConfig = ScaleConfig(useVisualIndicators: false);
final themeData = ThemeData.from(
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
@ -697,34 +621,18 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
backgroundColor: scaleScheme.primaryScale.elementBackground,
selectedColor: scaleScheme.primaryScale.activeElementBackground,
surfaceTintColor: scaleScheme.primaryScale.hoverElementBackground,
checkmarkColor: scaleScheme.primaryScale.background,
checkmarkColor: scaleScheme.primaryScale.primary,
side: BorderSide(color: scaleScheme.primaryScale.border)),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: scaleScheme.primaryScale.elementBackground,
foregroundColor: scaleScheme.primaryScale.appText,
foregroundColor: scaleScheme.primaryScale.primary,
disabledBackgroundColor: scaleScheme.grayScale.elementBackground,
disabledForegroundColor: scaleScheme.grayScale.appText,
disabledForegroundColor: scaleScheme.grayScale.primary,
shape: RoundedRectangleBorder(
side: BorderSide(color: scaleScheme.primaryScale.border),
borderRadius: BorderRadius.circular(8))),
),
focusColor: scaleScheme.primaryScale.activeElementBackground,
hoverColor: scaleScheme.primaryScale.hoverElementBackground,
inputDecorationTheme: themeData.inputDecorationTheme.copyWith(
border: OutlineInputBorder(
borderSide: BorderSide(color: scaleScheme.primaryScale.border),
borderRadius: BorderRadius.circular(8)),
contentPadding: const EdgeInsets.all(8),
labelStyle: TextStyle(
color: scaleScheme.primaryScale.subtleText.withAlpha(127)),
floatingLabelStyle:
TextStyle(color: scaleScheme.primaryScale.subtleText),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: scaleScheme.primaryScale.hoverBorder, width: 2),
borderRadius: BorderRadius.circular(8))),
extensions: <ThemeExtension<dynamic>>[
scaleScheme,
]);
inputDecorationTheme: ScaleInputDecoratorTheme(scaleScheme, textTheme),
extensions: <ThemeExtension<dynamic>>[scaleScheme, scaleConfig]);
}

View File

@ -10,11 +10,15 @@ class ScaleColor {
required this.subtleBorder,
required this.border,
required this.hoverBorder,
required this.background,
required this.hoverBackground,
required this.primary,
required this.hoverPrimary,
required this.subtleText,
required this.appText,
required this.foregroundText,
required this.primaryText,
required this.borderText,
required this.dialogBorder,
required this.calloutBackground,
required this.calloutText,
});
Color appBackground;
@ -25,11 +29,15 @@ class ScaleColor {
Color subtleBorder;
Color border;
Color hoverBorder;
Color background;
Color hoverBackground;
Color primary;
Color hoverPrimary;
Color subtleText;
Color appText;
Color foregroundText;
Color primaryText;
Color borderText;
Color dialogBorder;
Color calloutBackground;
Color calloutText;
ScaleColor copyWith({
Color? appBackground,
@ -45,6 +53,10 @@ class ScaleColor {
Color? subtleText,
Color? appText,
Color? foregroundText,
Color? borderText,
Color? dialogBorder,
Color? calloutBackground,
Color? calloutText,
}) =>
ScaleColor(
appBackground: appBackground ?? this.appBackground,
@ -57,12 +69,15 @@ class ScaleColor {
subtleBorder: subtleBorder ?? this.subtleBorder,
border: border ?? this.border,
hoverBorder: hoverBorder ?? this.hoverBorder,
background: background ?? this.background,
hoverBackground: hoverBackground ?? this.hoverBackground,
primary: background ?? this.primary,
hoverPrimary: hoverBackground ?? this.hoverPrimary,
subtleText: subtleText ?? this.subtleText,
appText: appText ?? this.appText,
foregroundText: foregroundText ?? this.foregroundText,
);
primaryText: foregroundText ?? this.primaryText,
borderText: borderText ?? this.borderText,
dialogBorder: dialogBorder ?? this.dialogBorder,
calloutBackground: calloutBackground ?? this.calloutBackground,
calloutText: calloutText ?? this.calloutText);
// ignore: prefer_constructors_over_static_methods
static ScaleColor lerp(ScaleColor a, ScaleColor b, double t) => ScaleColor(
@ -85,14 +100,22 @@ class ScaleColor {
border: Color.lerp(a.border, b.border, t) ?? const Color(0x00000000),
hoverBorder: Color.lerp(a.hoverBorder, b.hoverBorder, t) ??
const Color(0x00000000),
background: Color.lerp(a.background, b.background, t) ??
const Color(0x00000000),
hoverBackground: Color.lerp(a.hoverBackground, b.hoverBackground, t) ??
primary: Color.lerp(a.primary, b.primary, t) ?? const Color(0x00000000),
hoverPrimary: Color.lerp(a.hoverPrimary, b.hoverPrimary, t) ??
const Color(0x00000000),
subtleText: Color.lerp(a.subtleText, b.subtleText, t) ??
const Color(0x00000000),
appText: Color.lerp(a.appText, b.appText, t) ?? const Color(0x00000000),
foregroundText: Color.lerp(a.foregroundText, b.foregroundText, t) ??
primaryText: Color.lerp(a.primaryText, b.primaryText, t) ??
const Color(0x00000000),
borderText: Color.lerp(a.borderText, b.borderText, t) ??
const Color(0x00000000),
dialogBorder: Color.lerp(a.dialogBorder, b.dialogBorder, t) ??
const Color(0x00000000),
calloutBackground:
Color.lerp(a.calloutBackground, b.calloutBackground, t) ??
const Color(0x00000000),
calloutText: Color.lerp(a.calloutText, b.calloutText, t) ??
const Color(0x00000000),
);
}

View File

@ -0,0 +1,165 @@
import 'package:flutter/material.dart';
import 'scale_scheme.dart';
class ScaleInputDecoratorTheme extends InputDecorationTheme {
ScaleInputDecoratorTheme(this._scaleScheme, this._textTheme)
: super(
border: OutlineInputBorder(
borderSide: BorderSide(color: _scaleScheme.primaryScale.border),
borderRadius: BorderRadius.circular(8)),
contentPadding: const EdgeInsets.all(8),
labelStyle: TextStyle(
color: _scaleScheme.primaryScale.subtleText.withAlpha(127)),
floatingLabelStyle:
TextStyle(color: _scaleScheme.primaryScale.subtleText),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: _scaleScheme.primaryScale.hoverBorder, width: 2),
borderRadius: BorderRadius.circular(8)));
final ScaleScheme _scaleScheme;
final TextTheme _textTheme;
@override
TextStyle? get hintStyle => MaterialStateTextStyle.resolveWith((states) {
if (states.contains(MaterialState.disabled)) {
return TextStyle(color: _scaleScheme.grayScale.border);
}
return TextStyle(color: _scaleScheme.primaryScale.border);
});
@override
Color? get fillColor => MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.disabled)) {
return _scaleScheme.grayScale.primary.withOpacity(0.04);
}
return _scaleScheme.primaryScale.primary.withOpacity(0.04);
});
@override
BorderSide? get activeIndicatorBorder =>
MaterialStateBorderSide.resolveWith((states) {
if (states.contains(MaterialState.disabled)) {
return BorderSide(
color: _scaleScheme.grayScale.border.withAlpha(0x7F));
}
if (states.contains(MaterialState.error)) {
if (states.contains(MaterialState.hovered)) {
return BorderSide(color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(MaterialState.focused)) {
return BorderSide(color: _scaleScheme.errorScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(MaterialState.hovered)) {
return BorderSide(color: _scaleScheme.secondaryScale.hoverBorder);
}
if (states.contains(MaterialState.focused)) {
return BorderSide(
color: _scaleScheme.secondaryScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.secondaryScale.subtleBorder);
});
@override
BorderSide? get outlineBorder =>
MaterialStateBorderSide.resolveWith((states) {
if (states.contains(MaterialState.disabled)) {
return BorderSide(
color: _scaleScheme.grayScale.border.withAlpha(0x7F));
}
if (states.contains(MaterialState.error)) {
if (states.contains(MaterialState.hovered)) {
return BorderSide(color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(MaterialState.focused)) {
return BorderSide(color: _scaleScheme.errorScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(MaterialState.hovered)) {
return BorderSide(color: _scaleScheme.primaryScale.hoverBorder);
}
if (states.contains(MaterialState.focused)) {
return BorderSide(color: _scaleScheme.primaryScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.primaryScale.subtleBorder);
});
@override
Color? get iconColor => _scaleScheme.primaryScale.primary;
@override
Color? get prefixIconColor => MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.disabled)) {
return _scaleScheme.primaryScale.primary.withAlpha(0x3F);
}
if (states.contains(MaterialState.error)) {
return _scaleScheme.errorScale.primary;
}
return _scaleScheme.primaryScale.primary;
});
@override
Color? get suffixIconColor => MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.disabled)) {
return _scaleScheme.primaryScale.primary.withAlpha(0x3F);
}
if (states.contains(MaterialState.error)) {
return _scaleScheme.errorScale.primary;
}
return _scaleScheme.primaryScale.primary;
});
@override
TextStyle? get labelStyle => MaterialStateTextStyle.resolveWith((states) {
final textStyle = _textTheme.bodyLarge ?? const TextStyle();
if (states.contains(MaterialState.disabled)) {
return textStyle.copyWith(
color: _scaleScheme.grayScale.border.withAlpha(0x7F));
}
if (states.contains(MaterialState.error)) {
if (states.contains(MaterialState.hovered)) {
return textStyle.copyWith(
color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(MaterialState.focused)) {
return textStyle.copyWith(
color: _scaleScheme.errorScale.hoverBorder);
}
return textStyle.copyWith(
color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(MaterialState.hovered)) {
return textStyle.copyWith(
color: _scaleScheme.primaryScale.hoverBorder);
}
if (states.contains(MaterialState.focused)) {
return textStyle.copyWith(
color: _scaleScheme.primaryScale.hoverBorder);
}
return textStyle.copyWith(color: _scaleScheme.primaryScale.border);
});
@override
TextStyle? get floatingLabelStyle => labelStyle;
@override
TextStyle? get helperStyle => MaterialStateTextStyle.resolveWith((states) {
final textStyle = _textTheme.bodySmall ?? const TextStyle();
if (states.contains(MaterialState.disabled)) {
return textStyle.copyWith(
color: _scaleScheme.grayScale.border.withAlpha(0x7F));
}
return textStyle.copyWith(
color: _scaleScheme.secondaryScale.border.withAlpha(0x7F));
});
@override
TextStyle? get errorStyle => MaterialStateTextStyle.resolveWith((states) {
final textStyle = _textTheme.bodySmall ?? const TextStyle();
return textStyle.copyWith(color: _scaleScheme.errorScale.primary);
});
}

View File

@ -2,14 +2,17 @@ import 'package:flutter/material.dart';
import 'scale_color.dart';
enum ScaleKind { primary, primaryAlpha, secondary, tertiary, gray, error }
class ScaleScheme extends ThemeExtension<ScaleScheme> {
ScaleScheme(
{required this.primaryScale,
ScaleScheme({
required this.primaryScale,
required this.primaryAlphaScale,
required this.secondaryScale,
required this.tertiaryScale,
required this.grayScale,
required this.errorScale});
required this.errorScale,
});
final ScaleColor primaryScale;
final ScaleColor primaryAlphaScale;
@ -18,6 +21,23 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
final ScaleColor grayScale;
final ScaleColor errorScale;
ScaleColor scale(ScaleKind kind) {
switch (kind) {
case ScaleKind.primary:
return primaryScale;
case ScaleKind.primaryAlpha:
return primaryAlphaScale;
case ScaleKind.secondary:
return secondaryScale;
case ScaleKind.tertiary:
return tertiaryScale;
case ScaleKind.gray:
return grayScale;
case ScaleKind.error:
return errorScale;
}
}
@override
ScaleScheme copyWith(
{ScaleColor? primaryScale,
@ -50,4 +70,65 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
errorScale: ScaleColor.lerp(errorScale, other.errorScale, t),
);
}
ColorScheme toColorScheme(Brightness brightness) => ColorScheme(
brightness: brightness,
primary: primaryScale.primary, // reviewed
onPrimary: primaryScale.primaryText, // reviewed
// primaryContainer: primaryScale.hoverElementBackground,
// onPrimaryContainer: primaryScale.subtleText,
secondary: secondaryScale.primary,
onSecondary: secondaryScale.primaryText,
// secondaryContainer: secondaryScale.hoverElementBackground,
// onSecondaryContainer: secondaryScale.subtleText,
tertiary: tertiaryScale.primary,
onTertiary: tertiaryScale.primaryText,
// tertiaryContainer: tertiaryScale.hoverElementBackground,
// onTertiaryContainer: tertiaryScale.subtleText,
error: errorScale.primary,
onError: errorScale.primaryText,
// errorContainer: errorScale.hoverElementBackground,
// onErrorContainer: errorScale.subtleText,
background: grayScale.appBackground, // reviewed
onBackground: grayScale.appText, // reviewed
surface: primaryScale.primary, // reviewed
onSurface: primaryScale.primaryText, // reviewed
surfaceVariant: secondaryScale.primary,
onSurfaceVariant: secondaryScale.primaryText, // ?? reviewed a little
outline: primaryScale.border,
outlineVariant: secondaryScale.border,
shadow: const Color(0xFF000000),
//scrim: primaryScale.background,
// inverseSurface: primaryScale.subtleText,
// onInverseSurface: primaryScale.subtleBackground,
// inversePrimary: primaryScale.hoverBackground,
// surfaceTint: primaryAlphaScale.hoverElementBackground,
);
}
class ScaleConfig extends ThemeExtension<ScaleConfig> {
ScaleConfig({
required this.useVisualIndicators,
});
final bool useVisualIndicators;
@override
ScaleConfig copyWith({
bool? useVisualIndicators,
}) =>
ScaleConfig(
useVisualIndicators: useVisualIndicators ?? this.useVisualIndicators,
);
@override
ScaleConfig lerp(ScaleConfig? other, double t) {
if (other is! ScaleConfig) {
return this;
}
return ScaleConfig(
useVisualIndicators:
t < .5 ? useVisualIndicators : other.useVisualIndicators,
);
}
}

View File

@ -0,0 +1,151 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import '../theme.dart';
class SliderTileAction {
const SliderTileAction({
required this.actionScale,
required this.onPressed,
this.key,
this.icon,
this.label,
});
final Key? key;
final ScaleKind actionScale;
final String? label;
final IconData? icon;
final SlidableActionCallback? onPressed;
}
class SliderTile extends StatelessWidget {
const SliderTile(
{required this.disabled,
required this.selected,
required this.tileScale,
required this.title,
this.subtitle = '',
this.endActions = const [],
this.startActions = const [],
this.onTap,
this.icon,
super.key});
final bool disabled;
final bool selected;
final ScaleKind tileScale;
final List<SliderTileAction> endActions;
final List<SliderTileAction> startActions;
final GestureTapCallback? onTap;
final IconData? icon;
final String title;
final String subtitle;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<bool>('disabled', disabled))
..add(DiagnosticsProperty<bool>('selected', selected))
..add(DiagnosticsProperty<ScaleKind>('tileScale', tileScale))
..add(IterableProperty<SliderTileAction>('endActions', endActions))
..add(IterableProperty<SliderTileAction>('startActions', startActions))
..add(ObjectFlagProperty<GestureTapCallback?>.has('onTap', onTap))
..add(DiagnosticsProperty<IconData?>('icon', icon))
..add(StringProperty('title', title))
..add(StringProperty('subtitle', subtitle));
}
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final tileColor = scale.scale(!disabled ? tileScale : ScaleKind.gray);
final scalecfg = theme.extension<ScaleConfig>()!;
final borderColor = selected ? tileColor.hoverBorder : tileColor.border;
final backgroundColor = scalecfg.useVisualIndicators && !selected
? tileColor.borderText
: borderColor;
final textColor = scalecfg.useVisualIndicators && !selected
? borderColor
: tileColor.borderText;
return Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: backgroundColor,
shape: RoundedRectangleBorder(
side: scalecfg.useVisualIndicators
? BorderSide(width: 2, color: borderColor, strokeAlign: 0)
: BorderSide.none,
borderRadius: BorderRadius.circular(8),
)),
child: Slidable(
// Specify a key if the Slidable is dismissible.
key: key,
endActionPane: endActions.isEmpty
? null
: ActionPane(
motion: const DrawerMotion(),
children: endActions
.map(
(a) => SlidableAction(
onPressed: disabled ? null : a.onPressed,
backgroundColor: scalecfg.useVisualIndicators
? (selected
? tileColor.borderText
: tileColor.border)
: scale.scale(a.actionScale).primary,
foregroundColor: scalecfg.useVisualIndicators
? (selected
? tileColor.border
: tileColor.borderText)
: scale.scale(a.actionScale).primaryText,
icon: a.icon,
label: a.label,
padding: const EdgeInsets.all(2)),
)
.toList()),
startActionPane: startActions.isEmpty
? null
: ActionPane(
motion: const DrawerMotion(),
children: startActions
.map(
(a) => SlidableAction(
onPressed: disabled ? null : a.onPressed,
backgroundColor: scalecfg.useVisualIndicators
? (selected
? tileColor.borderText
: tileColor.border)
: scale.scale(a.actionScale).primary,
foregroundColor: scalecfg.useVisualIndicators
? (selected
? tileColor.border
: tileColor.borderText)
: scale.scale(a.actionScale).primaryText,
icon: a.icon,
label: a.label,
padding: const EdgeInsets.all(2)),
)
.toList()),
child: Padding(
padding: scalecfg.useVisualIndicators
? EdgeInsets.zero
: const EdgeInsets.fromLTRB(0, 2, 0, 2),
child: ListTile(
onTap: onTap,
title: Text(
title,
softWrap: true,
),
subtitle: subtitle.isNotEmpty ? Text(subtitle) : null,
iconColor: textColor,
textColor: textColor,
leading: icon == null ? null : Icon(icon)))));
}
}

View File

@ -2,7 +2,8 @@ import 'package:change_case/change_case.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../tools/tools.dart';
import '../views/widget_helpers.dart';
import 'contrast_generator.dart';
import 'radix_generator.dart';
part 'theme_preference.freezed.dart';
@ -83,7 +84,7 @@ extension ThemePreferencesExt on ThemePreferences {
// Special cases
case ColorPreference.contrast:
// xxx do contrastGenerator
themeData = radixGenerator(brightness, RadixThemeColor.grim);
themeData = contrastGenerator(brightness);
// Generate from Radix
case ColorPreference.scarlet:
themeData = radixGenerator(brightness, RadixThemeColor.scarlet);

View File

@ -2,7 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../theme/theme.dart';
import '../theme.dart';
class StyledDialog extends StatelessWidget {
const StyledDialog({required this.title, required this.child, super.key});
@ -19,10 +19,11 @@ class StyledDialog extends StatelessWidget {
borderRadius: BorderRadius.all(Radius.circular(16)),
),
contentPadding: const EdgeInsets.all(4),
backgroundColor: scale.primaryScale.border,
backgroundColor: scale.primaryScale.dialogBorder,
title: Text(
title,
style: textTheme.titleMedium,
style: textTheme.titleMedium!
.copyWith(color: scale.primaryScale.borderText),
textAlign: TextAlign.center,
),
titlePadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),

View File

@ -1,2 +1,5 @@
export 'brightness_preferences.dart';
export 'color_preferences.dart';
export 'scanner_error_widget.dart';
export 'styled_dialog.dart';
export 'widget_helpers.dart';

View File

@ -9,7 +9,7 @@ import 'package:flutter_translate/flutter_translate.dart';
import 'package:motion_toast/motion_toast.dart';
import 'package:quickalert/quickalert.dart';
import '../theme/theme.dart';
import '../theme.dart';
extension BorderExt on Widget {
DecoratedBox debugBorder() => DecoratedBox(
@ -35,19 +35,22 @@ Widget buildProgressIndicator() => Builder(builder: (context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return SpinKitFoldingCube(
color: scale.tertiaryScale.background,
color: scale.tertiaryScale.primary,
size: 80,
);
});
Widget waitingPage({String? text}) => Builder(
builder: (context) => ColoredBox(
color: Theme.of(context).scaffoldBackgroundColor,
Widget waitingPage({String? text}) => Builder(builder: (context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return ColoredBox(
color: scale.tertiaryScale.primaryText,
child: Center(
child: Column(children: [
buildProgressIndicator().expanded(),
if (text != null) Text(text)
]))));
])));
});
Widget debugPage(String text) => Builder(
builder: (context) => ColoredBox(
@ -132,12 +135,30 @@ void showInfoToast(BuildContext context, String message) {
).show(context);
}
// Widget insetBorder(
// {required BuildContext context,
// required bool enabled,
// required Color color,
// required Widget child}) {
// if (!enabled) {
// return child;
// }
// return Stack({
// children: [] {
// DecoratedBox(decoration: BoxDecoration()
// child,
// }
// })
// }
Widget styledTitleContainer({
required BuildContext context,
required String title,
required Widget child,
Color? borderColor,
Color? backgroundColor,
Color? titleColor,
}) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
@ -153,7 +174,7 @@ Widget styledTitleContainer({
Text(
title,
style: textTheme.titleMedium!
.copyWith(color: scale.primaryScale.subtleText),
.copyWith(color: titleColor ?? scale.primaryScale.borderText),
).paddingLTRB(8, 8, 8, 4),
DecoratedBox(
decoration: ShapeDecoration(
@ -174,6 +195,7 @@ Widget styledBottomSheet({
required Widget child,
Color? borderColor,
Color? backgroundColor,
Color? titleColor,
}) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
@ -181,7 +203,7 @@ Widget styledBottomSheet({
return DecoratedBox(
decoration: ShapeDecoration(
color: borderColor ?? scale.primaryScale.border,
color: borderColor ?? scale.primaryScale.dialogBorder,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
@ -190,7 +212,7 @@ Widget styledBottomSheet({
Text(
title,
style: textTheme.titleMedium!
.copyWith(color: scale.primaryScale.subtleText),
.copyWith(color: titleColor ?? scale.primaryScale.borderText),
).paddingLTRB(8, 8, 8, 4),
DecoratedBox(
decoration: ShapeDecoration(

View File

@ -52,27 +52,23 @@ class _EnterPasswordDialogState extends State<EnterPasswordDialog> {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return Dialog(
backgroundColor: scale.grayScale.subtleBackground,
return StyledDialog(
title: widget.matchPass == null
? translate('enter_password_dialog.enter_password')
: translate('enter_password_dialog.reenter_password'),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.matchPass == null
? translate('enter_password_dialog.enter_password')
: translate('enter_password_dialog.reenter_password'),
style: theme.textTheme.titleLarge,
).paddingAll(16),
TextField(
controller: passwordController,
focusNode: focusNode,
autofocus: true,
enableSuggestions: false,
obscureText:
!_passwordVisible, //This will obscure text dynamically
obscureText: !_passwordVisible,
obscuringCharacter: '*',
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.singleLineFormatter
],
@ -87,7 +83,7 @@ class _EnterPasswordDialogState extends State<EnterPasswordDialog> {
? null
: Icon(Icons.check_circle,
color: passwordController.text == widget.matchPass
? scale.primaryScale.background
? scale.primaryScale.primary
: scale.grayScale.subtleBackground),
suffixIcon: IconButton(
icon: Icon(

View File

@ -67,20 +67,16 @@ class _EnterPinDialogState extends State<EnterPinDialog> {
);
/// Optionally you can use form to validate the Pinput
return Dialog(
backgroundColor: scale.grayScale.subtleBackground,
return StyledDialog(
title: !widget.reenter
? translate('enter_pin_dialog.enter_pin')
: translate('enter_pin_dialog.reenter_pin'),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
!widget.reenter
? translate('enter_pin_dialog.enter_pin')
: translate('enter_pin_dialog.reenter_pin'),
style: theme.textTheme.titleLarge,
).paddingAll(16),
Directionality(
// Specify direction if desired
textDirection: TextDirection.ltr,

View File

@ -5,10 +5,7 @@ export 'loggy.dart';
export 'phono_byte.dart';
export 'pop_control.dart';
export 'responsive.dart';
export 'scanner_error_widget.dart';
export 'shared_preferences.dart';
export 'state_logger.dart';
export 'stream_listenable.dart';
export 'styled_dialog.dart';
export 'widget_helpers.dart';
export 'window_control.dart';

View File

@ -140,17 +140,18 @@ class _DeveloperPageState extends State<DeveloperPage> {
// });
return Scaffold(
backgroundColor: scale.primaryScale.primary,
appBar: DefaultAppBar(
title: Text(translate('developer.title')),
leading: IconButton(
icon: Icon(Icons.arrow_back, color: scale.primaryScale.appText),
icon: Icon(Icons.arrow_back, color: scale.primaryScale.primaryText),
onPressed: () => GoRouterHelper(context).pop(),
),
actions: [
IconButton(
icon: const Icon(Icons.copy),
color: scale.primaryScale.appText,
disabledColor: scale.grayScale.subtleText,
color: scale.primaryScale.primaryText,
disabledColor: scale.primaryScale.primaryText.withAlpha(0x3F),
onPressed: _terminalController.selection == null
? null
: () async {
@ -158,17 +159,22 @@ class _DeveloperPageState extends State<DeveloperPage> {
}),
IconButton(
icon: const Icon(Icons.clear_all),
color: scale.primaryScale.appText,
disabledColor: scale.grayScale.subtleText,
color: scale.primaryScale.primaryText,
disabledColor: scale.primaryScale.primaryText.withAlpha(0x3F),
onPressed: () async {
await QuickAlert.show(
context: context,
type: QuickAlertType.confirm,
title: translate('developer.are_you_sure_clear'),
textColor: scale.primaryScale.appText,
confirmBtnColor: scale.primaryScale.elementBackground,
backgroundColor: scale.primaryScale.subtleBackground,
headerBackgroundColor: scale.primaryScale.background,
titleColor: scale.primaryScale.appText,
textColor: scale.primaryScale.subtleText,
confirmBtnColor: scale.primaryScale.primary,
cancelBtnTextStyle: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
color: scale.primaryScale.appText),
backgroundColor: scale.primaryScale.appBackground,
headerBackgroundColor: scale.primaryScale.primary,
confirmBtnText: translate('button.ok'),
cancelBtnText: translate('button.cancel'),
onConfirmBtnTap: () async {
@ -194,13 +200,23 @@ class _DeveloperPageState extends State<DeveloperPage> {
width: 64,
height: 40,
render: ResultRender.icon,
icon: SizedBox(
width: 10,
height: 10,
child: CustomPaint(
painter: DropdownArrowPainter(
color: scale.primaryScale.primaryText))),
textStyle: textTheme.labelMedium!
.copyWith(color: scale.primaryScale.appText),
.copyWith(color: scale.primaryScale.primaryText),
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
openBoxDecoration: BoxDecoration(
color: scale.primaryScale.activeElementBackground),
boxDecoration:
BoxDecoration(color: scale.primaryScale.elementBackground),
color: scale.primaryScale.border,
borderRadius: BorderRadius.circular(8),
),
boxDecoration: BoxDecoration(
color: scale.primaryScale.hoverBorder,
borderRadius: BorderRadius.circular(8),
),
),
dropdownOptions: DropdownOptions(
width: 160,
@ -224,7 +240,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
selectedPadding: const EdgeInsets.fromLTRB(8, 4, 8, 4)),
dropdownList: _logLevelDropdownItems,
)
).paddingLTRB(0, 0, 8, 0)
],
),
body: SafeArea(
@ -245,13 +261,19 @@ class _DeveloperPageState extends State<DeveloperPage> {
decoration: InputDecoration(
filled: true,
contentPadding: const EdgeInsets.fromLTRB(8, 2, 8, 2),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: scale.primaryScale.border)),
),
fillColor: scale.primaryScale.subtleBackground,
hintText: translate('developer.command'),
suffixIcon: IconButton(
icon: const Icon(Icons.send),
icon: Icon(Icons.send,
color: _debugCommandController.text.isEmpty
? scale.primaryScale.primary.withAlpha(0x3F)
: scale.primaryScale.primary),
onPressed: _debugCommandController.text.isEmpty
? null
: () async {

View File

@ -33,32 +33,32 @@ class SignalStrengthMeterWidget extends StatelessWidget {
switch (connectionState.attachment.state) {
case AttachmentState.detached:
iconWidget = Icon(Icons.signal_cellular_nodata,
size: iconSize, color: scale.grayScale.appText);
size: iconSize, color: scale.primaryScale.primaryText);
return;
case AttachmentState.detaching:
iconWidget = Icon(Icons.signal_cellular_off,
size: iconSize, color: scale.grayScale.appText);
size: iconSize, color: scale.primaryScale.primaryText);
return;
case AttachmentState.attaching:
value = 0;
color = scale.primaryScale.appText;
color = scale.primaryScale.primaryText;
case AttachmentState.attachedWeak:
value = 1;
color = scale.primaryScale.appText;
color = scale.primaryScale.primaryText;
case AttachmentState.attachedStrong:
value = 2;
color = scale.primaryScale.appText;
color = scale.primaryScale.primaryText;
case AttachmentState.attachedGood:
value = 3;
color = scale.primaryScale.appText;
color = scale.primaryScale.primaryText;
case AttachmentState.fullyAttached:
value = 4;
color = scale.primaryScale.appText;
color = scale.primaryScale.primaryText;
case AttachmentState.overAttached:
value = 4;
color = scale.secondaryScale.subtleText;
color = scale.primaryScale.primaryText;
}
inactiveColor = scale.grayScale.subtleText;
inactiveColor = scale.primaryScale.primaryText;
iconWidget = SignalStrengthIndicator.bars(
value: value,
@ -66,7 +66,7 @@ class SignalStrengthMeterWidget extends StatelessWidget {
inactiveColor: inactiveColor,
size: iconSize,
barCount: 4,
spacing: 1);
spacing: 2);
},
loading: () => {iconWidget = const Icon(Icons.warning)},
error: (e, st) => {

View File

@ -69,7 +69,7 @@ class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
// Because this is async, we could get an update while we're
// still processing the last one. Only called after init future has run
// so we dont have to wait for that here.
_sspUpdate.busyUpdate<T, AsyncValue<IList<T>>>(
_sspUpdate.busyUpdate<T, DHTShortArrayState<T>>(
busy, (emit) async => _refreshInner(emit));
}

View File

@ -470,6 +470,8 @@ class _DHTShortArrayHead {
_subscription = null;
}
// Called when the shortarray changes online and we find out from a watch
// but not when we make a change locally
Future<void> _onHeadValueChanged(
DHTRecord record, Uint8List? data, List<ValueSubkeyRange> subkeys) async {
// If head record subkey zero changes, then the layout