sliver refactor

This commit is contained in:
Christien Rioux 2024-07-11 23:04:08 -04:00
parent 67812b3c6f
commit 2fa3cbd21c
25 changed files with 710 additions and 387 deletions

View File

@ -97,8 +97,9 @@
"invalid_account_title": "Invalid Account",
"invalid_account_text": "Account is invalid, removing from list"
},
"account_page": {
"contact_invitations": "Contact Invitations"
"contacts_page": {
"contacts": "Contacts",
"invitations": "Invitations"
},
"add_contact_sheet": {
"new_contact": "New Contact",
@ -176,14 +177,13 @@
"password_does_not_match": "Password does not match"
},
"contact_list": {
"title": "Contacts",
"invite_people": "Invite people to VeilidChat",
"search": "Search contacts",
"invitation": "Invitation"
},
"chat_list": {
"search": "Search chats",
"start_a_conversation": "Start a conversation",
"start_a_conversation": "Start A Conversation",
"chats": "Chats",
"groups": "Groups"
},

View File

@ -58,6 +58,8 @@ PODS:
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- native_device_orientation (0.0.1):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- pasteboard (0.0.1):
@ -91,6 +93,7 @@ DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
- native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
@ -129,6 +132,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_native_splash/ios"
mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/ios"
native_device_orientation:
:path: ".symlinks/plugins/native_device_orientation/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
pasteboard:
@ -169,6 +174,7 @@ SPEC CHECKSUMS:
MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1
mobile_scanner: 8564358885a9253c43f822435b70f9345c87224f
nanopb: 438bc412db1928dac798aa6fd75726007be04262
native_device_orientation: 348b10c346a60ebbc62fb235a4fdb5d1b61a8f55
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46

View File

@ -44,25 +44,11 @@ class EditAccountPage extends StatefulWidget {
}
}
class _EditAccountPageState extends State<EditAccountPage> {
bool _isInAsyncCall = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.portraitOnly);
});
}
@override
void dispose() {
unawaited(
changeWindowSetup(TitleBarStyle.normal, OrientationCapability.normal));
super.dispose();
}
class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
_EditAccountPageState()
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly);
Widget _editAccountForm(BuildContext context,
{required Future<void> Function(GlobalKey<FormBuilderState>)
@ -314,4 +300,8 @@ class _EditAccountPageState extends State<EditAccountPage> {
]).paddingSymmetric(horizontal: 24, vertical: 8)))
.withModalHUD(context, displayModalHUD);
}
////////////////////////////////////////////////////////////////////////////
bool _isInAsyncCall = false;
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -20,18 +22,11 @@ class NewAccountPage extends StatefulWidget {
State createState() => _NewAccountPageState();
}
class _NewAccountPageState extends State<NewAccountPage> {
bool _isInAsyncCall = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.portraitOnly);
});
}
class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
_NewAccountPageState()
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly);
Widget _newAccountForm(BuildContext context,
{required Future<void> Function(GlobalKey<FormBuilderState>) onSubmit}) {
@ -120,4 +115,8 @@ class _NewAccountPageState extends State<NewAccountPage> {
)).paddingSymmetric(horizontal: 24, vertical: 8),
).withModalHUD(context, displayModalHUD);
}
////////////////////////////////////////////////////////////////////////////
bool _isInAsyncCall = false;
}

View File

@ -43,7 +43,7 @@ class ProfileWidget extends StatelessWidget {
: scale.primaryScale.borderText,
width: 2),
borderRadius: BorderRadius.all(
Radius.circular(16 * scaleConfig.borderRadiusScale))),
Radius.circular(12 * scaleConfig.borderRadiusScale))),
),
child: Row(children: [
const Spacer(),

View File

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
@ -29,22 +30,17 @@ class ShowRecoveryKeyPage extends StatefulWidget {
_name = name;
@override
ShowRecoveryKeyPageState createState() => ShowRecoveryKeyPageState();
State<ShowRecoveryKeyPage> createState() => _ShowRecoveryKeyPageState();
final WritableSuperIdentity _writableSuperIdentity;
final String _name;
}
class ShowRecoveryKeyPageState extends State<ShowRecoveryKeyPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.portraitOnly);
});
}
class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
_ShowRecoveryKeyPageState()
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly);
Future<void> _shareRecoveryKey(
BuildContext context, Uint8List recoveryKey, String name) async {

View File

@ -15,7 +15,6 @@ import 'init.dart';
import 'layout/splash.dart';
import 'router/router.dart';
import 'settings/settings.dart';
import 'theme/models/theme_preference.dart';
import 'theme/theme.dart';
import 'tick.dart';
import 'tools/loggy.dart';
@ -92,7 +91,7 @@ class VeilidChatApp extends StatelessWidget {
Widget build(BuildContext context) => FutureProvider<VeilidChatGlobalInit?>(
initialData: null,
create: (context) async => VeilidChatGlobalInit.initialize(),
builder: (context, child) {
builder: (context, __) {
final globalInit = context.watch<VeilidChatGlobalInit?>();
if (globalInit == null) {
// Splash screen until we're done with init

View File

@ -63,17 +63,14 @@ class ChatListWidget extends StatelessWidget {
child: styledTitleContainer(
context: context,
title: translate('chat_list.chats'),
child: SizedBox.expand(
child: (chatList.isEmpty)
? const EmptyChatListWidget()
? const SizedBox.expand(child: EmptyChatListWidget())
: SearchableList<proto.Chat>(
initialList: chatList.map((x) => x.value).toList(),
itemBuilder: (c) {
switch (c.whichKind()) {
case proto.Chat_Kind.direct:
return _itemBuilderDirect(
c.direct,
contactMap,
return _itemBuilderDirect(c.direct, contactMap,
contactListV.busy || chatListV.busy);
case proto.Chat_Kind.group:
return const Text(
@ -88,8 +85,8 @@ class ChatListWidget extends StatelessWidget {
inputDecoration: InputDecoration(
labelText: translate('chat_list.search'),
),
),
).paddingAll(8))))
).paddingAll(8),
)))
.paddingLTRB(8, 0, 8, 8);
});
}

View File

@ -2,6 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
@ -31,36 +32,37 @@ class ContactInvitationListWidget extends StatefulWidget {
}
class ContactInvitationListWidgetState
extends State<ContactInvitationListWidget> {
final ScrollController _scrollController = ScrollController();
extends State<ContactInvitationListWidget>
with SingleTickerProviderStateMixin {
late final _controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 250), value: 1);
late final _animation =
CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
bool _expanded = true;
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
final theme = Theme.of(context);
//final textTheme = theme.textTheme;
// final textTheme = theme.textTheme;
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
return Container(
width: double.infinity,
margin: const EdgeInsets.fromLTRB(4, 0, 4, 4),
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16 * scaleConfig.borderRadiusScale),
)),
constraints: const BoxConstraints(maxHeight: 100),
child: Container(
width: double.infinity,
decoration: ShapeDecoration(
color: scale.primaryScale.subtleBackground,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16 * scaleConfig.borderRadiusScale),
)),
child: ListView.builder(
shrinkWrap: true,
controller: _scrollController,
return styledExpandingSliver(
context: context,
animation: _animation,
expanded: _expanded,
backgroundColor: scaleConfig.preferBorders
? scale.primaryScale.subtleBackground
: scale.primaryScale.subtleBorder,
onTap: () {
setState(() {
_expanded = !_expanded;
});
_controller.animateTo(_expanded ? 1 : 0);
},
title: translate('contacts_page.invitations'),
sliver: SliverList.builder(
itemCount: widget.contactInvitationRecordList.length,
itemBuilder: (context, index) {
if (index < 0 ||
@ -82,7 +84,6 @@ class ContactInvitationListWidgetState
}
return index;
},
).paddingLTRB(4, 6, 4, 6)),
);
));
}
}

View File

@ -28,27 +28,24 @@ class ContactListWidget extends StatefulWidget {
}
}
class _ContactListWidgetState extends State<ContactListWidget> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
class _ContactListWidgetState extends State<ContactListWidget>
with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
//final textTheme = theme.textTheme;
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
return styledTitleContainer(
return SliverLayoutBuilder(
builder: (context, constraints) => styledHeaderSliver(
context: context,
title: translate('contact_list.title'),
child: SearchableList<proto.Contact>(
shrinkWrap: true,
backgroundColor: scaleConfig.preferBorders
? scale.primaryScale.subtleBackground
: scale.primaryScale.subtleBorder,
title: translate('contacts_page.contacts'),
sliver: SliverFillRemaining(
child: SearchableList<proto.Contact>.sliver(
initialList: widget.contactList.toList(),
itemBuilder: (c) =>
ContactItemWidget(contact: c, disabled: widget.disabled)
@ -58,8 +55,12 @@ class _ContactListWidgetState extends State<ContactListWidget> {
return widget.contactList
.where((element) =>
element.nickname.toLowerCase().contains(lowerValue) ||
element.profile.name.toLowerCase().contains(lowerValue) ||
element.profile.pronouns.toLowerCase().contains(lowerValue))
element.profile.name
.toLowerCase()
.contains(lowerValue) ||
element.profile.pronouns
.toLowerCase()
.contains(lowerValue))
.toList();
},
searchFieldHeight: 40,
@ -70,7 +71,7 @@ class _ContactListWidgetState extends State<ContactListWidget> {
inputDecoration: InputDecoration(
labelText: translate('contact_list.search'),
),
).paddingAll(8),
).paddingLTRB(8, 0, 8, 8);
),
)));
}
}

View File

@ -143,7 +143,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
(scaleConfig.preferBorders || scaleConfig.useVisualIndicators)
? activeBorder
: null,
borderRadius: 16 * scaleConfig.borderRadiusScale,
borderRadius: 12 * scaleConfig.borderRadiusScale,
callback: callback,
footerButtonIcon: loggedIn ? Icons.edit_outlined : null,
footerCallback: footerCallback,
@ -197,11 +197,11 @@ class _DrawerMenuState extends State<DrawerMenu> {
loading: () => _wrapInBox(
child: buildProgressIndicator(),
color: scaleScheme.grayScale.subtleBorder,
borderRadius: 16 * scaleConfig.borderRadiusScale),
borderRadius: 12 * scaleConfig.borderRadiusScale),
error: (err, st) => _wrapInBox(
child: errorPage(err, st),
color: scaleScheme.errorScale.subtleBorder,
borderRadius: 16 * scaleConfig.borderRadiusScale),
borderRadius: 12 * scaleConfig.borderRadiusScale),
);
loggedInAccounts.add(loggedInAccount.paddingLTRB(0, 0, 0, 8));
} else {
@ -254,7 +254,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
return IconButton(
icon: icon,
color: border,
constraints: const BoxConstraints.expand(height: 64, width: 64),
constraints: const BoxConstraints.expand(height: 48, width: 48),
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
@ -269,18 +269,18 @@ class _DrawerMenuState extends State<DrawerMenu> {
return RoundedRectangleBorder(
side: BorderSide(color: hoverBorder, width: 2),
borderRadius: BorderRadius.all(
Radius.circular(16 * scaleConfig.borderRadiusScale)));
Radius.circular(12 * scaleConfig.borderRadiusScale)));
}
if (states.contains(WidgetState.focused)) {
return RoundedRectangleBorder(
side: BorderSide(color: activeBorder, width: 2),
borderRadius: BorderRadius.all(
Radius.circular(16 * scaleConfig.borderRadiusScale)));
Radius.circular(12 * scaleConfig.borderRadiusScale)));
}
return RoundedRectangleBorder(
side: BorderSide(color: border, width: 2),
borderRadius: BorderRadius.all(
Radius.circular(16 * scaleConfig.borderRadiusScale)));
Radius.circular(12 * scaleConfig.borderRadiusScale)));
})),
tooltip: tooltip,
onPressed: onPressed);
@ -413,12 +413,18 @@ class _DrawerMenuState extends State<DrawerMenu> {
_getBottomButtons(),
Row(children: [
Text('${translate('menu.version')} $packageInfoVersion',
style: theme.textTheme.labelMedium!
.copyWith(color: scale.tertiaryScale.hoverBorder)),
style: theme.textTheme.labelMedium!.copyWith(
color: scaleConfig.preferBorders
? scale.tertiaryScale.hoverBorder
: scale.tertiaryScale.subtleBackground)),
const Spacer(),
SignalStrengthMeterWidget(
color: scale.tertiaryScale.hoverBorder,
inactiveColor: scale.tertiaryScale.border,
color: scaleConfig.preferBorders
? scale.tertiaryScale.hoverBorder
: scale.tertiaryScale.subtleBackground,
inactiveColor: scaleConfig.preferBorders
? scale.tertiaryScale.border
: scale.tertiaryScale.elementBackground,
),
])
]).paddingAll(16),

View File

@ -8,7 +8,6 @@ import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import 'drawer_menu/drawer_menu.dart';
import 'home_account_invalid.dart';
import 'home_account_locked.dart';
@ -37,9 +36,6 @@ class HomeScreenState extends State<HomeScreen>
.indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount);
final canClose = activeIndex != -1;
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.normal);
if (!canClose) {
await _zoomDrawerController.open!();
}
@ -129,7 +125,6 @@ class HomeScreenState extends State<HomeScreen>
final canClose = activeIndex != -1;
return SafeArea(
bottom: false,
child: DefaultTextStyle(
style: theme.textTheme.bodySmall!,
child: ZoomDrawer(

View File

@ -1,86 +0,0 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../../../contact_invitation/contact_invitation.dart';
import '../../../contacts/contacts.dart';
import '../../../theme/theme.dart';
class AccountPage extends StatefulWidget {
const AccountPage({
super.key,
});
@override
AccountPageState createState() => AccountPageState();
}
class AccountPageState extends State<AccountPage> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@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 scaleConfig = theme.extension<ScaleConfig>()!;
final cilState = context.watch<ContactInvitationListCubit>().state;
final cilBusy = cilState.busy;
final contactInvitationRecordList =
cilState.state.asData?.value.map((x) => x.value).toIList() ??
const IListConst([]);
final ciState = context.watch<ContactListCubit>().state;
final ciBusy = ciState.busy;
final contactList =
ciState.state.asData?.value.map((x) => x.value).toIList() ??
const IListConst([]);
return SizedBox(
child: Column(children: <Widget>[
if (contactInvitationRecordList.isNotEmpty)
ExpansionTile(
tilePadding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
backgroundColor: scale.primaryScale.border,
collapsedBackgroundColor: scale.primaryScale.border,
dense: true,
minTileHeight: 16,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16 * scaleConfig.borderRadiusScale),
),
collapsedShape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16 * scaleConfig.borderRadiusScale),
),
title: Text(
translate('account_page.contact_invitations'),
textAlign: TextAlign.center,
style: textTheme.titleSmall!
.copyWith(color: scale.primaryScale.borderText),
),
iconColor: scale.primaryScale.borderText,
collapsedIconColor: scale.primaryScale.borderText,
initiallyExpanded: true,
children: [
ContactInvitationListWidget(
contactInvitationRecordList: contactInvitationRecordList,
disabled: cilBusy)
],
).paddingLTRB(8, 0, 8, 8),
ContactListWidget(contactList: contactList, disabled: ciBusy).expanded(),
]));
}
}

View File

@ -1,4 +1,3 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart';
import '../../../chat_list/chat_list.dart';
@ -24,8 +23,6 @@ class ChatsPageState extends State<ChatsPage> {
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Column(children: <Widget>[
const ChatListWidget().expanded(),
]);
return const ChatListWidget();
}
}

View File

@ -0,0 +1,59 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../contact_invitation/contact_invitation.dart';
import '../../../contacts/contacts.dart';
class ContactsPage extends StatefulWidget {
const ContactsPage({
super.key,
});
@override
ContactsPageState createState() => ContactsPageState();
}
class ContactsPageState extends State<ContactsPage> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@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 scaleConfig = theme.extension<ScaleConfig>()!;
final cilState = context.watch<ContactInvitationListCubit>().state;
final cilBusy = cilState.busy;
final contactInvitationRecordList =
cilState.state.asData?.value.map((x) => x.value).toIList() ??
const IListConst([]);
final ciState = context.watch<ContactListCubit>().state;
final ciBusy = ciState.busy;
final contactList =
ciState.state.asData?.value.map((x) => x.value).toIList() ??
const IListConst([]);
return CustomScrollView(slivers: [
if (contactInvitationRecordList.isNotEmpty)
SliverPadding(
padding: const EdgeInsets.only(bottom: 8),
sliver: ContactInvitationListWidget(
contactInvitationRecordList: contactInvitationRecordList,
disabled: cilBusy)),
ContactListWidget(contactList: contactList, disabled: ciBusy),
]).paddingLTRB(8, 0, 8, 8);
}
}

View File

@ -13,9 +13,9 @@ import 'package:provider/provider.dart';
import '../../../chat/chat.dart';
import '../../../contact_invitation/contact_invitation.dart';
import '../../../theme/theme.dart';
import 'account_page.dart';
import 'bottom_sheet_action_button.dart';
import 'chats_page.dart';
import 'contacts_page.dart';
class MainPager extends StatefulWidget {
const MainPager({super.key});
@ -41,25 +41,6 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
super.dispose();
}
bool _onScrollNotification(ScrollNotification notification) {
if (notification is UserScrollNotification &&
notification.metrics.axis == Axis.vertical) {
switch (notification.direction) {
case ScrollDirection.forward:
// _hideBottomBarAnimationController.reverse();
// _fabAnimationController.forward(from: 0);
break;
case ScrollDirection.reverse:
// _hideBottomBarAnimationController.forward();
// _fabAnimationController.reverse(from: 1);
break;
case ScrollDirection.idle:
break;
}
}
return false;
}
Future<void> scanContactInvitationDialog(BuildContext context) async {
await showDialog<void>(
context: context,
@ -162,9 +143,7 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
return Scaffold(
//extendBody: true,
backgroundColor: Colors.transparent,
body: NotificationListener<ScrollNotification>(
onNotification: _onScrollNotification,
child: PreloadPageView(
body: PreloadPageView(
key: _pageViewKey,
controller: pageController,
preloadPagesCount: 2,
@ -174,9 +153,9 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
});
},
children: const [
AccountPage(),
ContactsPage(),
ChatsPage(),
])),
]),
// appBar: AppBar(
// toolbarHeight: 24,
// title: Text(
@ -240,7 +219,7 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
// ];
final _fabIconList = <IconData>[
Icons.person_add_sharp,
Icons.add_comment_sharp,
Icons.chat,
];
final _bottomLabelList = <String>[
translate('pager.contacts'),

View File

@ -11,16 +11,11 @@ class Splash extends StatefulWidget {
State<Splash> createState() => _SplashState();
}
class _SplashState extends State<Splash> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.hidden, OrientationCapability.normal);
});
}
class _SplashState extends WindowSetupState<Splash> {
_SplashState()
: super(
titleBarStyle: TitleBarStyle.hidden,
orientationCapability: OrientationCapability.portraitOnly);
@override
Widget build(BuildContext context) => PopScope(

View File

@ -7,7 +7,6 @@ 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 'settings.dart';
@ -26,11 +25,6 @@ class SettingsPageState extends State<SettingsPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.normal);
});
}
@override

View File

@ -12,7 +12,7 @@ class StyledScaffold extends StatelessWidget {
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
return isDesktop
final scaffold = isDesktop
? clipBorder(
clipEnabled: true,
borderEnabled: scaleConfig.useVisualIndicators,
@ -21,6 +21,10 @@ class StyledScaffold extends StatelessWidget {
child: Scaffold(appBar: appBar, body: body, key: key))
.paddingAll(32)
: Scaffold(appBar: appBar, body: body, key: key);
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: scaffold);
}
////////////////////////////////////////////////////////////////////////////

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:async_tools/async_tools.dart';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
@ -5,8 +7,10 @@ import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
import 'package:motion_toast/motion_toast.dart';
import 'package:quickalert/quickalert.dart';
import 'package:sliver_expandable/sliver_expandable.dart';
import '../theme.dart';
@ -132,7 +136,7 @@ void showErrorToast(BuildContext context, String message) {
contentPadding: const EdgeInsets.all(16),
primaryColor: scale.errorScale.elementBackground,
secondaryColor: scale.errorScale.calloutBackground,
borderRadius: 16 * scaleConfig.borderRadiusScale,
borderRadius: 12 * scaleConfig.borderRadiusScale,
toastDuration: const Duration(seconds: 4),
animationDuration: const Duration(milliseconds: 1000),
displayBorder: scaleConfig.useVisualIndicators,
@ -152,7 +156,7 @@ void showInfoToast(BuildContext context, String message) {
contentPadding: const EdgeInsets.all(16),
primaryColor: scale.tertiaryScale.elementBackground,
secondaryColor: scale.tertiaryScale.calloutBackground,
borderRadius: 16 * scaleConfig.borderRadiusScale,
borderRadius: 12 * scaleConfig.borderRadiusScale,
toastDuration: const Duration(seconds: 2),
animationDuration: const Duration(milliseconds: 500),
displayBorder: scaleConfig.useVisualIndicators,
@ -160,6 +164,159 @@ void showInfoToast(BuildContext context, String message) {
).show(context);
}
SliverAppBar styledSliverAppBar(
{required BuildContext context, required String title, Color? titleColor}) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
//final scaleConfig = theme.extension<ScaleConfig>()!;
final textTheme = theme.textTheme;
return SliverAppBar(
title: Text(
title,
style: textTheme.titleSmall!
.copyWith(color: titleColor ?? scale.primaryScale.borderText),
),
pinned: true,
);
}
Widget styledHeaderSliver(
{required BuildContext context,
required String title,
required Widget sliver,
Color? borderColor,
Color? innerColor,
Color? titleColor,
Color? backgroundColor,
void Function()? onTap}) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
final textTheme = theme.textTheme;
return SliverStickyHeader(
header: ColoredBox(
color: backgroundColor ?? Colors.transparent,
child: DecoratedBox(
decoration: ShapeDecoration(
color: borderColor ?? scale.primaryScale.border,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft:
Radius.circular(12 * scaleConfig.borderRadiusScale),
topRight: Radius.circular(
12 * scaleConfig.borderRadiusScale)))),
child: ListTile(
onTap: onTap,
title: Text(title,
textAlign: TextAlign.center,
style: textTheme.titleSmall!.copyWith(
color: titleColor ?? scale.primaryScale.borderText)),
),
)),
sliver: DecoratedSliver(
decoration: ShapeDecoration(
color: borderColor ?? scale.primaryScale.border,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft:
Radius.circular(8 * scaleConfig.borderRadiusScale),
bottomRight:
Radius.circular(8 * scaleConfig.borderRadiusScale)))),
sliver: SliverPadding(
padding: const EdgeInsets.all(4),
sliver: DecoratedSliver(
decoration: ShapeDecoration(
color: innerColor ?? scale.primaryScale.subtleBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale))),
sliver: SliverPadding(
padding: const EdgeInsets.all(8),
sliver: sliver,
)))),
);
}
Widget styledExpandingSliver(
{required BuildContext context,
required String title,
required Widget sliver,
required bool expanded,
required Animation<double> animation,
Color? borderColor,
Color? innerColor,
Color? titleColor,
Color? backgroundColor,
void Function()? onTap}) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
final textTheme = theme.textTheme;
return SliverStickyHeader(
header: ColoredBox(
color: backgroundColor ?? Colors.transparent,
child: DecoratedBox(
decoration: ShapeDecoration(
color: borderColor ?? scale.primaryScale.border,
shape: RoundedRectangleBorder(
borderRadius: expanded
? BorderRadius.only(
topLeft: Radius.circular(
12 * scaleConfig.borderRadiusScale),
topRight: Radius.circular(
12 * scaleConfig.borderRadiusScale))
: BorderRadius.circular(
12 * scaleConfig.borderRadiusScale))),
child: ListTile(
onTap: onTap,
title: Text(title,
textAlign: TextAlign.center,
style: textTheme.titleSmall!.copyWith(
color: titleColor ?? scale.primaryScale.borderText)),
trailing: AnimatedBuilder(
animation: animation,
builder: (context, child) => Transform.rotate(
angle: (animation.value - 0.5) * pi,
child: child,
),
child: Icon(Icons.chevron_left,
color: borderColor ?? scale.primaryScale.borderText),
),
),
)),
sliver: SliverExpandable(
sliver: DecoratedSliver(
decoration: ShapeDecoration(
color: borderColor ?? scale.primaryScale.border,
shape: RoundedRectangleBorder(
borderRadius: expanded
? BorderRadius.only(
bottomLeft: Radius.circular(
8 * scaleConfig.borderRadiusScale),
bottomRight: Radius.circular(
8 * scaleConfig.borderRadiusScale))
: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale))),
sliver: SliverPadding(
padding: const EdgeInsets.all(4),
sliver: DecoratedSliver(
decoration: ShapeDecoration(
color:
innerColor ?? scale.primaryScale.subtleBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale))),
sliver: SliverPadding(
padding: const EdgeInsets.all(8),
sliver: sliver,
)))),
animation: animation,
));
}
Widget styledTitleContainer({
required BuildContext context,
required String title,
@ -178,7 +335,7 @@ Widget styledTitleContainer({
color: borderColor ?? scale.primaryScale.border,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16 * scaleConfig.borderRadiusScale),
BorderRadius.circular(12 * scaleConfig.borderRadiusScale),
)),
child: Column(children: [
Text(
@ -192,7 +349,7 @@ Widget styledTitleContainer({
backgroundColor ?? scale.primaryScale.subtleBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
16 * scaleConfig.borderRadiusScale),
12 * scaleConfig.borderRadiusScale),
)),
child: child)
.paddingAll(4)

View File

@ -0,0 +1,111 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:native_device_orientation/native_device_orientation.dart';
class NativeSafeArea extends StatelessWidget {
const NativeSafeArea({
required this.child,
this.left = true,
this.top = true,
this.right = true,
this.bottom = true,
this.minimum = EdgeInsets.zero,
this.maintainBottomViewPadding = false,
super.key,
});
/// Whether to avoid system intrusions on the left.
final bool left;
/// Whether to avoid system intrusions at the top of the screen, typically the
/// system status bar.
final bool top;
/// Whether to avoid system intrusions on the right.
final bool right;
/// Whether to avoid system intrusions on the bottom side of the screen.
final bool bottom;
/// This minimum padding to apply.
///
/// The greater of the minimum insets and the media padding will be applied.
final EdgeInsets minimum;
/// Specifies whether the [SafeArea] should maintain the bottom
/// [MediaQueryData.viewPadding] instead of the bottom
/// [MediaQueryData.padding], defaults to false.
///
/// For example, if there is an onscreen keyboard displayed above the
/// SafeArea, the padding can be maintained below the obstruction rather than
/// being consumed. This can be helpful in cases where your layout contains
/// flexible widgets, which could visibly move when opening a software
/// keyboard due to the change in the padding value. Setting this to true will
/// avoid the UI shift.
final bool maintainBottomViewPadding;
/// The widget below this widget in the tree.
///
/// The padding on the [MediaQuery] for the [child] will be suitably adjusted
/// to zero out any sides that were avoided by this widget.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget child;
@override
Widget build(BuildContext context) {
final nativeOrientation =
NativeDeviceOrientationReader.orientation(context);
late final bool realLeft;
late final bool realRight;
late final bool realTop;
late final bool realBottom;
switch (nativeOrientation) {
case NativeDeviceOrientation.unknown:
case NativeDeviceOrientation.portraitUp:
realLeft = left;
realRight = right;
realTop = top;
realBottom = bottom;
case NativeDeviceOrientation.portraitDown:
realLeft = right;
realRight = left;
realTop = bottom;
realBottom = top;
case NativeDeviceOrientation.landscapeRight:
realLeft = bottom;
realRight = top;
realTop = left;
realBottom = right;
case NativeDeviceOrientation.landscapeLeft:
realLeft = top;
realRight = bottom;
realTop = right;
realBottom = left;
}
return SafeArea(
left: realLeft,
right: realRight,
top: realTop,
bottom: realBottom,
minimum: minimum,
maintainBottomViewPadding: maintainBottomViewPadding,
child: child);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<bool>('left', left))
..add(DiagnosticsProperty<bool>('top', top))
..add(DiagnosticsProperty<bool>('right', right))
..add(DiagnosticsProperty<bool>('bottom', bottom))
..add(DiagnosticsProperty<EdgeInsets>('minimum', minimum))
..add(DiagnosticsProperty<bool>(
'maintainBottomViewPadding', maintainBottomViewPadding));
}
}

View File

@ -1,10 +1,13 @@
import 'dart:async';
import 'package:async_tools/async_tools.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:window_manager/window_manager.dart';
import '../theme/views/responsive.dart';
import 'tools.dart';
export 'package:window_manager/window_manager.dart' show TitleBarStyle;
@ -27,7 +30,7 @@ Future<void> initializeWindowControl() async {
skipTaskbar: false,
);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await changeWindowSetup(
await _asyncChangeWindowSetup(
TitleBarStyle.hidden, OrientationCapability.normal);
await windowManager.show();
await windowManager.focus();
@ -35,7 +38,9 @@ Future<void> initializeWindowControl() async {
}
}
Future<void> changeWindowSetup(TitleBarStyle titleBarStyle,
const kWindowSetup = '__windowSetup';
Future<void> _asyncChangeWindowSetup(TitleBarStyle titleBarStyle,
OrientationCapability orientationCapability) async {
if (isDesktop) {
await windowManager.setTitleBarStyle(titleBarStyle);
@ -59,3 +64,47 @@ Future<void> changeWindowSetup(TitleBarStyle titleBarStyle,
}
}
}
void changeWindowSetup(
TitleBarStyle titleBarStyle, OrientationCapability orientationCapability) {
singleFuture<void>(
kWindowSetup,
() async =>
_asyncChangeWindowSetup(titleBarStyle, orientationCapability));
}
abstract class WindowSetupState<T extends StatefulWidget> extends State<T> {
WindowSetupState(
{required this.titleBarStyle, required this.orientationCapability});
@override
void initState() {
changeWindowSetup(this.titleBarStyle, this.orientationCapability);
super.initState();
}
@override
void activate() {
changeWindowSetup(this.titleBarStyle, this.orientationCapability);
super.activate();
}
@override
void deactivate() {
changeWindowSetup(TitleBarStyle.normal, OrientationCapability.normal);
super.deactivate();
}
////////////////////////////////////////////////////////////////////////////
final TitleBarStyle titleBarStyle;
final OrientationCapability orientationCapability;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(EnumProperty<TitleBarStyle>('titleBarStyle', titleBarStyle))
..add(EnumProperty<OrientationCapability>(
'orientationCapability', orientationCapability));
}
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:ansicolor/ansicolor.dart';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:cool_dropdown/cool_dropdown.dart';
@ -45,11 +47,6 @@ class _DeveloperPageState extends State<DeveloperPage> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.normal);
});
_terminalController.addListener(() {
setState(() {});
});
@ -273,7 +270,6 @@ class _DeveloperPageState extends State<DeveloperPage> {
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: SafeArea(
bottom: false,
child: Column(children: [
Stack(alignment: AlignmentDirectional.center, children: [
Image.asset('assets/images/ellet.png'),
@ -313,8 +309,7 @@ class _DeveloperPageState extends State<DeveloperPage> {
onPressed: _debugCommandController.text.isEmpty
? null
: () async {
final debugCommand =
_debugCommandController.text;
final debugCommand = _debugCommandController.text;
_debugCommandController.clear();
await _sendDebugCommand(debugCommand);
},

View File

@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "67.0.0"
accordion:
dependency: "direct main"
description:
name: accordion
sha256: "0eca3d1c619c6df63d6e384010fd2ef1164e7385d7102f88a6b56f658f160cd0"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
analyzer:
dependency: transitive
description:
@ -449,6 +457,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.5"
expansion_tile_group:
dependency: "direct main"
description:
name: expansion_tile_group
sha256: "6918433891481c7d98cbc604d7b4c93509986e8134d52940853301ad6fbff404"
url: "https://pub.dev"
source: hosted
version: "1.2.4"
fast_immutable_collections:
dependency: "direct main"
description:
@ -620,6 +636,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.2.1"
flutter_sticky_header:
dependency: "direct main"
description:
name: flutter_sticky_header
sha256: "017f398fbb45a589e01491861ca20eb6570a763fd9f3888165a978e11248c709"
url: "https://pub.dev"
source: hosted
version: "0.6.5"
flutter_svg:
dependency: "direct main"
description:
@ -681,6 +705,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
get:
dependency: transitive
description:
name: get
sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e
url: "https://pub.dev"
source: hosted
version: "4.6.6"
glob:
dependency: transitive
description:
@ -897,6 +929,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.10.0"
native_device_orientation:
dependency: "direct main"
description:
name: native_device_orientation
sha256: "0c330c068575e4be72cce5968ca479a3f8d5d1e5dfce7d89d5c13a1e943b338c"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
nested:
dependency: transitive
description:
@ -1326,6 +1366,30 @@ packages:
description: flutter
source: sdk
version: "0.0.99"
sliver_expandable:
dependency: "direct main"
description:
name: sliver_expandable
sha256: ae20eb848bd0ba9dd704732ad654438ac5a5bea2b023fa3cf80a086166d96d97
url: "https://pub.dev"
source: hosted
version: "1.1.1"
sliver_fill_remaining_box_adapter:
dependency: "direct main"
description:
name: sliver_fill_remaining_box_adapter
sha256: "2a222c0f09eb07c37857ce2526c0fbf3b17b2bd1b1ff0e890085f2f7a9ba1927"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
sliver_tools:
dependency: "direct main"
description:
name: sliver_tools
sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6
url: "https://pub.dev"
source: hosted
version: "0.2.12"
smart_auth:
dependency: transitive
description:
@ -1583,6 +1647,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.4.0"
value_layout_builder:
dependency: transitive
description:
name: value_layout_builder
sha256: "98202ec1807e94ac72725b7f0d15027afde513c55c69ff3f41bcfccb950831bc"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
vector_graphics:
dependency: transitive
description:

View File

@ -8,6 +8,7 @@ environment:
flutter: '>=3.22.1'
dependencies:
accordion: ^2.6.0
animated_bottom_navigation_bar: ^1.3.3
animated_switcher_transitions: ^1.0.0
animated_theme_switcher: ^2.0.10
@ -27,6 +28,7 @@ dependencies:
cool_dropdown: ^2.1.0
cupertino_icons: ^1.0.8
equatable: ^2.0.5
expansion_tile_group: ^1.2.4
fast_immutable_collections: ^10.2.4
file_saver: ^0.2.13
fixnum: ^1.1.0
@ -46,6 +48,7 @@ dependencies:
flutter_native_splash: ^2.4.0
flutter_slidable: ^3.1.0
flutter_spinkit: ^5.2.1
flutter_sticky_header: ^0.6.5
flutter_svg: ^2.0.10+1
flutter_translate: ^4.1.0
flutter_zoom_drawer: ^3.2.0
@ -60,6 +63,7 @@ dependencies:
meta: ^1.12.0
mobile_scanner: ^5.1.1
motion_toast: ^2.10.0
native_device_orientation: ^2.0.3
package_info_plus: ^8.0.0
pasteboard: ^0.2.0
path: ^1.9.0
@ -81,6 +85,9 @@ dependencies:
share_plus: ^9.0.0
shared_preferences: ^2.2.3
signal_strength_indicator: ^0.4.1
sliver_expandable: ^1.1.1
sliver_fill_remaining_box_adapter: ^1.0.0
sliver_tools: ^0.2.12
sorted_list:
git:
url: https://gitlab.com/veilid/dart-sorted-list-improved.git